diff options
940 files changed, 40204 insertions, 23317 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 09ff2454ee..ebbbe345fd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -73,7 +73,6 @@ doc_classes/* @godotengine/documentation /modules/minimp3/ @godotengine/audio /modules/ogg/ @godotengine/audio /modules/opus/ @godotengine/audio -/modules/stb_vorbis/ @godotengine/audio /modules/theora/ @godotengine/audio /modules/vorbis/ @godotengine/audio /modules/webm/ @godotengine/audio diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml index a618b69e30..5efeba4b5f 100644 --- a/.github/workflows/android_builds.yml +++ b/.github/workflows/android_builds.yml @@ -7,6 +7,10 @@ env: SCONSFLAGS: platform=android verbose=yes warnings=extra werror=yes debug_symbols=no --jobs=2 module_text_server_fb_enabled=yes SCONS_CACHE_LIMIT: 4096 +concurrency: + group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-android + cancel-in-progress: true + jobs: android-template: runs-on: "ubuntu-20.04" diff --git a/.github/workflows/ios_builds.yml b/.github/workflows/ios_builds.yml index 6f5ffcf42f..69809c6cb6 100644 --- a/.github/workflows/ios_builds.yml +++ b/.github/workflows/ios_builds.yml @@ -7,6 +7,10 @@ env: SCONSFLAGS: platform=iphone verbose=yes warnings=extra werror=yes debug_symbols=no --jobs=2 module_text_server_fb_enabled=yes SCONS_CACHE_LIMIT: 4096 +concurrency: + group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-ios + cancel-in-progress: true + jobs: ios-template: runs-on: "macos-latest" diff --git a/.github/workflows/javascript_builds.yml b/.github/workflows/javascript_builds.yml index 5124197d3d..25a063c3b2 100644 --- a/.github/workflows/javascript_builds.yml +++ b/.github/workflows/javascript_builds.yml @@ -9,6 +9,10 @@ env: EM_VERSION: 2.0.27 EM_CACHE_FOLDER: 'emsdk-cache' +concurrency: + group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-javascript + cancel-in-progress: true + jobs: javascript-template: runs-on: "ubuntu-20.04" diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index e2b9fa8a7b..6fe76345f3 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -7,6 +7,10 @@ env: SCONSFLAGS: platform=linuxbsd verbose=yes warnings=extra werror=yes debug_symbols=no --jobs=2 module_text_server_fb_enabled=yes SCONS_CACHE_LIMIT: 4096 +concurrency: + group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-linux + cancel-in-progress: true + jobs: linux-editor: runs-on: "ubuntu-20.04" diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index abc561eaa9..2c9e0fa1a0 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -7,6 +7,10 @@ env: SCONSFLAGS: platform=osx verbose=yes warnings=extra werror=yes debug_symbols=no --jobs=2 module_text_server_fb_enabled=yes SCONS_CACHE_LIMIT: 4096 +concurrency: + group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-macos + cancel-in-progress: true + jobs: macos-editor: runs-on: "macos-latest" diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 30468034ff..fd2e748076 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -1,6 +1,10 @@ name: 📊 Static Checks on: [push, pull_request] +concurrency: + group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-static + cancel-in-progress: true + jobs: static-checks: name: Static Checks (clang-format, black format, file format, documentation checks) diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index 1f9e3ac5e3..53febd353b 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -9,6 +9,10 @@ env: SCONS_CACHE_MSVC_CONFIG: true SCONS_CACHE_LIMIT: 3072 +concurrency: + group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-windows + cancel-in-progress: true + jobs: windows-editor: # Windows 10 with latest image diff --git a/.gitignore b/.gitignore index c1c2374bc3..00d87b6bf6 100644 --- a/.gitignore +++ b/.gitignore @@ -83,7 +83,7 @@ gmon.out __MACOSX logs/ -# for projects that use SCons for building: http://http://www.scons.org/ +# for projects that use SCons for building: https://www.scons.org/ .sconf_temp .sconsign*.dblite *.pyc diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index ef444721b2..e46cc6f3f4 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -367,7 +367,6 @@ Copyright: 2016-2020, Aras Pranckevicius License: public-domain or Unlicense or Expat Files: ./thirdparty/misc/stb_rect_pack.h - ./thirdparty/misc/stb_vorbis.c Comment: stb libraries Copyright: Sean Barrett License: public-domain or Unlicense or Expat @@ -377,6 +376,11 @@ Comment: YUV2RGB Copyright: 2008-2011, Robin Watts License: BSD-2-clause +Files: ./thirdparty/msdfgen/ +Comment: Multi-channel signed distance field generator +Copyright: 2016, Viktor Chlumsky +License: MIT + Files: ./thirdparty/nanosvg/ Comment: NanoSVG Copyright: 2013-2014, Mikko Mononen @@ -1135,7 +1139,7 @@ License: glslang GNU General Public License for more details. . You should have received a copy of the GNU General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. + along with this program. If not, see <https://www.gnu.org/licenses/>. . As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work @@ -1761,7 +1765,7 @@ License: MPL-2.0 . This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. + file, You can obtain one at https://mozilla.org/MPL/2.0/. . If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE diff --git a/SConstruct b/SConstruct index 1ab42b6b90..8feb9e61bb 100644 --- a/SConstruct +++ b/SConstruct @@ -156,6 +156,7 @@ opts.Add(BoolVariable("builtin_certs", "Use the built-in SSL certificates bundle opts.Add(BoolVariable("builtin_embree", "Use the built-in Embree library", True)) opts.Add(BoolVariable("builtin_enet", "Use the built-in ENet library", True)) opts.Add(BoolVariable("builtin_freetype", "Use the built-in FreeType library", True)) +opts.Add(BoolVariable("builtin_msdfgen", "Use the built-in MSDFgen library", True)) opts.Add(BoolVariable("builtin_glslang", "Use the built-in glslang library", True)) opts.Add(BoolVariable("builtin_graphite", "Use the built-in Graphite library", True)) opts.Add(BoolVariable("builtin_harfbuzz", "Use the built-in HarfBuzz library", True)) @@ -316,10 +317,10 @@ if env_base["target"] == "debug": # will properly be rebuilt. As such, we only enable them for debug (dev) builds, not release. # To decide whether to rebuild a file, use the MD5 sum only if the timestamp has changed. - # http://scons.org/doc/production/HTML/scons-user/ch06.html#idm139837621851792 + # https://scons.org/doc/production/HTML/scons-user/ch06.html#idm139837621851792 env_base.Decider("MD5-timestamp") # Use cached implicit dependencies by default. Can be overridden by specifying `--implicit-deps-changed` in the command line. - # http://scons.org/doc/production/HTML/scons-user/ch06s04.html + # https://scons.org/doc/production/HTML/scons-user/ch06s04.html env_base.SetOption("implicit_cache", 1) if env_base["no_editor_splash"]: diff --git a/core/SCsub b/core/SCsub index d9167b8f83..14dfa3487f 100644 --- a/core/SCsub +++ b/core/SCsub @@ -183,6 +183,7 @@ SConscript("os/SCsub") SConscript("math/SCsub") SConscript("crypto/SCsub") SConscript("io/SCsub") +SConscript("multiplayer/SCsub") SConscript("debugger/SCsub") SConscript("input/SCsub") SConscript("variant/SCsub") diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 495670bc88..d8fbb50a75 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -199,17 +199,41 @@ bool Engine::is_printing_error_messages() const { } void Engine::add_singleton(const Singleton &p_singleton) { + ERR_FAIL_COND_MSG(singleton_ptrs.has(p_singleton.name), "Can't register singleton that already exists: " + String(p_singleton.name)); singletons.push_back(p_singleton); singleton_ptrs[p_singleton.name] = p_singleton.ptr; } -Object *Engine::get_singleton_object(const String &p_name) const { +Object *Engine::get_singleton_object(const StringName &p_name) const { const Map<StringName, Object *>::Element *E = singleton_ptrs.find(p_name); - ERR_FAIL_COND_V_MSG(!E, nullptr, "Failed to retrieve non-existent singleton '" + p_name + "'."); + ERR_FAIL_COND_V_MSG(!E, nullptr, "Failed to retrieve non-existent singleton '" + String(p_name) + "'."); return E->get(); } -bool Engine::has_singleton(const String &p_name) const { +bool Engine::is_singleton_user_created(const StringName &p_name) const { + ERR_FAIL_COND_V(!singleton_ptrs.has(p_name), false); + + for (const Singleton &E : singletons) { + if (E.name == p_name && E.user_created) { + return true; + } + } + + return false; +} +void Engine::remove_singleton(const StringName &p_name) { + ERR_FAIL_COND(!singleton_ptrs.has(p_name)); + + for (List<Singleton>::Element *E = singletons.front(); E; E = E->next()) { + if (E->get().name == p_name) { + singletons.erase(E); + singleton_ptrs.erase(p_name); + return; + } + } +} + +bool Engine::has_singleton(const StringName &p_name) const { return singleton_ptrs.has(p_name); } diff --git a/core/config/engine.h b/core/config/engine.h index e6b5df2d5a..ae33acede2 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -42,6 +42,7 @@ public: StringName name; Object *ptr; StringName class_name; //used for binding generation hinting + bool user_created = false; Singleton(const StringName &p_name = StringName(), Object *p_ptr = nullptr, const StringName &p_class_name = StringName()); }; @@ -109,8 +110,10 @@ public: void add_singleton(const Singleton &p_singleton); void get_singletons(List<Singleton> *p_singletons); - bool has_singleton(const String &p_name) const; - Object *get_singleton_object(const String &p_name) const; + bool has_singleton(const StringName &p_name) const; + Object *get_singleton_object(const StringName &p_name) const; + void remove_singleton(const StringName &p_name); + bool is_singleton_user_created(const StringName &p_name) const; #ifdef TOOLS_ENABLED _FORCE_INLINE_ void set_editor_hint(bool p_enabled) { editor_hint = p_enabled; } diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index c5e6c6d685..03892d1d4f 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1006,7 +1006,7 @@ bool ProjectSettings::has_custom_feature(const String &p_feature) const { return custom_features.has(p_feature); } -Map<StringName, ProjectSettings::AutoloadInfo> ProjectSettings::get_autoload_list() const { +OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> ProjectSettings::get_autoload_list() const { return autoloads; } diff --git a/core/config/project_settings.h b/core/config/project_settings.h index ed8fb19fa0..7e93f26f0d 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -33,6 +33,7 @@ #include "core/object/class_db.h" #include "core/os/thread_safe.h" +#include "core/templates/ordered_hash_map.h" #include "core/templates/set.h" class ProjectSettings : public Object { @@ -91,7 +92,7 @@ protected: Set<String> custom_features; Map<StringName, StringName> feature_overrides; - Map<StringName, AutoloadInfo> autoloads; + OrderedHashMap<StringName, AutoloadInfo> autoloads; bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -168,7 +169,7 @@ public: bool has_custom_feature(const String &p_feature) const; - Map<StringName, AutoloadInfo> get_autoload_list() const; + OrderedHashMap<StringName, AutoloadInfo> get_autoload_list() const; void add_autoload(const AutoloadInfo &p_autoload); void remove_autoload(const StringName &p_autoload); bool has_autoload(const StringName &p_autoload) const; diff --git a/core/core_bind.cpp b/core/core_bind.cpp index efb4b84716..fd5b3bb731 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -1504,7 +1504,7 @@ String Directory::get_current_dir() { Error Directory::make_dir(String p_dir) { ERR_FAIL_COND_V_MSG(!d, ERR_UNCONFIGURED, "Directory is not configured properly."); - if (!p_dir.is_rel_path()) { + if (!p_dir.is_relative_path()) { DirAccess *d = DirAccess::create_for_path(p_dir); Error err = d->make_dir(p_dir); memdelete(d); @@ -1515,7 +1515,7 @@ Error Directory::make_dir(String p_dir) { Error Directory::make_dir_recursive(String p_dir) { ERR_FAIL_COND_V_MSG(!d, ERR_UNCONFIGURED, "Directory is not configured properly."); - if (!p_dir.is_rel_path()) { + if (!p_dir.is_relative_path()) { DirAccess *d = DirAccess::create_for_path(p_dir); Error err = d->make_dir_recursive(p_dir); memdelete(d); @@ -1526,7 +1526,7 @@ Error Directory::make_dir_recursive(String p_dir) { bool Directory::file_exists(String p_file) { ERR_FAIL_COND_V_MSG(!d, false, "Directory is not configured properly."); - if (!p_file.is_rel_path()) { + if (!p_file.is_relative_path()) { return FileAccess::exists(p_file); } @@ -1535,7 +1535,7 @@ bool Directory::file_exists(String p_file) { bool Directory::dir_exists(String p_dir) { ERR_FAIL_COND_V_MSG(!d, false, "Directory is not configured properly."); - if (!p_dir.is_rel_path()) { + if (!p_dir.is_relative_path()) { DirAccess *d = DirAccess::create_for_path(p_dir); bool exists = d->dir_exists(p_dir); memdelete(d); @@ -1559,7 +1559,7 @@ Error Directory::rename(String p_from, String p_to) { ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use."); ERR_FAIL_COND_V_MSG(p_from.is_empty() || p_from == "." || p_from == "..", ERR_INVALID_PARAMETER, "Invalid path to rename."); - if (!p_from.is_rel_path()) { + if (!p_from.is_relative_path()) { DirAccess *d = DirAccess::create_for_path(p_from); ERR_FAIL_COND_V_MSG(!d->file_exists(p_from) && !d->dir_exists(p_from), ERR_DOES_NOT_EXIST, "File or directory does not exist."); Error err = d->rename(p_from, p_to); @@ -1573,7 +1573,7 @@ Error Directory::rename(String p_from, String p_to) { Error Directory::remove(String p_name) { ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use."); - if (!p_name.is_rel_path()) { + if (!p_name.is_relative_path()) { DirAccess *d = DirAccess::create_for_path(p_name); Error err = d->remove(p_name); memdelete(d); @@ -2165,14 +2165,41 @@ bool Engine::is_in_physics_frame() const { return ::Engine::get_singleton()->is_in_physics_frame(); } -bool Engine::has_singleton(const String &p_name) const { +bool Engine::has_singleton(const StringName &p_name) const { return ::Engine::get_singleton()->has_singleton(p_name); } -Object *Engine::get_singleton_object(const String &p_name) const { +Object *Engine::get_singleton_object(const StringName &p_name) const { return ::Engine::get_singleton()->get_singleton_object(p_name); } +void Engine::register_singleton(const StringName &p_name, Object *p_object) { + ERR_FAIL_COND_MSG(has_singleton(p_name), "Singleton already registered: " + String(p_name)); + ERR_FAIL_COND_MSG(p_name.operator String().is_valid_identifier(), "Singleton name is not a valid identifier: " + String(p_name)); + ::Engine::Singleton s; + s.class_name = p_name; + s.name = p_name; + s.ptr = p_object; + s.user_created = true; + ::Engine::get_singleton()->add_singleton(s); + ; +} +void Engine::unregister_singleton(const StringName &p_name) { + ERR_FAIL_COND_MSG(!has_singleton(p_name), "Attempt to remove unregisteres singleton: " + String(p_name)); + ERR_FAIL_COND_MSG(!::Engine::get_singleton()->is_singleton_user_created(p_name), "Attempt to remove non-user created singleton: " + String(p_name)); + ::Engine::get_singleton()->remove_singleton(p_name); +} + +Vector<String> Engine::get_singleton_list() const { + List<::Engine::Singleton> singletons; + ::Engine::get_singleton()->get_singletons(&singletons); + Vector<String> ret; + for (List<::Engine::Singleton>::Element *E = singletons.front(); E; E = E->next()) { + ret.push_back(E->get().name); + } + return ret; +} + void Engine::set_editor_hint(bool p_enabled) { ::Engine::get_singleton()->set_editor_hint(p_enabled); } @@ -2220,6 +2247,10 @@ void Engine::_bind_methods() { ClassDB::bind_method(D_METHOD("has_singleton", "name"), &Engine::has_singleton); ClassDB::bind_method(D_METHOD("get_singleton", "name"), &Engine::get_singleton_object); + ClassDB::bind_method(D_METHOD("register_singleton", "name", "instance"), &Engine::register_singleton); + ClassDB::bind_method(D_METHOD("unregister_singleton", "name"), &Engine::unregister_singleton); + ClassDB::bind_method(D_METHOD("get_singleton_list"), &Engine::get_singleton_list); + ClassDB::bind_method(D_METHOD("set_editor_hint", "enabled"), &Engine::set_editor_hint); ClassDB::bind_method(D_METHOD("is_editor_hint"), &Engine::is_editor_hint); diff --git a/core/core_bind.h b/core/core_bind.h index 1dbe49f418..a6fac63edd 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -639,8 +639,11 @@ public: bool is_in_physics_frame() const; - bool has_singleton(const String &p_name) const; - Object *get_singleton_object(const String &p_name) const; + bool has_singleton(const StringName &p_name) const; + Object *get_singleton_object(const StringName &p_name) const; + void register_singleton(const StringName &p_name, Object *p_object); + void unregister_singleton(const StringName &p_name); + Vector<String> get_singleton_list() const; void set_editor_hint(bool p_enabled); bool is_editor_hint() const; diff --git a/core/core_constants.cpp b/core/core_constants.cpp index ffddcbabc4..721e5ae622 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -31,6 +31,7 @@ #include "core_constants.h" #include "core/input/input_event.h" +#include "core/multiplayer/multiplayer.h" #include "core/object/class_db.h" #include "core/os/keyboard.h" #include "core/variant/variant.h" @@ -558,6 +559,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_PATH_VALID_TYPES); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_SAVE_FILE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_OBJECTID); + BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_POINTER); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX); @@ -592,6 +594,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_DEFERRED_SET_RESOURCE); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_EDITOR_BASIC_SETTING); + BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_ARRAY); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_DEFAULT); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_DEFAULT_INTL); @@ -608,6 +611,15 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_OBJECT_CORE); BIND_CORE_ENUM_CONSTANT(METHOD_FLAGS_DEFAULT); + // rpc + BIND_CORE_ENUM_CONSTANT_CUSTOM("RPC_MODE_DISABLED", Multiplayer::RPC_MODE_DISABLED); + BIND_CORE_ENUM_CONSTANT_CUSTOM("RPC_MODE_ANY", Multiplayer::RPC_MODE_ANY); + BIND_CORE_ENUM_CONSTANT_CUSTOM("RPC_MODE_AUTH", Multiplayer::RPC_MODE_AUTHORITY); + + BIND_CORE_ENUM_CONSTANT_CUSTOM("TRANSFER_MODE_UNRELIABLE", Multiplayer::TRANSFER_MODE_UNRELIABLE); + BIND_CORE_ENUM_CONSTANT_CUSTOM("TRANSFER_MODE_ORDERED", Multiplayer::TRANSFER_MODE_ORDERED); + BIND_CORE_ENUM_CONSTANT_CUSTOM("TRANSFER_MODE_RELIABLE", Multiplayer::TRANSFER_MODE_RELIABLE); + BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_NIL", Variant::NIL); BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_BOOL", Variant::BOOL); BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_INT", Variant::INT); diff --git a/core/doc_data.cpp b/core/doc_data.cpp index 45450bf97a..4b284a30aa 100644 --- a/core/doc_data.cpp +++ b/core/doc_data.cpp @@ -31,7 +31,14 @@ #include "doc_data.h" void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const PropertyInfo &p_retinfo) { - if (p_retinfo.type == Variant::INT && p_retinfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + if (p_retinfo.type == Variant::INT && p_retinfo.hint == PROPERTY_HINT_INT_IS_POINTER) { + p_method.return_type = p_retinfo.hint_string; + if (p_method.return_type == "") { + p_method.return_type = "void*"; + } else { + p_method.return_type += "*"; + } + } else if (p_retinfo.type == Variant::INT && p_retinfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { p_method.return_enum = p_retinfo.class_name; if (p_method.return_enum.begins_with("_")) { //proxy class p_method.return_enum = p_method.return_enum.substr(1, p_method.return_enum.length()); @@ -55,7 +62,14 @@ void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const Proper void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const PropertyInfo &p_arginfo) { p_argument.name = p_arginfo.name; - if (p_arginfo.type == Variant::INT && p_arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + if (p_arginfo.type == Variant::INT && p_arginfo.hint == PROPERTY_HINT_INT_IS_POINTER) { + p_argument.type = p_arginfo.hint_string; + if (p_argument.type == "") { + p_argument.type = "void*"; + } else { + p_argument.type += "*"; + } + } else if (p_arginfo.type == Variant::INT && p_arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { p_argument.enumeration = p_arginfo.class_name; if (p_argument.enumeration.begins_with("_")) { //proxy class p_argument.enumeration = p_argument.enumeration.substr(1, p_argument.enumeration.length()); diff --git a/core/doc_data.h b/core/doc_data.h index a3011fe275..19dec71927 100644 --- a/core/doc_data.h +++ b/core/doc_data.h @@ -67,6 +67,7 @@ public: String qualifiers; String description; Vector<ArgumentDoc> arguments; + Vector<int> errors_returned; bool operator<(const MethodDoc &p_method) const { if (name == p_method.name) { // Must be a constructor since there is no overloading. diff --git a/core/error/error_list.cpp b/core/error/error_list.cpp index a8355065fe..e1e94dd65d 100644 --- a/core/error/error_list.cpp +++ b/core/error/error_list.cpp @@ -31,55 +31,55 @@ #include "error_list.h" const char *error_names[] = { - "No error", - "Generic error", - "Requested operation is unsupported/unavailable", - "The object hasn't been set up properly", - "Missing credentials for requested resource", - "Parameter out of range", - "Out of memory", - "File not found", - "Bad drive", - "Bad path", - "Permission denied", - "Already in use", - "Can't open file", - "Can't write file", - "Can't read file", - "File unrecognized", - "File corrupt", - "Missing dependencies for file", - "Unexpected eof", - "Can't open resource/socket/file", // File too? What's the difference to ERR_FILE_CANT_OPEN - "Can't create", // What can't be created, - "Query failed", // What query, - "Already in use", - "Resource is locked", - "Timeout", - "Can't connect", - "Can't resolve hostname", // I guessed it's the hostname here. - "Connection error", - "Can't acquire resource", - "Can't fork", - "Invalid data", - "Invalid parameter", - "Item already exists", - "Item does not exist", - "Can't read from database", // Comments say, it's full? Is that correct? - "Can't write to database", // Is the database always full when this is raised? - "Compilation failed", - "Method not found", - "Link failed", - "Script failed", - "Cyclic link detected", - "Invalid declaration", - "Duplicate symbol", - "Parse error", - "Resource is busy", - "Skip error", // ???? What's this? String taken from the docs - "Help error", // More specific? - "Bug", - "Printer on fire", + "OK", // OK + "Failed", // FAILED + "Unavailable", // ERR_UNAVAILABLE + "Unconfigured", // ERR_UNCONFIGURED + "Unauthorized", // ERR_UNAUTHORIZED + "Parameter out of range", // ERR_PARAMETER_RANGE_ERROR + "Out of memory", // ERR_OUT_OF_MEMORY + "File not found", // ERR_FILE_NOT_FOUND + "File: Bad drive", // ERR_FILE_BAD_DRIVE + "File: Bad path", // ERR_FILE_BAD_PATH + "File: Permission denied", // ERR_FILE_NO_PERMISSION + "File already in use", // ERR_FILE_ALREADY_IN_USE + "Can't open file", // ERR_FILE_CANT_OPEN + "Can't write file", // ERR_FILE_CANT_WRITE + "Can't read file", // ERR_FILE_CANT_READ + "File unrecognized", // ERR_FILE_UNRECOGNIZED + "File corrupt", // ERR_FILE_CORRUPT + "Missing dependencies for file", // ERR_FILE_MISSING_DEPENDENCIES + "End of file", // ERR_FILE_EOF + "Can't open", // ERR_CANT_OPEN + "Can't create", // ERR_CANT_CREATE + "Query failed", // ERR_QUERY_FAILED + "Already in use", // ERR_ALREADY_IN_USE + "Locked", // ERR_LOCKED + "Timeout", // ERR_TIMEOUT + "Can't connect", // ERR_CANT_CONNECT + "Can't resolve", // ERR_CANT_RESOLVE + "Connection error", // ERR_CONNECTION_ERROR + "Can't acquire resource", // ERR_CANT_ACQUIRE_RESOURCE + "Can't fork", // ERR_CANT_FORK + "Invalid data", // ERR_INVALID_DATA + "Invalid parameter", // ERR_INVALID_PARAMETER + "Already exists", // ERR_ALREADY_EXISTS + "Does not exist", // ERR_DOES_NOT_EXIST + "Can't read database", // ERR_DATABASE_CANT_READ + "Can't write database", // ERR_DATABASE_CANT_WRITE + "Compilation failed", // ERR_COMPILATION_FAILED + "Method not found", // ERR_METHOD_NOT_FOUND + "Link failed", // ERR_LINK_FAILED + "Script failed", // ERR_SCRIPT_FAILED + "Cyclic link detected", // ERR_CYCLIC_LINK + "Invalid declaration", // ERR_INVALID_DECLARATION + "Duplicate symbol", // ERR_DUPLICATE_SYMBOL + "Parse error", // ERR_PARSE_ERROR + "Busy", // ERR_BUSY + "Skip", // ERR_SKIP + "Help", // ERR_HELP + "Bug", // ERR_BUG + "Printer on fire", // ERR_PRINTER_ON_FIRE }; static_assert(sizeof(error_names) / sizeof(*error_names) == ERR_MAX); diff --git a/core/error/error_list.h b/core/error/error_list.h index e7c7f10265..852825dda5 100644 --- a/core/error/error_list.h +++ b/core/error/error_list.h @@ -36,6 +36,11 @@ * values can be more detailed in the future. * * This is a generic error list, mainly for organizing a language of returning errors. + * + * Errors: + * - Are added to the Error enum in core/error/error_list.h + * - Have a description added to error_names in core/error/error_list.cpp + * - Are bound with BIND_CORE_ENUM_CONSTANT() in core/core_constants.cpp */ enum Error { diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index 46dc5f284b..a8547a0090 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -39,6 +39,13 @@ #ifdef TOOLS_ENABLED static String get_type_name(const PropertyInfo &p_info) { + if (p_info.type == Variant::INT && (p_info.hint == PROPERTY_HINT_INT_IS_POINTER)) { + if (p_info.hint_string == "") { + return "void*"; + } else { + return p_info.hint_string + "*"; + } + } if (p_info.type == Variant::INT && (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM)) { return String("enum::") + String(p_info.class_name); } @@ -831,6 +838,20 @@ Dictionary NativeExtensionAPIDump::generate_extension_api() { } } + { + Array native_structures; + + { + Dictionary d; + d["name"] = "AudioFrame"; + d["format"] = "float left,float right"; + + native_structures.push_back(d); + } + + api_dump["native_structures"] = native_structures; + } + return api_dump; } diff --git a/core/extension/gdnative_interface.cpp b/core/extension/gdnative_interface.cpp index de107b4156..a65bdd16dc 100644 --- a/core/extension/gdnative_interface.cpp +++ b/core/extension/gdnative_interface.cpp @@ -661,6 +661,116 @@ static const char32_t *gdnative_string_operator_index_const(const GDNativeString return &self->ptr()[p_index]; } +/* Packed array functions */ + +static uint8_t *gdnative_packed_byte_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedByteArray *self = (PackedByteArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptrw()[p_index]; +} + +static const uint8_t *gdnative_packed_byte_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedByteArray *self = (const PackedByteArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptr()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_color_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedColorArray *self = (PackedColorArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptrw()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_color_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedColorArray *self = (const PackedColorArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptr()[p_index]; +} + +static float *gdnative_packed_float32_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedFloat32Array *self = (PackedFloat32Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptrw()[p_index]; +} + +static const float *gdnative_packed_float32_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedFloat32Array *self = (const PackedFloat32Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptr()[p_index]; +} + +static double *gdnative_packed_float64_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedFloat64Array *self = (PackedFloat64Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptrw()[p_index]; +} + +static const double *gdnative_packed_float64_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedFloat64Array *self = (const PackedFloat64Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptr()[p_index]; +} + +static int32_t *gdnative_packed_int32_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedInt32Array *self = (PackedInt32Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptrw()[p_index]; +} + +static const int32_t *gdnative_packed_int32_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedInt32Array *self = (const PackedInt32Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptr()[p_index]; +} + +static int64_t *gdnative_packed_int64_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedInt64Array *self = (PackedInt64Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptrw()[p_index]; +} + +static const int64_t *gdnative_packed_int64_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedInt64Array *self = (const PackedInt64Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptr()[p_index]; +} + +static GDNativeStringPtr gdnative_packed_string_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedStringArray *self = (PackedStringArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeStringPtr)&self->ptrw()[p_index]; +} + +static GDNativeStringPtr gdnative_packed_string_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedStringArray *self = (const PackedStringArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeStringPtr)&self->ptr()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_vector2_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedVector2Array *self = (PackedVector2Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptrw()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_vector2_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedVector2Array *self = (const PackedVector2Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptr()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_vector3_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedVector3Array *self = (PackedVector3Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptrw()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_vector3_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedVector3Array *self = (const PackedVector3Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptr()[p_index]; +} + /* OBJECT API */ static void gdnative_object_method_bind_call(const GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeVariantPtr *p_args, GDNativeInt p_arg_count, GDNativeVariantPtr r_return, GDNativeCallError *r_error) { @@ -843,6 +953,32 @@ void gdnative_setup_interface(GDNativeInterface *p_interface) { gdni.string_operator_index = gdnative_string_operator_index; gdni.string_operator_index_const = gdnative_string_operator_index_const; + /* Packed array functions */ + + gdni.packed_byte_array_operator_index = gdnative_packed_byte_array_operator_index; + gdni.packed_byte_array_operator_index_const = gdnative_packed_byte_array_operator_index_const; + + gdni.packed_color_array_operator_index = gdnative_packed_color_array_operator_index; + gdni.packed_color_array_operator_index_const = gdnative_packed_color_array_operator_index_const; + + gdni.packed_float32_array_operator_index = gdnative_packed_float32_array_operator_index; + gdni.packed_float32_array_operator_index_const = gdnative_packed_float32_array_operator_index_const; + gdni.packed_float64_array_operator_index = gdnative_packed_float64_array_operator_index; + gdni.packed_float64_array_operator_index_const = gdnative_packed_float64_array_operator_index_const; + + gdni.packed_int32_array_operator_index = gdnative_packed_int32_array_operator_index; + gdni.packed_int32_array_operator_index_const = gdnative_packed_int32_array_operator_index_const; + gdni.packed_int64_array_operator_index = gdnative_packed_int64_array_operator_index; + gdni.packed_int64_array_operator_index_const = gdnative_packed_int64_array_operator_index_const; + + gdni.packed_string_array_operator_index = gdnative_packed_string_array_operator_index; + gdni.packed_string_array_operator_index_const = gdnative_packed_string_array_operator_index_const; + + gdni.packed_vector2_array_operator_index = gdnative_packed_vector2_array_operator_index; + gdni.packed_vector2_array_operator_index_const = gdnative_packed_vector2_array_operator_index_const; + gdni.packed_vector3_array_operator_index = gdnative_packed_vector3_array_operator_index; + gdni.packed_vector3_array_operator_index_const = gdnative_packed_vector3_array_operator_index_const; + /* OBJECT */ gdni.object_method_bind_call = gdnative_object_method_bind_call; diff --git a/core/extension/gdnative_interface.h b/core/extension/gdnative_interface.h index 3a5b04429c..63f4b0917c 100644 --- a/core/extension/gdnative_interface.h +++ b/core/extension/gdnative_interface.h @@ -387,6 +387,32 @@ typedef struct { char32_t *(*string_operator_index)(GDNativeStringPtr p_self, GDNativeInt p_index); const char32_t *(*string_operator_index_const)(const GDNativeStringPtr p_self, GDNativeInt p_index); + /* Packed array functions */ + + uint8_t *(*packed_byte_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedByteArray + const uint8_t *(*packed_byte_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedByteArray + + GDNativeTypePtr (*packed_color_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedColorArray, returns Color ptr + GDNativeTypePtr (*packed_color_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedColorArray, returns Color ptr + + float *(*packed_float32_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedFloat32Array + const float *(*packed_float32_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedFloat32Array + double *(*packed_float64_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedFloat64Array + const double *(*packed_float64_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedFloat64Array + + int32_t *(*packed_int32_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedInt32Array + const int32_t *(*packed_int32_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedInt32Array + int64_t *(*packed_int64_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedInt32Array + const int64_t *(*packed_int64_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedInt32Array + + GDNativeStringPtr (*packed_string_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedStringArray + GDNativeStringPtr (*packed_string_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedStringArray + + GDNativeTypePtr (*packed_vector2_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedVector2Array, returns Vector2 ptr + GDNativeTypePtr (*packed_vector2_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedVector2Array, returns Vector2 ptr + GDNativeTypePtr (*packed_vector3_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedVector3Array, returns Vector3 ptr + GDNativeTypePtr (*packed_vector3_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedVector3Array, returns Vector3 ptr + /* OBJECT */ void (*object_method_bind_call)(const GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeVariantPtr *p_args, GDNativeInt p_arg_count, GDNativeVariantPtr r_ret, GDNativeCallError *r_error); diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp index 0556c9c84d..a3cd7ca14c 100644 --- a/core/extension/native_extension.cpp +++ b/core/extension/native_extension.cpp @@ -35,6 +35,8 @@ #include "core/object/method_bind.h" #include "core/os/os.h" +const char *NativeExtension::EXTENSION_LIST_CONFIG_FILE = "res://.godot/extension_list.cfg"; + class NativeExtensionMethodBind : public MethodBind { GDNativeExtensionClassMethodCall call_func; GDNativeExtensionClassMethodPtrCall ptrcall_func; diff --git a/core/extension/native_extension.h b/core/extension/native_extension.h index a961b21cc9..b661381d64 100644 --- a/core/extension/native_extension.h +++ b/core/extension/native_extension.h @@ -60,6 +60,8 @@ protected: static void _bind_methods(); public: + static const char *EXTENSION_LIST_CONFIG_FILE; + Error open_library(const String &p_path, const String &p_entry_symbol); void close_library(); diff --git a/core/extension/native_extension_manager.cpp b/core/extension/native_extension_manager.cpp index 7be2593845..8b7a9df4f1 100644 --- a/core/extension/native_extension_manager.cpp +++ b/core/extension/native_extension_manager.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "native_extension_manager.h" +#include "core/io/file_access.h" NativeExtensionManager::LoadStatus NativeExtensionManager::load_extension(const String &p_path) { if (native_extension_map.has(p_path)) { @@ -76,6 +77,11 @@ NativeExtensionManager::LoadStatus NativeExtensionManager::unload_extension(cons native_extension_map.erase(p_path); return LOAD_STATUS_OK; } + +bool NativeExtensionManager::is_extension_loaded(const String &p_path) const { + return native_extension_map.has(p_path); +} + Vector<String> NativeExtensionManager::get_loaded_extensions() const { Vector<String> ret; for (const Map<String, Ref<NativeExtension>>::Element *E = native_extension_map.front(); E; E = E->next()) { @@ -105,6 +111,17 @@ void NativeExtensionManager::deinitialize_extensions(NativeExtension::Initializa level = int32_t(p_level) - 1; } +void NativeExtensionManager::load_extensions() { + FileAccessRef f = FileAccess::open(NativeExtension::EXTENSION_LIST_CONFIG_FILE, FileAccess::READ); + while (f && !f->eof_reached()) { + String s = f->get_line().strip_edges(); + if (s != String()) { + LoadStatus err = load_extension(s); + ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, "Error loading extension: " + s); + } + } +} + NativeExtensionManager *NativeExtensionManager::get_singleton() { return singleton; } @@ -112,6 +129,8 @@ void NativeExtensionManager::_bind_methods() { ClassDB::bind_method(D_METHOD("load_extension", "path"), &NativeExtensionManager::load_extension); ClassDB::bind_method(D_METHOD("reload_extension", "path"), &NativeExtensionManager::reload_extension); ClassDB::bind_method(D_METHOD("unload_extension", "path"), &NativeExtensionManager::unload_extension); + ClassDB::bind_method(D_METHOD("is_extension_loaded", "path"), &NativeExtensionManager::is_extension_loaded); + ClassDB::bind_method(D_METHOD("get_loaded_extensions"), &NativeExtensionManager::get_loaded_extensions); ClassDB::bind_method(D_METHOD("get_extension", "path"), &NativeExtensionManager::get_extension); diff --git a/core/extension/native_extension_manager.h b/core/extension/native_extension_manager.h index 78465bd5cf..89ccd155fe 100644 --- a/core/extension/native_extension_manager.h +++ b/core/extension/native_extension_manager.h @@ -55,6 +55,7 @@ public: LoadStatus load_extension(const String &p_path); LoadStatus reload_extension(const String &p_path); LoadStatus unload_extension(const String &p_path); + bool is_extension_loaded(const String &p_path) const; Vector<String> get_loaded_extensions() const; Ref<NativeExtension> get_extension(const String &p_path); @@ -63,6 +64,8 @@ public: static NativeExtensionManager *get_singleton(); + void load_extensions(); + NativeExtensionManager(); }; diff --git a/core/input/input.cpp b/core/input/input.cpp index 72563cc40a..9195f7d8b5 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -240,18 +240,12 @@ bool Input::is_joy_button_pressed(int p_device, JoyButton p_button) const { } bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const { -#ifdef DEBUG_ENABLED - bool has_action = InputMap::get_singleton()->has_action(p_action); - ERR_FAIL_COND_V_MSG(!has_action, false, "Request for nonexistent InputMap action '" + String(p_action) + "'."); -#endif + ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); return action_state.has(p_action) && action_state[p_action].pressed && (p_exact ? action_state[p_action].exact : true); } bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) const { -#ifdef DEBUG_ENABLED - bool has_action = InputMap::get_singleton()->has_action(p_action); - ERR_FAIL_COND_V_MSG(!has_action, false, "Request for nonexistent InputMap action '" + String(p_action) + "'."); -#endif + ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); const Map<StringName, Action>::Element *E = action_state.find(p_action); if (!E) { return false; @@ -269,10 +263,7 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con } bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const { -#ifdef DEBUG_ENABLED - bool has_action = InputMap::get_singleton()->has_action(p_action); - ERR_FAIL_COND_V_MSG(!has_action, false, "Request for nonexistent InputMap action '" + String(p_action) + "'."); -#endif + ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); const Map<StringName, Action>::Element *E = action_state.find(p_action); if (!E) { return false; @@ -290,10 +281,7 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co } float Input::get_action_strength(const StringName &p_action, bool p_exact) const { -#ifdef DEBUG_ENABLED - bool has_action = InputMap::get_singleton()->has_action(p_action); - ERR_FAIL_COND_V_MSG(!has_action, false, "Request for nonexistent InputMap action '" + String(p_action) + "'."); -#endif + ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action)); const Map<StringName, Action>::Element *E = action_state.find(p_action); if (!E) { return 0.0f; diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 325cdf2127..50b2099236 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -31,8 +31,8 @@ #include "input_event.h" #include "core/input/input_map.h" +#include "core/input/shortcut.h" #include "core/os/keyboard.h" -#include "scene/gui/shortcut.h" const int InputEvent::DEVICE_ID_TOUCH_MOUSE = -1; const int InputEvent::DEVICE_ID_INTERNAL = -2; @@ -1545,6 +1545,13 @@ Ref<Shortcut> InputEventShortcut::get_shortcut() { return shortcut; } +void InputEventShortcut::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_shortcut", "shortcut"), &InputEventShortcut::set_shortcut); + ClassDB::bind_method(D_METHOD("get_shortcut"), &InputEventShortcut::get_shortcut); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut"); +} + bool InputEventShortcut::is_pressed() const { return true; } diff --git a/core/input/input_event.h b/core/input/input_event.h index 517d63eb40..3fc8078a09 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -548,6 +548,9 @@ class InputEventShortcut : public InputEvent { Ref<Shortcut> shortcut; +protected: + static void _bind_methods(); + public: void set_shortcut(Ref<Shortcut> p_shortcut); Ref<Shortcut> get_shortcut(); diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 83ec70757e..fe4ee99204 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -59,7 +59,7 @@ void InputMap::_bind_methods() { * Returns an nonexistent action error message with a suggestion of the closest * matching action name (if possible). */ -String InputMap::_suggest_actions(const StringName &p_action) const { +String InputMap::suggest_actions(const StringName &p_action) const { List<StringName> actions = get_actions(); StringName closest_action; float closest_similarity = 0.0; @@ -93,7 +93,7 @@ void InputMap::add_action(const StringName &p_action, float p_deadzone) { } void InputMap::erase_action(const StringName &p_action) { - ERR_FAIL_COND_MSG(!input_map.has(p_action), _suggest_actions(p_action)); + ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action)); input_map.erase(p_action); } @@ -147,20 +147,20 @@ bool InputMap::has_action(const StringName &p_action) const { } float InputMap::action_get_deadzone(const StringName &p_action) { - ERR_FAIL_COND_V_MSG(!input_map.has(p_action), 0.0f, _suggest_actions(p_action)); + ERR_FAIL_COND_V_MSG(!input_map.has(p_action), 0.0f, suggest_actions(p_action)); return input_map[p_action].deadzone; } void InputMap::action_set_deadzone(const StringName &p_action, float p_deadzone) { - ERR_FAIL_COND_MSG(!input_map.has(p_action), _suggest_actions(p_action)); + ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action)); input_map[p_action].deadzone = p_deadzone; } void InputMap::action_add_event(const StringName &p_action, const Ref<InputEvent> &p_event) { ERR_FAIL_COND_MSG(p_event.is_null(), "It's not a reference to a valid InputEvent object."); - ERR_FAIL_COND_MSG(!input_map.has(p_action), _suggest_actions(p_action)); + ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action)); if (_find_event(input_map[p_action], p_event, true)) { return; // Already added. } @@ -169,12 +169,12 @@ void InputMap::action_add_event(const StringName &p_action, const Ref<InputEvent } bool InputMap::action_has_event(const StringName &p_action, const Ref<InputEvent> &p_event) { - ERR_FAIL_COND_V_MSG(!input_map.has(p_action), false, _suggest_actions(p_action)); + ERR_FAIL_COND_V_MSG(!input_map.has(p_action), false, suggest_actions(p_action)); return (_find_event(input_map[p_action], p_event, true) != nullptr); } void InputMap::action_erase_event(const StringName &p_action, const Ref<InputEvent> &p_event) { - ERR_FAIL_COND_MSG(!input_map.has(p_action), _suggest_actions(p_action)); + ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action)); List<Ref<InputEvent>>::Element *E = _find_event(input_map[p_action], p_event, true); if (E) { @@ -186,7 +186,7 @@ void InputMap::action_erase_event(const StringName &p_action, const Ref<InputEve } void InputMap::action_erase_events(const StringName &p_action) { - ERR_FAIL_COND_MSG(!input_map.has(p_action), _suggest_actions(p_action)); + ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action)); input_map[p_action].inputs.clear(); } @@ -218,7 +218,7 @@ bool InputMap::event_is_action(const Ref<InputEvent> &p_event, const StringName bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match, bool *p_pressed, float *p_strength, float *p_raw_strength) const { OrderedHashMap<StringName, Action>::Element E = input_map.find(p_action); - ERR_FAIL_COND_V_MSG(!E, false, _suggest_actions(p_action)); + ERR_FAIL_COND_V_MSG(!E, false, suggest_actions(p_action)); Ref<InputEventAction> input_event_action = p_event; if (input_event_action.is_valid()) { @@ -317,36 +317,36 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = { { "ui_text_dedent", TTRC("Dedent") }, { "ui_text_backspace", TTRC("Backspace") }, { "ui_text_backspace_word", TTRC("Backspace Word") }, - { "ui_text_backspace_word.OSX", TTRC("Backspace Word") }, + { "ui_text_backspace_word.osx", TTRC("Backspace Word") }, { "ui_text_backspace_all_to_left", TTRC("Backspace all to Left") }, - { "ui_text_backspace_all_to_left.OSX", TTRC("Backspace all to Left") }, + { "ui_text_backspace_all_to_left.osx", TTRC("Backspace all to Left") }, { "ui_text_delete", TTRC("Delete") }, { "ui_text_delete_word", TTRC("Delete Word") }, - { "ui_text_delete_word.OSX", TTRC("Delete Word") }, + { "ui_text_delete_word.osx", TTRC("Delete Word") }, { "ui_text_delete_all_to_right", TTRC("Delete all to Right") }, - { "ui_text_delete_all_to_right.OSX", TTRC("Delete all to Right") }, + { "ui_text_delete_all_to_right.osx", TTRC("Delete all to Right") }, { "ui_text_caret_left", TTRC("Caret Left") }, { "ui_text_caret_word_left", TTRC("Caret Word Left") }, - { "ui_text_caret_word_left.OSX", TTRC("Caret Word Left") }, + { "ui_text_caret_word_left.osx", TTRC("Caret Word Left") }, { "ui_text_caret_right", TTRC("Caret Right") }, { "ui_text_caret_word_right", TTRC("Caret Word Right") }, - { "ui_text_caret_word_right.OSX", TTRC("Caret Word Right") }, + { "ui_text_caret_word_right.osx", TTRC("Caret Word Right") }, { "ui_text_caret_up", TTRC("Caret Up") }, { "ui_text_caret_down", TTRC("Caret Down") }, { "ui_text_caret_line_start", TTRC("Caret Line Start") }, - { "ui_text_caret_line_start.OSX", TTRC("Caret Line Start") }, + { "ui_text_caret_line_start.osx", TTRC("Caret Line Start") }, { "ui_text_caret_line_end", TTRC("Caret Line End") }, - { "ui_text_caret_line_end.OSX", TTRC("Caret Line End") }, + { "ui_text_caret_line_end.osx", TTRC("Caret Line End") }, { "ui_text_caret_page_up", TTRC("Caret Page Up") }, { "ui_text_caret_page_down", TTRC("Caret Page Down") }, { "ui_text_caret_document_start", TTRC("Caret Document Start") }, - { "ui_text_caret_document_start.OSX", TTRC("Caret Document Start") }, + { "ui_text_caret_document_start.osx", TTRC("Caret Document Start") }, { "ui_text_caret_document_end", TTRC("Caret Document End") }, - { "ui_text_caret_document_end.OSX", TTRC("Caret Document End") }, + { "ui_text_caret_document_end.osx", TTRC("Caret Document End") }, { "ui_text_scroll_up", TTRC("Scroll Up") }, - { "ui_text_scroll_up.OSX", TTRC("Scroll Up") }, + { "ui_text_scroll_up.osx", TTRC("Scroll Up") }, { "ui_text_scroll_down", TTRC("Scroll Down") }, - { "ui_text_scroll_down.OSX", TTRC("Scroll Down") }, + { "ui_text_scroll_down.osx", TTRC("Scroll Down") }, { "ui_text_select_all", TTRC("Select All") }, { "ui_text_select_word_under_caret", TTRC("Select Word Under Caret") }, { "ui_text_toggle_insert_mode", TTRC("Toggle Insert Mode") }, @@ -516,14 +516,14 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_BACKSPACE | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_backspace_word.OSX", inputs); + default_builtin_cache.insert("ui_text_backspace_word.osx", inputs); inputs = List<Ref<InputEvent>>(); default_builtin_cache.insert("ui_text_backspace_all_to_left", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_BACKSPACE | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_backspace_all_to_left.OSX", inputs); + default_builtin_cache.insert("ui_text_backspace_all_to_left.osx", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DELETE)); @@ -535,14 +535,14 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DELETE | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_delete_word.OSX", inputs); + default_builtin_cache.insert("ui_text_delete_word.osx", inputs); inputs = List<Ref<InputEvent>>(); default_builtin_cache.insert("ui_text_delete_all_to_right", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DELETE | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_delete_all_to_right.OSX", inputs); + default_builtin_cache.insert("ui_text_delete_all_to_right.osx", inputs); // Text Caret Movement Left/Right @@ -556,7 +556,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_LEFT | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_caret_word_left.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_word_left.osx", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_RIGHT)); @@ -568,7 +568,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_RIGHT | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_caret_word_right.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_word_right.osx", inputs); // Text Caret Movement Up/Down @@ -589,7 +589,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_A | KEY_MASK_CTRL)); inputs.push_back(InputEventKey::create_reference(KEY_LEFT | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_caret_line_start.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_line_start.osx", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_END)); @@ -598,7 +598,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_E | KEY_MASK_CTRL)); inputs.push_back(InputEventKey::create_reference(KEY_RIGHT | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_caret_line_end.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_line_end.osx", inputs); // Text Caret Movement Page Up/Down @@ -618,7 +618,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_UP | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_caret_document_start.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_document_start.osx", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_END | KEY_MASK_CMD)); @@ -626,7 +626,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DOWN | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_caret_document_end.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_document_end.osx", inputs); // Text Scrolling @@ -636,7 +636,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_UP | KEY_MASK_CMD | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_scroll_up.OSX", inputs); + default_builtin_cache.insert("ui_text_scroll_up.osx", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DOWN | KEY_MASK_CMD)); @@ -644,7 +644,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DOWN | KEY_MASK_CMD | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_scroll_down.OSX", inputs); + default_builtin_cache.insert("ui_text_scroll_down.osx", inputs); // Text Misc @@ -702,11 +702,11 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { void InputMap::load_default() { OrderedHashMap<String, List<Ref<InputEvent>>> builtins = get_builtins(); - // List of Builtins which have an override for OSX. + // List of Builtins which have an override for macOS. Vector<String> osx_builtins; for (OrderedHashMap<String, List<Ref<InputEvent>>>::Element E = builtins.front(); E; E = E.next()) { - if (String(E.key()).ends_with(".OSX")) { - // Strip .OSX from name: some_input_name.OSX -> some_input_name + if (String(E.key()).ends_with(".osx")) { + // Strip .osx from name: some_input_name.osx -> some_input_name osx_builtins.push_back(String(E.key()).split(".")[0]); } } @@ -717,13 +717,13 @@ void InputMap::load_default() { String override_for = fullname.split(".").size() > 1 ? fullname.split(".")[1] : ""; #ifdef APPLE_STYLE_KEYS - if (osx_builtins.has(name) && override_for != "OSX") { - // Name has osx builtin but this particular one is for non-osx systems - so skip. + if (osx_builtins.has(name) && override_for != "osx") { + // Name has `osx` builtin but this particular one is for non-macOS systems - so skip. continue; } #else - if (override_for == "OSX") { - // Override for OSX - not needed on non-osx platforms. + if (override_for == "osx") { + // Override for macOS - not needed on non-macOS platforms. continue; } #endif diff --git a/core/input/input_map.h b/core/input/input_map.h index 0e0567464a..a2d3952f94 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -61,7 +61,6 @@ private: Array _action_get_events(const StringName &p_action); Array _get_actions(); - String _suggest_actions(const StringName &p_action) const; protected: static void _bind_methods(); @@ -89,6 +88,8 @@ public: void load_from_project_settings(); void load_default(); + String suggest_actions(const StringName &p_action) const; + String get_builtin_display_name(const String &p_name) const; // Use an Ordered Map so insertion order is preserved. We want the elements to be 'grouped' somewhat. const OrderedHashMap<String, List<Ref<InputEvent>>> &get_builtins(); diff --git a/scene/gui/shortcut.cpp b/core/input/shortcut.cpp index d0cb08724e..d0cb08724e 100644 --- a/scene/gui/shortcut.cpp +++ b/core/input/shortcut.cpp diff --git a/scene/gui/shortcut.h b/core/input/shortcut.h index 249dd1971f..249dd1971f 100644 --- a/scene/gui/shortcut.h +++ b/core/input/shortcut.h diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp index aeaf25f321..49fa73dab2 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -188,7 +188,7 @@ Error ConfigFile::_internal_save(FileAccess *file) { for (OrderedHashMap<String, Variant>::Element F = E.get().front(); F; F = F.next()) { String vstr; VariantWriter::write_to_string(F.get(), vstr); - file->store_string(F.key() + "=" + vstr + "\n"); + file->store_string(F.key().property_name_encode() + "=" + vstr + "\n"); } } @@ -315,6 +315,8 @@ void ConfigFile::_bind_methods() { ClassDB::bind_method(D_METHOD("parse", "data"), &ConfigFile::parse); ClassDB::bind_method(D_METHOD("save", "path"), &ConfigFile::save); + BIND_METHOD_ERR_RETURN_DOC("load", ERR_FILE_CANT_OPEN); + ClassDB::bind_method(D_METHOD("load_encrypted", "path", "key"), &ConfigFile::load_encrypted); ClassDB::bind_method(D_METHOD("load_encrypted_pass", "path", "password"), &ConfigFile::load_encrypted_pass); diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 8234adea06..3bff0a3fd5 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -135,7 +135,7 @@ Error DirAccess::make_dir_recursive(String p_dir) { String full_dir; - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { //append current full_dir = get_current_dir().plus_file(p_dir); @@ -345,7 +345,7 @@ Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flag dirs.push_back(n); } else { const String &rel_path = n; - if (!n.is_rel_path()) { + if (!n.is_relative_path()) { list_dir_end(); return ERR_BUG; } diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp index f291086808..b3d35b3603 100644 --- a/core/io/http_client_tcp.cpp +++ b/core/io/http_client_tcp.cpp @@ -45,6 +45,8 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss conn_port = p_port; conn_host = p_host; + ip_candidates.clear(); + ssl = p_ssl; ssl_verify_host = p_verify_host; @@ -234,6 +236,7 @@ void HTTPClientTCP::close() { resolving = IP::RESOLVER_INVALID_ID; } + ip_candidates.clear(); response_headers.clear(); response_str.clear(); body_size = -1; @@ -256,10 +259,17 @@ Error HTTPClientTCP::poll() { return OK; // Still resolving case IP::RESOLVER_STATUS_DONE: { - IPAddress host = IP::get_singleton()->get_resolve_item_address(resolving); - Error err = tcp_connection->connect_to_host(host, conn_port); + ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolving); IP::get_singleton()->erase_resolve_item(resolving); resolving = IP::RESOLVER_INVALID_ID; + + Error err = ERR_BUG; // Should be at least one entry. + while (ip_candidates.size() > 0) { + err = tcp_connection->connect_to_host(ip_candidates.front(), conn_port); + if (err == OK) { + break; + } + } if (err) { status = STATUS_CANT_CONNECT; return err; @@ -313,6 +323,7 @@ Error HTTPClientTCP::poll() { if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { // Handshake has been successful handshaking = false; + ip_candidates.clear(); status = STATUS_CONNECTED; return OK; } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { @@ -323,15 +334,24 @@ Error HTTPClientTCP::poll() { } // ... we will need to poll more for handshake to finish } else { + ip_candidates.clear(); status = STATUS_CONNECTED; } return OK; } break; case StreamPeerTCP::STATUS_ERROR: case StreamPeerTCP::STATUS_NONE: { + Error err = ERR_CANT_CONNECT; + while (ip_candidates.size() > 0) { + tcp_connection->disconnect_from_host(); + err = tcp_connection->connect_to_host(ip_candidates.pop_front(), conn_port); + if (err == OK) { + return OK; + } + } close(); status = STATUS_CANT_CONNECT; - return ERR_CANT_CONNECT; + return err; } break; } } break; diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h index e178399fbe..170afb551c 100644 --- a/core/io/http_client_tcp.h +++ b/core/io/http_client_tcp.h @@ -37,6 +37,7 @@ class HTTPClientTCP : public HTTPClient { private: Status status = STATUS_DISCONNECTED; IP::ResolverID resolving = IP::RESOLVER_INVALID_ID; + Array ip_candidates; int conn_port = -1; String conn_host; bool ssl = false; diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp deleted file mode 100644 index 0ce9a70921..0000000000 --- a/core/io/multiplayer_api.cpp +++ /dev/null @@ -1,1148 +0,0 @@ -/*************************************************************************/ -/* multiplayer_api.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 "multiplayer_api.h" - -#include "core/debugger/engine_debugger.h" -#include "core/io/marshalls.h" -#include "core/io/multiplayer_replicator.h" -#include "scene/main/node.h" - -#include <stdint.h> - -#ifdef DEBUG_ENABLED -#include "core/os/os.h" -#endif - -String _get_rpc_md5(const Node *p_node) { - String rpc_list; - const Vector<MultiplayerAPI::RPCConfig> node_config = p_node->get_node_rpc_methods(); - for (int i = 0; i < node_config.size(); i++) { - rpc_list += String(node_config[i].name); - } - if (p_node->get_script_instance()) { - const Vector<MultiplayerAPI::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); - for (int i = 0; i < script_config.size(); i++) { - rpc_list += String(script_config[i].name); - } - } - return rpc_list.md5_text(); -} - -const MultiplayerAPI::RPCConfig _get_rpc_config(const Node *p_node, const StringName &p_method, uint16_t &r_id) { - const Vector<MultiplayerAPI::RPCConfig> node_config = p_node->get_node_rpc_methods(); - for (int i = 0; i < node_config.size(); i++) { - if (node_config[i].name == p_method) { - r_id = ((uint16_t)i) | (1 << 15); - return node_config[i]; - } - } - if (p_node->get_script_instance()) { - const Vector<MultiplayerAPI::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); - for (int i = 0; i < script_config.size(); i++) { - if (script_config[i].name == p_method) { - r_id = (uint16_t)i; - return script_config[i]; - } - } - } - return MultiplayerAPI::RPCConfig(); -} - -const MultiplayerAPI::RPCConfig _get_rpc_config_by_id(Node *p_node, uint16_t p_id) { - Vector<MultiplayerAPI::RPCConfig> config; - uint16_t id = p_id; - if (id & (1 << 15)) { - id = id & ~(1 << 15); - config = p_node->get_node_rpc_methods(); - } else if (p_node->get_script_instance()) { - config = p_node->get_script_instance()->get_rpc_methods(); - } - if (id < config.size()) { - return config[id]; - } - return MultiplayerAPI::RPCConfig(); -} - -_FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, int p_remote_id) { - switch (mode) { - case MultiplayerAPI::RPC_MODE_DISABLED: { - return false; - } break; - case MultiplayerAPI::RPC_MODE_REMOTE: { - return true; - } break; - case MultiplayerAPI::RPC_MODE_MASTER: { - return p_node->is_network_master(); - } break; - case MultiplayerAPI::RPC_MODE_PUPPET: { - return !p_node->is_network_master() && p_remote_id == p_node->get_network_master(); - } break; - } - - return false; -} - -void MultiplayerAPI::poll() { - if (!network_peer.is_valid() || network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) { - return; - } - - network_peer->poll(); - - if (!network_peer.is_valid()) { // It's possible that polling might have resulted in a disconnection, so check here. - return; - } - - while (network_peer->get_available_packet_count()) { - int sender = network_peer->get_packet_peer(); - const uint8_t *packet; - int len; - - Error err = network_peer->get_packet(&packet, len); - if (err != OK) { - ERR_PRINT("Error getting packet!"); - break; // Something is wrong! - } - - rpc_sender_id = sender; - _process_packet(sender, packet, len); - rpc_sender_id = 0; - - if (!network_peer.is_valid()) { - break; // It's also possible that a packet or RPC caused a disconnection, so also check here. - } - } -} - -void MultiplayerAPI::clear() { - replicator->clear(); - connected_peers.clear(); - path_get_cache.clear(); - path_send_cache.clear(); - packet_cache.clear(); - last_send_cache_id = 1; -} - -void MultiplayerAPI::set_root_node(Node *p_node) { - root_node = p_node; -} - -Node *MultiplayerAPI::get_root_node() { - return root_node; -} - -void MultiplayerAPI::set_network_peer(const Ref<MultiplayerPeer> &p_peer) { - if (p_peer == network_peer) { - return; // Nothing to do - } - - ERR_FAIL_COND_MSG(p_peer.is_valid() && p_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, - "Supplied MultiplayerPeer must be connecting or connected."); - - if (network_peer.is_valid()) { - network_peer->disconnect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer)); - network_peer->disconnect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer)); - network_peer->disconnect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server)); - network_peer->disconnect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); - network_peer->disconnect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); - clear(); - } - - network_peer = p_peer; - - if (network_peer.is_valid()) { - network_peer->connect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer)); - network_peer->connect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer)); - network_peer->connect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server)); - network_peer->connect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); - network_peer->connect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); - } -} - -Ref<MultiplayerPeer> MultiplayerAPI::get_network_peer() const { - return network_peer; -} - -#ifdef DEBUG_ENABLED -void _profile_node_data(const String &p_what, ObjectID p_id) { - if (EngineDebugger::is_profiling("multiplayer")) { - Array values; - values.push_back("node"); - values.push_back(p_id); - values.push_back(p_what); - EngineDebugger::profiler_add_frame_data("multiplayer", values); - } -} - -void _profile_bandwidth_data(const String &p_inout, int p_size) { - if (EngineDebugger::is_profiling("multiplayer")) { - Array values; - values.push_back("bandwidth"); - values.push_back(p_inout); - values.push_back(OS::get_singleton()->get_ticks_msec()); - values.push_back(p_size); - EngineDebugger::profiler_add_frame_data("multiplayer", values); - } -} -#endif - -// Returns the packet size stripping the node path added when the node is not yet cached. -int get_packet_len(uint32_t p_node_target, int p_packet_len) { - if (p_node_target & 0x80000000) { - int ofs = p_node_target & 0x7FFFFFFF; - return p_packet_len - (p_packet_len - ofs); - } else { - return p_packet_len; - } -} - -void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(root_node == nullptr, "Multiplayer root node was not initialized. If you are using custom multiplayer, remember to set the root node via MultiplayerAPI.set_root_node before using it."); - ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small."); - -#ifdef DEBUG_ENABLED - _profile_bandwidth_data("in", p_packet_len); -#endif - - // Extract the `packet_type` from the LSB three bits: - uint8_t packet_type = p_packet[0] & 7; - - switch (packet_type) { - case NETWORK_COMMAND_SIMPLIFY_PATH: { - _process_simplify_path(p_from, p_packet, p_packet_len); - } break; - - case NETWORK_COMMAND_CONFIRM_PATH: { - _process_confirm_path(p_from, p_packet, p_packet_len); - } break; - - case NETWORK_COMMAND_REMOTE_CALL: { - // Extract packet meta - int packet_min_size = 1; - int name_id_offset = 1; - ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); - // Compute the meta size, which depends on the compression level. - int node_id_compression = (p_packet[0] & 24) >> NODE_ID_COMPRESSION_SHIFT; - int name_id_compression = (p_packet[0] & 32) >> NAME_ID_COMPRESSION_SHIFT; - - switch (node_id_compression) { - case NETWORK_NODE_ID_COMPRESSION_8: - packet_min_size += 1; - name_id_offset += 1; - break; - case NETWORK_NODE_ID_COMPRESSION_16: - packet_min_size += 2; - name_id_offset += 2; - break; - case NETWORK_NODE_ID_COMPRESSION_32: - packet_min_size += 4; - name_id_offset += 4; - break; - default: - ERR_FAIL_MSG("Was not possible to extract the node id compression mode."); - } - switch (name_id_compression) { - case NETWORK_NAME_ID_COMPRESSION_8: - packet_min_size += 1; - break; - case NETWORK_NAME_ID_COMPRESSION_16: - packet_min_size += 2; - break; - default: - ERR_FAIL_MSG("Was not possible to extract the name id compression mode."); - } - ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); - - uint32_t node_target = 0; - switch (node_id_compression) { - case NETWORK_NODE_ID_COMPRESSION_8: - node_target = p_packet[1]; - break; - case NETWORK_NODE_ID_COMPRESSION_16: - node_target = decode_uint16(p_packet + 1); - break; - case NETWORK_NODE_ID_COMPRESSION_32: - node_target = decode_uint32(p_packet + 1); - break; - default: - // Unreachable, checked before. - CRASH_NOW(); - } - - Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len); - ERR_FAIL_COND_MSG(node == nullptr, "Invalid packet received. Requested node was not found."); - - uint16_t name_id = 0; - switch (name_id_compression) { - case NETWORK_NAME_ID_COMPRESSION_8: - name_id = p_packet[name_id_offset]; - break; - case NETWORK_NAME_ID_COMPRESSION_16: - name_id = decode_uint16(p_packet + name_id_offset); - break; - default: - // Unreachable, checked before. - CRASH_NOW(); - } - - const int packet_len = get_packet_len(node_target, p_packet_len); - _process_rpc(node, name_id, p_from, p_packet, packet_len, packet_min_size); - } break; - - case NETWORK_COMMAND_RAW: { - _process_raw(p_from, p_packet, p_packet_len); - } break; - case NETWORK_COMMAND_SPAWN: { - replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true); - } break; - case NETWORK_COMMAND_DESPAWN: { - replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false); - } break; - } -} - -Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) { - Node *node = nullptr; - - if (p_node_target & 0x80000000) { - // Use full path (not cached yet). - int ofs = p_node_target & 0x7FFFFFFF; - - ERR_FAIL_COND_V_MSG(ofs >= p_packet_len, nullptr, "Invalid packet received. Size smaller than declared."); - - String paths; - paths.parse_utf8((const char *)&p_packet[ofs], p_packet_len - ofs); - - NodePath np = paths; - - node = root_node->get_node(np); - - if (!node) { - ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); - } - return node; - } else { - // Use cached path. - return get_cached_node(p_from, p_node_target); - } -} - -void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) { - ERR_FAIL_COND_MSG(p_offset > p_packet_len, "Invalid packet received. Size too small."); - - // Check that remote can call the RPC on this node. - const RPCConfig config = _get_rpc_config_by_id(p_node, p_rpc_method_id); - ERR_FAIL_COND(config.name == StringName()); - - bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from); - ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", master is " + itos(p_node->get_network_master()) + "."); - - int argc = 0; - bool byte_only = false; - - const bool byte_only_or_no_args = ((p_packet[0] & 64) >> BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; - if (byte_only_or_no_args) { - if (p_offset < p_packet_len) { - // This packet contains only bytes. - argc = 1; - byte_only = true; - } else { - // This rpc calls a method without parameters. - } - } else { - // Normal variant, takes the argument count from the packet. - ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); - argc = p_packet[p_offset]; - p_offset += 1; - } - - Vector<Variant> args; - Vector<const Variant *> argp; - args.resize(argc); - argp.resize(argc); - -#ifdef DEBUG_ENABLED - _profile_node_data("in_rpc", p_node->get_instance_id()); -#endif - - if (byte_only) { - Vector<uint8_t> pure_data; - const int len = p_packet_len - p_offset; - pure_data.resize(len); - memcpy(pure_data.ptrw(), &p_packet[p_offset], len); - args.write[0] = pure_data; - argp.write[0] = &args[0]; - p_offset += len; - } else { - for (int i = 0; i < argc; i++) { - ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); - - int vlen; - Error err = decode_and_decompress_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen); - ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RPC argument."); - - argp.write[i] = &args[i]; - p_offset += vlen; - } - } - - Callable::CallError ce; - - p_node->call(config.name, (const Variant **)argp.ptr(), argc, ce); - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, config.name, (const Variant **)argp.ptr(), argc, ce); - error = "RPC - " + error; - ERR_PRINT(error); - } -} - -void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small."); - int ofs = 1; - - String methods_md5; - methods_md5.parse_utf8((const char *)(p_packet + ofs), 32); - ofs += 33; - - int id = decode_uint32(&p_packet[ofs]); - ofs += 4; - - String paths; - paths.parse_utf8((const char *)(p_packet + ofs), p_packet_len - ofs); - - NodePath path = paths; - - if (!path_get_cache.has(p_from)) { - path_get_cache[p_from] = PathGetCache(); - } - - Node *node = root_node->get_node(path); - ERR_FAIL_COND(node == nullptr); - const bool valid_rpc_checksum = _get_rpc_md5(node) == methods_md5; - if (valid_rpc_checksum == false) { - ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); - } - - PathGetCache::NodeInfo ni; - ni.path = path; - - path_get_cache[p_from].nodes[id] = ni; - - // Encode path to send ack. - CharString pname = String(path).utf8(); - int len = encode_cstring(pname.get_data(), nullptr); - - Vector<uint8_t> packet; - - packet.resize(1 + 1 + len); - packet.write[0] = NETWORK_COMMAND_CONFIRM_PATH; - packet.write[1] = valid_rpc_checksum; - encode_cstring(pname.get_data(), &packet.write[2]); - - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - network_peer->set_target_peer(p_from); - network_peer->put_packet(packet.ptr(), packet.size()); -} - -void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < 3, "Invalid packet received. Size too small."); - - const bool valid_rpc_checksum = p_packet[1]; - - String paths; - paths.parse_utf8((const char *)&p_packet[2], p_packet_len - 2); - - NodePath path = paths; - - if (valid_rpc_checksum == false) { - ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); - } - - PathSentCache *psc = path_send_cache.getptr(path); - ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); - - Map<int, bool>::Element *E = psc->confirmed_peers.find(p_from); - ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path."); - E->get() = true; -} - -bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) { - bool has_all_peers = true; - List<int> peers_to_add; // If one is missing, take note to add it. - - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - if (p_target < 0 && E->get() == -p_target) { - continue; // Continue, excluded. - } - - if (p_target > 0 && E->get() != p_target) { - continue; // Continue, not for this peer. - } - - Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); - - if (!F || !F->get()) { - // Path was not cached, or was cached but is unconfirmed. - if (!F) { - // Not cached at all, take note. - peers_to_add.push_back(E->get()); - } - - has_all_peers = false; - } - } - - if (peers_to_add.size() > 0) { - // Those that need to be added, send a message for this. - - // Encode function name. - const CharString path = String(p_path).utf8(); - const int path_len = encode_cstring(path.get_data(), nullptr); - - // Extract MD5 from rpc methods list. - const String methods_md5 = _get_rpc_md5(p_node); - const int methods_md5_len = 33; // 32 + 1 for the `0` that is added by the encoder. - - Vector<uint8_t> packet; - packet.resize(1 + 4 + path_len + methods_md5_len); - int ofs = 0; - - packet.write[ofs] = NETWORK_COMMAND_SIMPLIFY_PATH; - ofs += 1; - - ofs += encode_cstring(methods_md5.utf8().get_data(), &packet.write[ofs]); - - ofs += encode_uint32(psc->id, &packet.write[ofs]); - - ofs += encode_cstring(path.get_data(), &packet.write[ofs]); - - for (int &E : peers_to_add) { - network_peer->set_target_peer(E); // To all of you. - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - network_peer->put_packet(packet.ptr(), packet.size()); - - psc->confirmed_peers.insert(E, false); // Insert into confirmed, but as false since it was not confirmed. - } - } - - return has_all_peers; -} - -// The variant is compressed and encoded; The first byte contains all the meta -// information and the format is: -// - The first LSB 5 bits are used for the variant type. -// - The next two bits are used to store the encoding mode. -// - The most significant is used to store the boolean value. -#define VARIANT_META_TYPE_MASK 0x1F -#define VARIANT_META_EMODE_MASK 0x60 -#define VARIANT_META_BOOL_MASK 0x80 -#define ENCODE_8 0 << 5 -#define ENCODE_16 1 << 5 -#define ENCODE_32 2 << 5 -#define ENCODE_64 3 << 5 -Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len) { - // Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31 - CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK); - - uint8_t *buf = r_buffer; - r_len = 0; - uint8_t encode_mode = 0; - - switch (p_variant.get_type()) { - case Variant::BOOL: { - if (buf) { - // We still have 1 free bit in the meta, so let's use it. - buf[0] = (p_variant.operator bool()) ? (1 << 7) : 0; - buf[0] |= encode_mode | p_variant.get_type(); - } - r_len += 1; - } break; - case Variant::INT: { - if (buf) { - // Reserve the first byte for the meta. - buf += 1; - } - r_len += 1; - int64_t val = p_variant; - if (val <= (int64_t)INT8_MAX && val >= (int64_t)INT8_MIN) { - // Use 8 bit - encode_mode = ENCODE_8; - if (buf) { - buf[0] = val; - } - r_len += 1; - } else if (val <= (int64_t)INT16_MAX && val >= (int64_t)INT16_MIN) { - // Use 16 bit - encode_mode = ENCODE_16; - if (buf) { - encode_uint16(val, buf); - } - r_len += 2; - } else if (val <= (int64_t)INT32_MAX && val >= (int64_t)INT32_MIN) { - // Use 32 bit - encode_mode = ENCODE_32; - if (buf) { - encode_uint32(val, buf); - } - r_len += 4; - } else { - // Use 64 bit - encode_mode = ENCODE_64; - if (buf) { - encode_uint64(val, buf); - } - r_len += 8; - } - // Store the meta - if (buf) { - buf -= 1; - buf[0] = encode_mode | p_variant.get_type(); - } - } break; - default: - // Any other case is not yet compressed. - Error err = encode_variant(p_variant, r_buffer, r_len, allow_object_decoding); - if (err != OK) { - return err; - } - if (r_buffer) { - // The first byte is not used by the marshalling, so store the type - // so we know how to decompress and decode this variant. - r_buffer[0] = p_variant.get_type(); - } - } - - return OK; -} - -Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) { - const uint8_t *buf = p_buffer; - int len = p_len; - - ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA); - uint8_t type = buf[0] & VARIANT_META_TYPE_MASK; - uint8_t encode_mode = buf[0] & VARIANT_META_EMODE_MASK; - - ERR_FAIL_COND_V(type >= Variant::VARIANT_MAX, ERR_INVALID_DATA); - - switch (type) { - case Variant::BOOL: { - bool val = (buf[0] & VARIANT_META_BOOL_MASK) > 0; - r_variant = val; - if (r_len) { - *r_len = 1; - } - } break; - case Variant::INT: { - buf += 1; - len -= 1; - if (r_len) { - *r_len = 1; - } - if (encode_mode == ENCODE_8) { - // 8 bits. - ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA); - int8_t val = buf[0]; - r_variant = val; - if (r_len) { - (*r_len) += 1; - } - } else if (encode_mode == ENCODE_16) { - // 16 bits. - ERR_FAIL_COND_V(len < 2, ERR_INVALID_DATA); - int16_t val = decode_uint16(buf); - r_variant = val; - if (r_len) { - (*r_len) += 2; - } - } else if (encode_mode == ENCODE_32) { - // 32 bits. - ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); - int32_t val = decode_uint32(buf); - r_variant = val; - if (r_len) { - (*r_len) += 4; - } - } else { - // 64 bits. - ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA); - int64_t val = decode_uint64(buf); - r_variant = val; - if (r_len) { - (*r_len) += 8; - } - } - } break; - default: - Error err = decode_variant(r_variant, p_buffer, p_len, r_len, allow_object_decoding); - if (err != OK) { - return err; - } - } - - return OK; -} - -void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) { - ERR_FAIL_COND_MSG(network_peer.is_null(), "Attempt to remote call/set when networking is not active in SceneTree."); - - ERR_FAIL_COND_MSG(network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTING, "Attempt to remote call/set when networking is not connected yet in SceneTree."); - - ERR_FAIL_COND_MSG(network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, "Attempt to remote call/set when networking is disconnected."); - - ERR_FAIL_COND_MSG(p_argcount > 255, "Too many arguments >255."); - - if (p_to != 0 && !connected_peers.has(ABS(p_to))) { - ERR_FAIL_COND_MSG(p_to == network_peer->get_unique_id(), "Attempt to remote call/set yourself! unique ID: " + itos(network_peer->get_unique_id()) + "."); - - ERR_FAIL_MSG("Attempt to remote call unexisting ID: " + itos(p_to) + "."); - } - - NodePath from_path = (root_node->get_path()).rel_path_to(p_from->get_path()); - ERR_FAIL_COND_MSG(from_path.is_empty(), "Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE!"); - - // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(from_path); - if (!psc) { - // Path is not cached, create. - path_send_cache[from_path] = PathSentCache(); - psc = path_send_cache.getptr(from_path); - psc->id = last_send_cache_id++; - } - - // See if all peers have cached path (if so, call can be fast). - const bool has_all_peers = _send_confirm_path(p_from, from_path, psc, p_to); - - // Create base packet, lots of hardcode because it must be tight. - - int ofs = 0; - -#define MAKE_ROOM(m_amount) \ - if (packet_cache.size() < m_amount) \ - packet_cache.resize(m_amount); - - // Encode meta. - // The meta is composed by a single byte that contains (starting from the least significant bit): - // - `NetworkCommands` in the first three bits. - // - `NetworkNodeIdCompression` in the next 2 bits. - // - `NetworkNameIdCompression` in the next 1 bit. - // - `byte_only_or_no_args` in the next 1 bit. - // - So we still have the last bit free! - uint8_t command_type = NETWORK_COMMAND_REMOTE_CALL; - uint8_t node_id_compression = UINT8_MAX; - uint8_t name_id_compression = UINT8_MAX; - bool byte_only_or_no_args = false; - - MAKE_ROOM(1); - // The meta is composed along the way, so just set 0 for now. - packet_cache.write[0] = 0; - ofs += 1; - - // Encode Node ID. - if (has_all_peers) { - // Compress the node ID only if all the target peers already know it. - if (psc->id >= 0 && psc->id <= 255) { - // We can encode the id in 1 byte - node_id_compression = NETWORK_NODE_ID_COMPRESSION_8; - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = static_cast<uint8_t>(psc->id); - ofs += 1; - } else if (psc->id >= 0 && psc->id <= 65535) { - // We can encode the id in 2 bytes - node_id_compression = NETWORK_NODE_ID_COMPRESSION_16; - MAKE_ROOM(ofs + 2); - encode_uint16(static_cast<uint16_t>(psc->id), &(packet_cache.write[ofs])); - ofs += 2; - } else { - // Too big, let's use 4 bytes. - node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; - MAKE_ROOM(ofs + 4); - encode_uint32(psc->id, &(packet_cache.write[ofs])); - ofs += 4; - } - } else { - // The targets don't know the node yet, so we need to use 32 bits int. - node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; - MAKE_ROOM(ofs + 4); - encode_uint32(psc->id, &(packet_cache.write[ofs])); - ofs += 4; - } - - // Encode method ID - if (p_rpc_id <= UINT8_MAX) { - // The ID fits in 1 byte - name_id_compression = NETWORK_NAME_ID_COMPRESSION_8; - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = static_cast<uint8_t>(p_rpc_id); - ofs += 1; - } else { - // The ID is larger, let's use 2 bytes - name_id_compression = NETWORK_NAME_ID_COMPRESSION_16; - MAKE_ROOM(ofs + 2); - encode_uint16(p_rpc_id, &(packet_cache.write[ofs])); - ofs += 2; - } - - if (p_argcount == 0) { - byte_only_or_no_args = true; - } else if (p_argcount == 1 && p_arg[0]->get_type() == Variant::PACKED_BYTE_ARRAY) { - byte_only_or_no_args = true; - // Special optimization when only the byte vector is sent. - const Vector<uint8_t> data = *p_arg[0]; - MAKE_ROOM(ofs + data.size()); - memcpy(&(packet_cache.write[ofs]), data.ptr(), sizeof(uint8_t) * data.size()); - ofs += data.size(); - } else { - // Arguments - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = p_argcount; - ofs += 1; - for (int i = 0; i < p_argcount; i++) { - int len(0); - Error err = encode_and_compress_variant(*p_arg[i], nullptr, len); - ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC argument. THIS IS LIKELY A BUG IN THE ENGINE!"); - MAKE_ROOM(ofs + len); - encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len); - ofs += len; - } - } - - ERR_FAIL_COND(command_type > 7); - ERR_FAIL_COND(node_id_compression > 3); - ERR_FAIL_COND(name_id_compression > 1); - - // We can now set the meta - packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + ((byte_only_or_no_args ? 1 : 0) << BYTE_ONLY_OR_NO_ARGS_SHIFT); - -#ifdef DEBUG_ENABLED - _profile_bandwidth_data("out", ofs); -#endif - - // Take chance and set transfer mode, since all send methods will use it. - network_peer->set_transfer_channel(p_config.channel); - network_peer->set_transfer_mode(p_config.transfer_mode); - - if (has_all_peers) { - // They all have verified paths, so send fast. - network_peer->set_target_peer(p_to); // To all of you. - network_peer->put_packet(packet_cache.ptr(), ofs); // A message with love. - } else { - // Unreachable because the node ID is never compressed if the peers doesn't know it. - CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32); - - // Not all verified path, so send one by one. - - // Append path at the end, since we will need it for some packets. - CharString pname = String(from_path).utf8(); - int path_len = encode_cstring(pname.get_data(), nullptr); - MAKE_ROOM(ofs + path_len); - encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); - - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - if (p_to < 0 && E->get() == -p_to) { - continue; // Continue, excluded. - } - - if (p_to > 0 && E->get() != p_to) { - continue; // Continue, not for this peer. - } - - Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); - ERR_CONTINUE(!F); // Should never happen. - - network_peer->set_target_peer(E->get()); // To this one specifically. - - if (F->get()) { - // This one confirmed path, so use id. - encode_uint32(psc->id, &(packet_cache.write[1])); - network_peer->put_packet(packet_cache.ptr(), ofs); - } else { - // This one did not confirm path yet, so use entire path (sorry!). - encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag. - network_peer->put_packet(packet_cache.ptr(), ofs + path_len); - } - } - } -} - -void MultiplayerAPI::_add_peer(int p_id) { - connected_peers.insert(p_id); - path_get_cache.insert(p_id, PathGetCache()); - if (is_network_server()) { - replicator->spawn_all(p_id); - } - emit_signal(SNAME("network_peer_connected"), p_id); -} - -void MultiplayerAPI::_del_peer(int p_id) { - connected_peers.erase(p_id); - // Cleanup get cache. - path_get_cache.erase(p_id); - // Cleanup sent cache. - // Some refactoring is needed to make this faster and do paths GC. - List<NodePath> keys; - path_send_cache.get_key_list(&keys); - for (const NodePath &E : keys) { - PathSentCache *psc = path_send_cache.getptr(E); - psc->confirmed_peers.erase(p_id); - } - emit_signal(SNAME("network_peer_disconnected"), p_id); -} - -void MultiplayerAPI::_connected_to_server() { - emit_signal(SNAME("connected_to_server")); -} - -void MultiplayerAPI::_connection_failed() { - emit_signal(SNAME("connection_failed")); -} - -void MultiplayerAPI::_server_disconnected() { - emit_signal(SNAME("server_disconnected")); -} - -void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount) { - ERR_FAIL_COND_MSG(!network_peer.is_valid(), "Trying to call an RPC while no network peer is active."); - ERR_FAIL_COND_MSG(!p_node->is_inside_tree(), "Trying to call an RPC on a node which is not inside SceneTree."); - ERR_FAIL_COND_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, "Trying to call an RPC via a network peer which is not connected."); - - int node_id = network_peer->get_unique_id(); - bool call_local_native = false; - bool call_local_script = false; - uint16_t rpc_id = UINT16_MAX; - const RPCConfig config = _get_rpc_config(p_node, p_method, rpc_id); - ERR_FAIL_COND_MSG(config.name == StringName(), - vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, p_node->get_path())); - if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { - if (rpc_id & (1 << 15)) { - call_local_native = config.sync; - } else { - call_local_script = config.sync; - } - } - - if (p_peer_id != node_id) { -#ifdef DEBUG_ENABLED - _profile_node_data("out_rpc", p_node->get_instance_id()); -#endif - - _send_rpc(p_node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); - } - - if (call_local_native) { - int temp_id = rpc_sender_id; - rpc_sender_id = get_network_unique_id(); - Callable::CallError ce; - p_node->call(p_method, p_arg, p_argcount, ce); - rpc_sender_id = temp_id; - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); - error = "rpc() aborted in local call: - " + error + "."; - ERR_PRINT(error); - return; - } - } - - if (call_local_script) { - int temp_id = rpc_sender_id; - rpc_sender_id = get_network_unique_id(); - Callable::CallError ce; - ce.error = Callable::CallError::CALL_OK; - p_node->get_script_instance()->call(p_method, p_arg, p_argcount, ce); - rpc_sender_id = temp_id; - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); - error = "rpc() aborted in script local call: - " + error + "."; - ERR_PRINT(error); - return; - } - } - - ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.sync, "RPC '" + p_method + "' on yourself is not allowed by selected mode."); -} - -Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) { - ERR_FAIL_COND_V_MSG(p_data.size() < 1, ERR_INVALID_DATA, "Trying to send an empty raw packet."); - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no network peer is active."); - ERR_FAIL_COND_V_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a network peer which is not connected."); - - MAKE_ROOM(p_data.size() + 1); - const uint8_t *r = p_data.ptr(); - packet_cache.write[0] = NETWORK_COMMAND_RAW; - memcpy(&packet_cache.write[1], &r[0], p_data.size()); - - network_peer->set_target_peer(p_to); - network_peer->set_transfer_channel(p_channel); - network_peer->set_transfer_mode(p_mode); - - return network_peer->put_packet(packet_cache.ptr(), p_data.size() + 1); -} - -void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < 2, "Invalid packet received. Size too small."); - - Vector<uint8_t> out; - int len = p_packet_len - 1; - out.resize(len); - { - uint8_t *w = out.ptrw(); - memcpy(&w[0], &p_packet[1], len); - } - emit_signal(SNAME("network_peer_packet"), p_from, out); -} - -bool MultiplayerAPI::send_confirm_path(Node *p_node, NodePath p_path, int p_peer_id, int &r_id) { - // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(p_path); - if (!psc) { - // Path is not cached, create. - path_send_cache[p_path] = PathSentCache(); - psc = path_send_cache.getptr(p_path); - psc->id = last_send_cache_id++; - } - r_id = psc->id; - - // See if all peers have cached path (if so, call can be fast). - return _send_confirm_path(p_node, p_path, psc, p_peer_id); -} - -Node *MultiplayerAPI::get_cached_node(int p_from, uint32_t p_node_id) { - Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); - ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); - - Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(p_node_id); - ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_node_id, p_from)); - - PathGetCache::NodeInfo *ni = &F->get(); - Node *node = root_node->get_node(ni->path); - if (!node) { - ERR_PRINT("Failed to get cached path: " + String(ni->path) + "."); - } - return node; -} - -int MultiplayerAPI::get_network_unique_id() const { - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), 0, "No network peer is assigned. Unable to get unique network ID."); - return network_peer->get_unique_id(); -} - -bool MultiplayerAPI::is_network_server() const { - return network_peer.is_valid() && network_peer->is_server(); -} - -void MultiplayerAPI::set_refuse_new_network_connections(bool p_refuse) { - ERR_FAIL_COND_MSG(!network_peer.is_valid(), "No network peer is assigned. Unable to set 'refuse_new_connections'."); - network_peer->set_refuse_new_connections(p_refuse); -} - -bool MultiplayerAPI::is_refusing_new_network_connections() const { - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), false, "No network peer is assigned. Unable to get 'refuse_new_connections'."); - return network_peer->is_refusing_new_connections(); -} - -Vector<int> MultiplayerAPI::get_network_connected_peers() const { - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), Vector<int>(), "No network peer is assigned. Assume no peers are connected."); - - Vector<int> ret; - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - ret.push_back(E->get()); - } - - return ret; -} - -void MultiplayerAPI::set_allow_object_decoding(bool p_enable) { - allow_object_decoding = p_enable; -} - -bool MultiplayerAPI::is_object_decoding_allowed() const { - return allow_object_decoding; -} - -MultiplayerReplicator *MultiplayerAPI::get_replicator() const { - return replicator; -} - -void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { - replicator->scene_enter_exit_notify(p_scene, p_node, p_enter); -} - -void MultiplayerAPI::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); - ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node); - ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("has_network_peer"), &MultiplayerAPI::has_network_peer); - ClassDB::bind_method(D_METHOD("get_network_peer"), &MultiplayerAPI::get_network_peer); - ClassDB::bind_method(D_METHOD("get_network_unique_id"), &MultiplayerAPI::get_network_unique_id); - ClassDB::bind_method(D_METHOD("is_network_server"), &MultiplayerAPI::is_network_server); - ClassDB::bind_method(D_METHOD("get_rpc_sender_id"), &MultiplayerAPI::get_rpc_sender_id); - ClassDB::bind_method(D_METHOD("set_network_peer", "peer"), &MultiplayerAPI::set_network_peer); - ClassDB::bind_method(D_METHOD("poll"), &MultiplayerAPI::poll); - ClassDB::bind_method(D_METHOD("clear"), &MultiplayerAPI::clear); - - ClassDB::bind_method(D_METHOD("get_network_connected_peers"), &MultiplayerAPI::get_network_connected_peers); - ClassDB::bind_method(D_METHOD("set_refuse_new_network_connections", "refuse"), &MultiplayerAPI::set_refuse_new_network_connections); - ClassDB::bind_method(D_METHOD("is_refusing_new_network_connections"), &MultiplayerAPI::is_refusing_new_network_connections); - ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &MultiplayerAPI::set_allow_object_decoding); - ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &MultiplayerAPI::is_object_decoding_allowed); - ClassDB::bind_method(D_METHOD("get_replicator"), &MultiplayerAPI::get_replicator); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_network_connections"), "set_refuse_new_network_connections", "is_refusing_new_network_connections"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "network_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_network_peer", "get_network_peer"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_node", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_root_node", "get_root_node"); - ADD_PROPERTY_DEFAULT("refuse_new_network_connections", false); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replicator", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerReplicator", PROPERTY_USAGE_NONE), "", "get_replicator"); - - ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id"))); - ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id"))); - ADD_SIGNAL(MethodInfo("network_peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet"))); - ADD_SIGNAL(MethodInfo("connected_to_server")); - ADD_SIGNAL(MethodInfo("connection_failed")); - ADD_SIGNAL(MethodInfo("server_disconnected")); - - BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); - BIND_ENUM_CONSTANT(RPC_MODE_REMOTE); - BIND_ENUM_CONSTANT(RPC_MODE_MASTER); - BIND_ENUM_CONSTANT(RPC_MODE_PUPPET); -} - -MultiplayerAPI::MultiplayerAPI() { - replicator = memnew(MultiplayerReplicator(this)); - clear(); -} - -MultiplayerAPI::~MultiplayerAPI() { - clear(); - memdelete(replicator); -} diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 00d4d093da..84fd6496a7 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -335,7 +335,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { String exttype = get_unicode_string(); String path = get_unicode_string(); - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(res_path.get_base_dir().plus_file(path)); } @@ -626,7 +626,7 @@ Error ResourceLoaderBinary::load() { path = remaps[path]; } - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(path.get_base_dir().plus_file(external_resources[i].path)); } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 64237f3b15..3026236f07 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -278,7 +278,7 @@ static String _validate_local_path(const String &p_path) { ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(p_path); if (uid != ResourceUID::INVALID_ID) { return ResourceUID::get_singleton()->get_id_path(uid); - } else if (p_path.is_rel_path()) { + } else if (p_path.is_relative_path()) { return "res://" + p_path; } else { return ProjectSettings::get_singleton()->localize_path(p_path); diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 5c42213e61..eec9caf149 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -775,7 +775,7 @@ Basis::operator String() const { Quaternion Basis::get_quaternion() const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_rotation(), Quaternion(), "Basis must be normalized in order to be casted to a Quaternion. Use get_rotation_quaternion() or call orthonormalized() instead."); + ERR_FAIL_COND_V_MSG(!is_rotation(), Quaternion(), "Basis must be normalized in order to be casted to a Quaternion. Use get_rotation_quaternion() or call orthonormalized() if the Basis contains linearly independent vectors."); #endif /* Allow getting a quaternion from an unnormalized transform */ Basis m = *this; diff --git a/core/math/camera_matrix.cpp b/core/math/camera_matrix.cpp index 66c18f7b3c..8066a59281 100644 --- a/core/math/camera_matrix.cpp +++ b/core/math/camera_matrix.cpp @@ -341,8 +341,8 @@ bool CameraMatrix::get_endpoints(const Transform3D &p_transform, Vector3 *p_8poi Vector<Plane> CameraMatrix::get_projection_planes(const Transform3D &p_transform) const { /** Fast Plane Extraction from combined modelview/projection matrices. * References: - * https://web.archive.org/web/20011221205252/http://www.markmorley.com/opengl/frustumculling.html - * https://web.archive.org/web/20061020020112/http://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf + * https://web.archive.org/web/20011221205252/https://www.markmorley.com/opengl/frustumculling.html + * https://web.archive.org/web/20061020020112/https://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf */ Vector<Plane> planes; diff --git a/core/math/convex_hull.cpp b/core/math/convex_hull.cpp index 21cb0efe20..f67035c803 100644 --- a/core/math/convex_hull.cpp +++ b/core/math/convex_hull.cpp @@ -2260,10 +2260,21 @@ Error ConvexHullComputer::convex_hull(const Vector<Vector3> &p_points, Geometry3 r_mesh.vertices = ch.vertices; - r_mesh.edges.resize(ch.edges.size()); + // Copy the edges over. There's two "half-edges" for every edge, so we pick only one of them. + r_mesh.edges.resize(ch.edges.size() / 2); + uint32_t edges_copied = 0; for (uint32_t i = 0; i < ch.edges.size(); i++) { - r_mesh.edges.write[i].a = (&ch.edges[i])->get_source_vertex(); - r_mesh.edges.write[i].b = (&ch.edges[i])->get_target_vertex(); + uint32_t a = (&ch.edges[i])->get_source_vertex(); + uint32_t b = (&ch.edges[i])->get_target_vertex(); + if (a < b) { // Copy only the "canonical" edge. For the reverse edge, this will be false. + ERR_BREAK(edges_copied >= (uint32_t)r_mesh.edges.size()); + r_mesh.edges.write[edges_copied].a = a; + r_mesh.edges.write[edges_copied].b = b; + edges_copied++; + } + } + if (edges_copied != (uint32_t)r_mesh.edges.size()) { + ERR_PRINT("Invalid edge count."); } r_mesh.faces.resize(ch.faces.size()); diff --git a/core/math/convex_hull.h b/core/math/convex_hull.h index ba7be9c5e8..a860d60b02 100644 --- a/core/math/convex_hull.h +++ b/core/math/convex_hull.h @@ -49,7 +49,7 @@ subject to the following restrictions: #include "core/templates/vector.h" /// Convex hull implementation based on Preparata and Hong -/// See http://code.google.com/p/bullet/issues/detail?id=275 +/// See https://code.google.com/p/bullet/issues/detail?id=275 /// Ole Kniemeyer, MAXON Computer GmbH class ConvexHullComputer { public: diff --git a/core/math/dynamic_bvh.h b/core/math/dynamic_bvh.h index 0b6286cd9d..d63132b4da 100644 --- a/core/math/dynamic_bvh.h +++ b/core/math/dynamic_bvh.h @@ -41,7 +41,7 @@ /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2013 Erwin Coumans http://bulletphysics.org +Copyright (c) 2003-2013 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h index e1a5bfe6f2..8e5830f9b3 100644 --- a/core/math/geometry_2d.h +++ b/core/math/geometry_2d.h @@ -182,7 +182,15 @@ public: C = Vector2(C.x * Bn.x + C.y * Bn.y, C.y * Bn.x - C.x * Bn.y); D = Vector2(D.x * Bn.x + D.y * Bn.y, D.y * Bn.x - D.x * Bn.y); - if ((C.y < 0 && D.y < 0) || (C.y >= 0 && D.y >= 0)) { + // Fail if C x B and D x B have the same sign (segments don't intersect). + // (equivalent to condition (C.y < 0 && D.y < CMP_EPSILON) || (C.y > 0 && D.y > CMP_EPSILON)) + if (C.y * D.y > CMP_EPSILON) { + return false; + } + + // Fail if segments are parallel or colinear. + // (when A x B == zero, i.e (C - D) x B == zero, i.e C x B == D x B) + if (Math::is_equal_approx(C.y, D.y)) { return false; } @@ -193,7 +201,7 @@ public: return false; } - // (4) Apply the discovered position to line A-B in the original coordinate system. + // Apply the discovered position to line A-B in the original coordinate system. if (r_result) { *r_result = p_from_a + B * ABpos; } @@ -353,8 +361,14 @@ public: for (int i = 0; i < c; i++) { const Vector2 &v1 = p[i]; const Vector2 &v2 = p[(i + 1) % c]; - if (segment_intersects_segment(v1, v2, p_point, further_away, nullptr)) { + + Vector2 res; + if (segment_intersects_segment(v1, v2, p_point, further_away, &res)) { intersections++; + if (res.is_equal_approx(p_point)) { + // Point is in one of the polygon edges. + return true; + } } } diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h index cadfdc13d1..345e0fade0 100644 --- a/core/math/transform_3d.h +++ b/core/math/transform_3d.h @@ -155,7 +155,7 @@ _FORCE_INLINE_ Plane Transform3D::xform_inv(const Plane &p_plane) const { } _FORCE_INLINE_ AABB Transform3D::xform(const AABB &p_aabb) const { - /* http://dev.theomader.com/transform-bounding-boxes/ */ + /* https://dev.theomader.com/transform-bounding-boxes/ */ Vector3 min = p_aabb.position; Vector3 max = p_aabb.position + p_aabb.size; Vector3 tmin, tmax; diff --git a/core/math/triangulate.h b/core/math/triangulate.h index 55dc4e8e7d..249ca6238f 100644 --- a/core/math/triangulate.h +++ b/core/math/triangulate.h @@ -34,7 +34,7 @@ #include "core/math/vector2.h" /* -http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml +https://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml */ class Triangulate { diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp index 54abc1b7f2..b53dc05a00 100644 --- a/core/math/vector2.cpp +++ b/core/math/vector2.cpp @@ -34,6 +34,10 @@ real_t Vector2::angle() const { return Math::atan2(y, x); } +Vector2 Vector2::from_angle(const real_t p_angle) { + return Vector2(Math::cos(p_angle), Math::sin(p_angle)); +} + real_t Vector2::length() const { return Math::sqrt(x * x + y * y); } diff --git a/core/math/vector2.h b/core/math/vector2.h index 330b4741b1..332c0475fa 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -147,6 +147,7 @@ struct Vector2 { bool operator>=(const Vector2 &p_vec2) const { return x == p_vec2.x ? (y >= p_vec2.y) : (x > p_vec2.x); } real_t angle() const; + static Vector2 from_angle(const real_t p_angle); _FORCE_INLINE_ Vector2 abs() const { return Vector2(Math::abs(x), Math::abs(y)); diff --git a/core/multiplayer/SCsub b/core/multiplayer/SCsub new file mode 100644 index 0000000000..19a6549225 --- /dev/null +++ b/core/multiplayer/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.core_sources, "*.cpp") diff --git a/core/multiplayer/multiplayer.h b/core/multiplayer/multiplayer.h new file mode 100644 index 0000000000..00c81d3c9a --- /dev/null +++ b/core/multiplayer/multiplayer.h @@ -0,0 +1,80 @@ +/*************************************************************************/ +/* multiplayer.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 MULTIPLAYER_H +#define MULTIPLAYER_H + +#include "core/variant/binder_common.h" + +#include "core/string/string_name.h" + +namespace Multiplayer { + +enum TransferMode { + TRANSFER_MODE_UNRELIABLE, + TRANSFER_MODE_ORDERED, + TRANSFER_MODE_RELIABLE +}; + +enum RPCMode { + RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default) + RPC_MODE_ANY, // Any peer can call this RPC + RPC_MODE_AUTHORITY, // / Only the node's multiplayer authority (server by default) can call this RPC +}; + +struct RPCConfig { + StringName name; + RPCMode rpc_mode = RPC_MODE_DISABLED; + bool sync = false; + TransferMode transfer_mode = TRANSFER_MODE_RELIABLE; + int channel = 0; + + bool operator==(RPCConfig const &p_other) const { + return name == p_other.name; + } +}; + +struct SortRPCConfig { + StringName::AlphCompare compare; + bool operator()(const RPCConfig &p_a, const RPCConfig &p_b) const { + return compare(p_a.name, p_b.name); + } +}; + +}; // namespace Multiplayer + +// This is needed for proper docs generation (i.e. not "Multiplayer."-prefixed). +typedef Multiplayer::RPCMode RPCMode; +typedef Multiplayer::TransferMode TransferMode; + +VARIANT_ENUM_CAST(RPCMode); +VARIANT_ENUM_CAST(TransferMode); + +#endif // MULTIPLAYER_H diff --git a/core/multiplayer/multiplayer_api.cpp b/core/multiplayer/multiplayer_api.cpp new file mode 100644 index 0000000000..9543f77c1e --- /dev/null +++ b/core/multiplayer/multiplayer_api.cpp @@ -0,0 +1,668 @@ +/*************************************************************************/ +/* multiplayer_api.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 "multiplayer_api.h" + +#include "core/debugger/engine_debugger.h" +#include "core/io/marshalls.h" +#include "core/multiplayer/multiplayer_replicator.h" +#include "core/multiplayer/rpc_manager.h" +#include "scene/main/node.h" + +#include <stdint.h> + +#ifdef DEBUG_ENABLED +#include "core/os/os.h" +#endif + +#ifdef DEBUG_ENABLED +void MultiplayerAPI::profile_bandwidth(const String &p_inout, int p_size) { + if (EngineDebugger::is_profiling("multiplayer")) { + Array values; + values.push_back("bandwidth"); + values.push_back(p_inout); + values.push_back(OS::get_singleton()->get_ticks_msec()); + values.push_back(p_size); + EngineDebugger::profiler_add_frame_data("multiplayer", values); + } +} +#endif + +void MultiplayerAPI::poll() { + if (!multiplayer_peer.is_valid() || multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) { + return; + } + + multiplayer_peer->poll(); + + if (!multiplayer_peer.is_valid()) { // It's possible that polling might have resulted in a disconnection, so check here. + return; + } + + while (multiplayer_peer->get_available_packet_count()) { + int sender = multiplayer_peer->get_packet_peer(); + const uint8_t *packet; + int len; + + Error err = multiplayer_peer->get_packet(&packet, len); + if (err != OK) { + ERR_PRINT("Error getting packet!"); + break; // Something is wrong! + } + + remote_sender_id = sender; + _process_packet(sender, packet, len); + remote_sender_id = 0; + + if (!multiplayer_peer.is_valid()) { + break; // It's also possible that a packet or RPC caused a disconnection, so also check here. + } + } + if (multiplayer_peer.is_valid() && multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED) { + replicator->poll(); + } +} + +void MultiplayerAPI::clear() { + replicator->clear(); + connected_peers.clear(); + path_get_cache.clear(); + path_send_cache.clear(); + packet_cache.clear(); + last_send_cache_id = 1; +} + +void MultiplayerAPI::set_root_node(Node *p_node) { + root_node = p_node; +} + +Node *MultiplayerAPI::get_root_node() { + return root_node; +} + +void MultiplayerAPI::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) { + if (p_peer == multiplayer_peer) { + return; // Nothing to do + } + + ERR_FAIL_COND_MSG(p_peer.is_valid() && p_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, + "Supplied MultiplayerPeer must be connecting or connected."); + + if (multiplayer_peer.is_valid()) { + multiplayer_peer->disconnect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer)); + multiplayer_peer->disconnect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer)); + multiplayer_peer->disconnect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server)); + multiplayer_peer->disconnect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); + multiplayer_peer->disconnect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); + clear(); + } + + multiplayer_peer = p_peer; + + if (multiplayer_peer.is_valid()) { + multiplayer_peer->connect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer)); + multiplayer_peer->connect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer)); + multiplayer_peer->connect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server)); + multiplayer_peer->connect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); + multiplayer_peer->connect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); + } +} + +Ref<MultiplayerPeer> MultiplayerAPI::get_multiplayer_peer() const { + return multiplayer_peer; +} + +void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(root_node == nullptr, "Multiplayer root node was not initialized. If you are using custom multiplayer, remember to set the root node via MultiplayerAPI.set_root_node before using it."); + ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small."); + +#ifdef DEBUG_ENABLED + profile_bandwidth("in", p_packet_len); +#endif + + // Extract the `packet_type` from the LSB three bits: + uint8_t packet_type = p_packet[0] & CMD_MASK; + + switch (packet_type) { + case NETWORK_COMMAND_SIMPLIFY_PATH: { + _process_simplify_path(p_from, p_packet, p_packet_len); + } break; + + case NETWORK_COMMAND_CONFIRM_PATH: { + _process_confirm_path(p_from, p_packet, p_packet_len); + } break; + + case NETWORK_COMMAND_REMOTE_CALL: { + rpc_manager->process_rpc(p_from, p_packet, p_packet_len); + } break; + + case NETWORK_COMMAND_RAW: { + _process_raw(p_from, p_packet, p_packet_len); + } break; + case NETWORK_COMMAND_SPAWN: { + replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true); + } break; + case NETWORK_COMMAND_DESPAWN: { + replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false); + } break; + case NETWORK_COMMAND_SYNC: { + replicator->process_sync(p_from, p_packet, p_packet_len); + } break; + } +} + +void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small."); + int ofs = 1; + + String methods_md5; + methods_md5.parse_utf8((const char *)(p_packet + ofs), 32); + ofs += 33; + + int id = decode_uint32(&p_packet[ofs]); + ofs += 4; + + String paths; + paths.parse_utf8((const char *)(p_packet + ofs), p_packet_len - ofs); + + NodePath path = paths; + + if (!path_get_cache.has(p_from)) { + path_get_cache[p_from] = PathGetCache(); + } + + Node *node = root_node->get_node(path); + ERR_FAIL_COND(node == nullptr); + const bool valid_rpc_checksum = rpc_manager->get_rpc_md5(node) == methods_md5; + if (valid_rpc_checksum == false) { + ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); + } + + PathGetCache::NodeInfo ni; + ni.path = path; + + path_get_cache[p_from].nodes[id] = ni; + + // Encode path to send ack. + CharString pname = String(path).utf8(); + int len = encode_cstring(pname.get_data(), nullptr); + + Vector<uint8_t> packet; + + packet.resize(1 + 1 + len); + packet.write[0] = NETWORK_COMMAND_CONFIRM_PATH; + packet.write[1] = valid_rpc_checksum; + encode_cstring(pname.get_data(), &packet.write[2]); + + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + multiplayer_peer->set_target_peer(p_from); + multiplayer_peer->put_packet(packet.ptr(), packet.size()); +} + +void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < 3, "Invalid packet received. Size too small."); + + const bool valid_rpc_checksum = p_packet[1]; + + String paths; + paths.parse_utf8((const char *)&p_packet[2], p_packet_len - 2); + + NodePath path = paths; + + if (valid_rpc_checksum == false) { + ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); + } + + PathSentCache *psc = path_send_cache.getptr(path); + ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); + + Map<int, bool>::Element *E = psc->confirmed_peers.find(p_from); + ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path."); + E->get() = true; +} + +bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) { + bool has_all_peers = true; + List<int> peers_to_add; // If one is missing, take note to add it. + + for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { + if (p_target < 0 && E->get() == -p_target) { + continue; // Continue, excluded. + } + + if (p_target > 0 && E->get() != p_target) { + continue; // Continue, not for this peer. + } + + Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); + + if (!F || !F->get()) { + // Path was not cached, or was cached but is unconfirmed. + if (!F) { + // Not cached at all, take note. + peers_to_add.push_back(E->get()); + } + + has_all_peers = false; + } + } + + if (peers_to_add.size() > 0) { + // Those that need to be added, send a message for this. + + // Encode function name. + const CharString path = String(p_path).utf8(); + const int path_len = encode_cstring(path.get_data(), nullptr); + + // Extract MD5 from rpc methods list. + const String methods_md5 = rpc_manager->get_rpc_md5(p_node); + const int methods_md5_len = 33; // 32 + 1 for the `0` that is added by the encoder. + + Vector<uint8_t> packet; + packet.resize(1 + 4 + path_len + methods_md5_len); + int ofs = 0; + + packet.write[ofs] = NETWORK_COMMAND_SIMPLIFY_PATH; + ofs += 1; + + ofs += encode_cstring(methods_md5.utf8().get_data(), &packet.write[ofs]); + + ofs += encode_uint32(psc->id, &packet.write[ofs]); + + ofs += encode_cstring(path.get_data(), &packet.write[ofs]); + + for (int &E : peers_to_add) { + multiplayer_peer->set_target_peer(E); // To all of you. + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + multiplayer_peer->put_packet(packet.ptr(), packet.size()); + + psc->confirmed_peers.insert(E, false); // Insert into confirmed, but as false since it was not confirmed. + } + } + + return has_all_peers; +} + +// The variant is compressed and encoded; The first byte contains all the meta +// information and the format is: +// - The first LSB 5 bits are used for the variant type. +// - The next two bits are used to store the encoding mode. +// - The most significant is used to store the boolean value. +#define VARIANT_META_TYPE_MASK 0x1F +#define VARIANT_META_EMODE_MASK 0x60 +#define VARIANT_META_BOOL_MASK 0x80 +#define ENCODE_8 0 << 5 +#define ENCODE_16 1 << 5 +#define ENCODE_32 2 << 5 +#define ENCODE_64 3 << 5 +Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len) { + // Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31 + CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK); + + uint8_t *buf = r_buffer; + r_len = 0; + uint8_t encode_mode = 0; + + switch (p_variant.get_type()) { + case Variant::BOOL: { + if (buf) { + // We still have 1 free bit in the meta, so let's use it. + buf[0] = (p_variant.operator bool()) ? (1 << 7) : 0; + buf[0] |= encode_mode | p_variant.get_type(); + } + r_len += 1; + } break; + case Variant::INT: { + if (buf) { + // Reserve the first byte for the meta. + buf += 1; + } + r_len += 1; + int64_t val = p_variant; + if (val <= (int64_t)INT8_MAX && val >= (int64_t)INT8_MIN) { + // Use 8 bit + encode_mode = ENCODE_8; + if (buf) { + buf[0] = val; + } + r_len += 1; + } else if (val <= (int64_t)INT16_MAX && val >= (int64_t)INT16_MIN) { + // Use 16 bit + encode_mode = ENCODE_16; + if (buf) { + encode_uint16(val, buf); + } + r_len += 2; + } else if (val <= (int64_t)INT32_MAX && val >= (int64_t)INT32_MIN) { + // Use 32 bit + encode_mode = ENCODE_32; + if (buf) { + encode_uint32(val, buf); + } + r_len += 4; + } else { + // Use 64 bit + encode_mode = ENCODE_64; + if (buf) { + encode_uint64(val, buf); + } + r_len += 8; + } + // Store the meta + if (buf) { + buf -= 1; + buf[0] = encode_mode | p_variant.get_type(); + } + } break; + default: + // Any other case is not yet compressed. + Error err = encode_variant(p_variant, r_buffer, r_len, allow_object_decoding); + if (err != OK) { + return err; + } + if (r_buffer) { + // The first byte is not used by the marshalling, so store the type + // so we know how to decompress and decode this variant. + r_buffer[0] = p_variant.get_type(); + } + } + + return OK; +} + +Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) { + const uint8_t *buf = p_buffer; + int len = p_len; + + ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA); + uint8_t type = buf[0] & VARIANT_META_TYPE_MASK; + uint8_t encode_mode = buf[0] & VARIANT_META_EMODE_MASK; + + ERR_FAIL_COND_V(type >= Variant::VARIANT_MAX, ERR_INVALID_DATA); + + switch (type) { + case Variant::BOOL: { + bool val = (buf[0] & VARIANT_META_BOOL_MASK) > 0; + r_variant = val; + if (r_len) { + *r_len = 1; + } + } break; + case Variant::INT: { + buf += 1; + len -= 1; + if (r_len) { + *r_len = 1; + } + if (encode_mode == ENCODE_8) { + // 8 bits. + ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA); + int8_t val = buf[0]; + r_variant = val; + if (r_len) { + (*r_len) += 1; + } + } else if (encode_mode == ENCODE_16) { + // 16 bits. + ERR_FAIL_COND_V(len < 2, ERR_INVALID_DATA); + int16_t val = decode_uint16(buf); + r_variant = val; + if (r_len) { + (*r_len) += 2; + } + } else if (encode_mode == ENCODE_32) { + // 32 bits. + ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); + int32_t val = decode_uint32(buf); + r_variant = val; + if (r_len) { + (*r_len) += 4; + } + } else { + // 64 bits. + ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA); + int64_t val = decode_uint64(buf); + r_variant = val; + if (r_len) { + (*r_len) += 8; + } + } + } break; + default: + Error err = decode_variant(r_variant, p_buffer, p_len, r_len, allow_object_decoding); + if (err != OK) { + return err; + } + } + + return OK; +} + +void MultiplayerAPI::_add_peer(int p_id) { + connected_peers.insert(p_id); + path_get_cache.insert(p_id, PathGetCache()); + if (is_server()) { + replicator->spawn_all(p_id); + } + emit_signal(SNAME("peer_connected"), p_id); +} + +void MultiplayerAPI::_del_peer(int p_id) { + connected_peers.erase(p_id); + // Cleanup get cache. + path_get_cache.erase(p_id); + // Cleanup sent cache. + // Some refactoring is needed to make this faster and do paths GC. + List<NodePath> keys; + path_send_cache.get_key_list(&keys); + for (const NodePath &E : keys) { + PathSentCache *psc = path_send_cache.getptr(E); + psc->confirmed_peers.erase(p_id); + } + emit_signal(SNAME("peer_disconnected"), p_id); +} + +void MultiplayerAPI::_connected_to_server() { + emit_signal(SNAME("connected_to_server")); +} + +void MultiplayerAPI::_connection_failed() { + emit_signal(SNAME("connection_failed")); +} + +void MultiplayerAPI::_server_disconnected() { + emit_signal(SNAME("server_disconnected")); +} + +Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, Multiplayer::TransferMode p_mode, int p_channel) { + ERR_FAIL_COND_V_MSG(p_data.size() < 1, ERR_INVALID_DATA, "Trying to send an empty raw packet."); + ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no multiplayer peer is active."); + ERR_FAIL_COND_V_MSG(multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a multiplayer peer which is not connected."); + + if (packet_cache.size() < p_data.size() + 1) { + packet_cache.resize(p_data.size() + 1); + } + + const uint8_t *r = p_data.ptr(); + packet_cache.write[0] = NETWORK_COMMAND_RAW; + memcpy(&packet_cache.write[1], &r[0], p_data.size()); + + multiplayer_peer->set_target_peer(p_to); + multiplayer_peer->set_transfer_channel(p_channel); + multiplayer_peer->set_transfer_mode(p_mode); + + return multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 1); +} + +void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < 2, "Invalid packet received. Size too small."); + + Vector<uint8_t> out; + int len = p_packet_len - 1; + out.resize(len); + { + uint8_t *w = out.ptrw(); + memcpy(&w[0], &p_packet[1], len); + } + emit_signal(SNAME("peer_packet"), p_from, out); +} + +bool MultiplayerAPI::is_cache_confirmed(NodePath p_path, int p_peer) { + const PathSentCache *psc = path_send_cache.getptr(p_path); + ERR_FAIL_COND_V(!psc, false); + const Map<int, bool>::Element *F = psc->confirmed_peers.find(p_peer); + ERR_FAIL_COND_V(!F, false); // Should never happen. + return F->get(); +} + +bool MultiplayerAPI::send_confirm_path(Node *p_node, NodePath p_path, int p_peer_id, int &r_id) { + // See if the path is cached. + PathSentCache *psc = path_send_cache.getptr(p_path); + if (!psc) { + // Path is not cached, create. + path_send_cache[p_path] = PathSentCache(); + psc = path_send_cache.getptr(p_path); + psc->id = last_send_cache_id++; + } + r_id = psc->id; + + // See if all peers have cached path (if so, call can be fast). + return _send_confirm_path(p_node, p_path, psc, p_peer_id); +} + +Node *MultiplayerAPI::get_cached_node(int p_from, uint32_t p_node_id) { + Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); + ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); + + Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(p_node_id); + ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_node_id, p_from)); + + PathGetCache::NodeInfo *ni = &F->get(); + Node *node = root_node->get_node(ni->path); + if (!node) { + ERR_PRINT("Failed to get cached path: " + String(ni->path) + "."); + } + return node; +} + +int MultiplayerAPI::get_unique_id() const { + ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), 0, "No multiplayer peer is assigned. Unable to get unique ID."); + return multiplayer_peer->get_unique_id(); +} + +bool MultiplayerAPI::is_server() const { + return multiplayer_peer.is_valid() && multiplayer_peer->is_server(); +} + +void MultiplayerAPI::set_refuse_new_connections(bool p_refuse) { + ERR_FAIL_COND_MSG(!multiplayer_peer.is_valid(), "No multiplayer peer is assigned. Unable to set 'refuse_new_connections'."); + multiplayer_peer->set_refuse_new_connections(p_refuse); +} + +bool MultiplayerAPI::is_refusing_new_connections() const { + ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), false, "No multiplayer peer is assigned. Unable to get 'refuse_new_connections'."); + return multiplayer_peer->is_refusing_new_connections(); +} + +Vector<int> MultiplayerAPI::get_peer_ids() const { + ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), Vector<int>(), "No multiplayer peer is assigned. Assume no peers are connected."); + + Vector<int> ret; + for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { + ret.push_back(E->get()); + } + + return ret; +} + +void MultiplayerAPI::set_allow_object_decoding(bool p_enable) { + allow_object_decoding = p_enable; +} + +bool MultiplayerAPI::is_object_decoding_allowed() const { + return allow_object_decoding; +} + +void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { + replicator->scene_enter_exit_notify(p_scene, p_node, p_enter); +} + +void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { + rpc_manager->rpcp(p_node, p_peer_id, p_method, p_arg, p_argcount); +} + +void MultiplayerAPI::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); + ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node); + ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("has_multiplayer_peer"), &MultiplayerAPI::has_multiplayer_peer); + ClassDB::bind_method(D_METHOD("get_multiplayer_peer"), &MultiplayerAPI::get_multiplayer_peer); + ClassDB::bind_method(D_METHOD("set_multiplayer_peer", "peer"), &MultiplayerAPI::set_multiplayer_peer); + ClassDB::bind_method(D_METHOD("get_unique_id"), &MultiplayerAPI::get_unique_id); + ClassDB::bind_method(D_METHOD("is_server"), &MultiplayerAPI::is_server); + ClassDB::bind_method(D_METHOD("get_remote_sender_id"), &MultiplayerAPI::get_remote_sender_id); + ClassDB::bind_method(D_METHOD("poll"), &MultiplayerAPI::poll); + ClassDB::bind_method(D_METHOD("clear"), &MultiplayerAPI::clear); + + ClassDB::bind_method(D_METHOD("get_peers"), &MultiplayerAPI::get_peer_ids); + ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "refuse"), &MultiplayerAPI::set_refuse_new_connections); + ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &MultiplayerAPI::is_refusing_new_connections); + ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &MultiplayerAPI::set_allow_object_decoding); + ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &MultiplayerAPI::is_object_decoding_allowed); + ClassDB::bind_method(D_METHOD("get_replicator"), &MultiplayerAPI::get_replicator); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_multiplayer_peer", "get_multiplayer_peer"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_node", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_root_node", "get_root_node"); + ADD_PROPERTY_DEFAULT("refuse_new_connections", false); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replicator", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerReplicator", PROPERTY_USAGE_NONE), "", "get_replicator"); + + ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet"))); + ADD_SIGNAL(MethodInfo("connected_to_server")); + ADD_SIGNAL(MethodInfo("connection_failed")); + ADD_SIGNAL(MethodInfo("server_disconnected")); +} + +MultiplayerAPI::MultiplayerAPI() { + replicator = memnew(MultiplayerReplicator(this)); + rpc_manager = memnew(RPCManager(this)); + clear(); +} + +MultiplayerAPI::~MultiplayerAPI() { + clear(); + memdelete(replicator); + memdelete(rpc_manager); +} diff --git a/core/io/multiplayer_api.h b/core/multiplayer/multiplayer_api.h index 5853541efa..1fb0318403 100644 --- a/core/io/multiplayer_api.h +++ b/core/multiplayer/multiplayer_api.h @@ -31,42 +31,17 @@ #ifndef MULTIPLAYER_API_H #define MULTIPLAYER_API_H -#include "core/io/multiplayer_peer.h" -#include "core/io/resource_uid.h" +#include "core/multiplayer/multiplayer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "core/object/ref_counted.h" class MultiplayerReplicator; +class RPCManager; class MultiplayerAPI : public RefCounted { GDCLASS(MultiplayerAPI, RefCounted); public: - enum RPCMode { - RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default) - RPC_MODE_REMOTE, // Using rpc() on it will call method in all remote peers - RPC_MODE_MASTER, // Using rpc() on it will call method on wherever the master is, be it local or remote - RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets - }; - - struct RPCConfig { - StringName name; - RPCMode rpc_mode = RPC_MODE_DISABLED; - bool sync = false; - MultiplayerPeer::TransferMode transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; - int channel = 0; - - bool operator==(RPCConfig const &p_other) const { - return name == p_other.name; - } - }; - - struct SortRPCConfig { - StringName::AlphCompare compare; - bool operator()(const RPCConfig &p_a, const RPCConfig &p_b) const { - return compare(p_a.name, p_b.name); - } - }; - enum NetworkCommands { NETWORK_COMMAND_REMOTE_CALL = 0, NETWORK_COMMAND_SIMPLIFY_PATH, @@ -74,23 +49,20 @@ public: NETWORK_COMMAND_RAW, NETWORK_COMMAND_SPAWN, NETWORK_COMMAND_DESPAWN, + NETWORK_COMMAND_SYNC, }; - enum NetworkNodeIdCompression { - NETWORK_NODE_ID_COMPRESSION_8 = 0, - NETWORK_NODE_ID_COMPRESSION_16, - NETWORK_NODE_ID_COMPRESSION_32, - }; - - enum NetworkNameIdCompression { - NETWORK_NAME_ID_COMPRESSION_8 = 0, - NETWORK_NAME_ID_COMPRESSION_16, + // For each command, the 4 MSB can contain custom flags, as defined by subsystems. + enum { + CMD_FLAG_0_SHIFT = 4, + CMD_FLAG_1_SHIFT = 5, + CMD_FLAG_2_SHIFT = 6, + CMD_FLAG_3_SHIFT = 7, }; + // This is the mask that will be used to extract the command. enum { - NODE_ID_COMPRESSION_SHIFT = 3, - NAME_ID_COMPRESSION_SHIFT = 5, - BYTE_ONLY_OR_NO_ARGS_SHIFT = 6, + CMD_MASK = 7, // 0x7 -> 0b00001111 }; private: @@ -110,49 +82,52 @@ private: Map<int, NodeInfo> nodes; }; - Ref<MultiplayerPeer> network_peer; - int rpc_sender_id = 0; + Ref<MultiplayerPeer> multiplayer_peer; Set<int> connected_peers; + int remote_sender_id = 0; + int remote_sender_override = 0; + HashMap<NodePath, PathSentCache> path_send_cache; Map<int, PathGetCache> path_get_cache; int last_send_cache_id; Vector<uint8_t> packet_cache; + Node *root_node = nullptr; bool allow_object_decoding = false; + MultiplayerReplicator *replicator = nullptr; + RPCManager *rpc_manager = nullptr; protected: static void _bind_methods(); + bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target); void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len); - Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len); - void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); - void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount); - bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target); - public: void poll(); void clear(); void set_root_node(Node *p_node); Node *get_root_node(); - void set_network_peer(const Ref<MultiplayerPeer> &p_peer); - Ref<MultiplayerPeer> get_network_peer() const; - Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0); + void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer); + Ref<MultiplayerPeer> get_multiplayer_peer() const; + + Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, Multiplayer::TransferMode p_mode = Multiplayer::TRANSFER_MODE_RELIABLE, int p_channel = 0); Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len); Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len); // Called by Node.rpc - void rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount); + void rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount); // Called by Node._notification void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); // Called by replicator bool send_confirm_path(Node *p_node, NodePath p_path, int p_target, int &p_id); Node *get_cached_node(int p_from, uint32_t p_node_id); + bool is_cache_confirmed(NodePath p_path, int p_peer); void _add_peer(int p_id); void _del_peer(int p_id); @@ -160,23 +135,28 @@ public: void _connection_failed(); void _server_disconnected(); - bool has_network_peer() const { return network_peer.is_valid(); } - Vector<int> get_network_connected_peers() const; - int get_rpc_sender_id() const { return rpc_sender_id; } - int get_network_unique_id() const; - bool is_network_server() const; - void set_refuse_new_network_connections(bool p_refuse); - bool is_refusing_new_network_connections() const; + bool has_multiplayer_peer() const { return multiplayer_peer.is_valid(); } + Vector<int> get_peer_ids() const; + const Set<int> get_connected_peers() const { return connected_peers; } + int get_remote_sender_id() const { return remote_sender_override ? remote_sender_override : remote_sender_id; } + void set_remote_sender_override(int p_id) { remote_sender_override = p_id; } + int get_unique_id() const; + bool is_server() const; + void set_refuse_new_connections(bool p_refuse); + bool is_refusing_new_connections() const; void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; - MultiplayerReplicator *get_replicator() const; + MultiplayerReplicator *get_replicator() const { return replicator; } + RPCManager *get_rpc_manager() const { return rpc_manager; } + +#ifdef DEBUG_ENABLED + void profile_bandwidth(const String &p_inout, int p_size); +#endif MultiplayerAPI(); ~MultiplayerAPI(); }; -VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode); - #endif // MULTIPLAYER_API_H diff --git a/core/io/multiplayer_peer.cpp b/core/multiplayer/multiplayer_peer.cpp index 83cf24d7e3..40847102d8 100644 --- a/core/io/multiplayer_peer.cpp +++ b/core/multiplayer/multiplayer_peer.cpp @@ -75,10 +75,6 @@ void MultiplayerPeer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_mode", PROPERTY_HINT_ENUM, "Unreliable,Unreliable Ordered,Reliable"), "set_transfer_mode", "get_transfer_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel", PROPERTY_HINT_RANGE, "0,255,1"), "set_transfer_channel", "get_transfer_channel"); - BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE); - BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE_ORDERED); - BIND_ENUM_CONSTANT(TRANSFER_MODE_RELIABLE); - BIND_ENUM_CONSTANT(CONNECTION_DISCONNECTED); BIND_ENUM_CONSTANT(CONNECTION_CONNECTING); BIND_ENUM_CONSTANT(CONNECTION_CONNECTED); diff --git a/core/io/multiplayer_peer.h b/core/multiplayer/multiplayer_peer.h index 7ca4e7930b..ba00c3b41b 100644 --- a/core/io/multiplayer_peer.h +++ b/core/multiplayer/multiplayer_peer.h @@ -32,6 +32,7 @@ #define NETWORKED_MULTIPLAYER_PEER_H #include "core/io/packet_peer.h" +#include "core/multiplayer/multiplayer.h" class MultiplayerPeer : public PacketPeer { GDCLASS(MultiplayerPeer, PacketPeer); @@ -44,11 +45,6 @@ public: TARGET_PEER_BROADCAST = 0, TARGET_PEER_SERVER = 1 }; - enum TransferMode { - TRANSFER_MODE_UNRELIABLE, - TRANSFER_MODE_UNRELIABLE_ORDERED, - TRANSFER_MODE_RELIABLE, - }; enum ConnectionStatus { CONNECTION_DISCONNECTED, @@ -58,8 +54,8 @@ public: virtual void set_transfer_channel(int p_channel) = 0; virtual int get_transfer_channel() const = 0; - virtual void set_transfer_mode(TransferMode p_mode) = 0; - virtual TransferMode get_transfer_mode() const = 0; + virtual void set_transfer_mode(Multiplayer::TransferMode p_mode) = 0; + virtual Multiplayer::TransferMode get_transfer_mode() const = 0; virtual void set_target_peer(int p_peer_id) = 0; virtual int get_packet_peer() const = 0; @@ -79,7 +75,6 @@ public: MultiplayerPeer() {} }; -VARIANT_ENUM_CAST(MultiplayerPeer::TransferMode) VARIANT_ENUM_CAST(MultiplayerPeer::ConnectionStatus) #endif // NETWORKED_MULTIPLAYER_PEER_H diff --git a/core/io/multiplayer_replicator.cpp b/core/multiplayer/multiplayer_replicator.cpp index ba0fe32b58..17af2c5ef8 100644 --- a/core/io/multiplayer_replicator.cpp +++ b/core/multiplayer/multiplayer_replicator.cpp @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "core/io/multiplayer_replicator.h" +#include "core/multiplayer/multiplayer_replicator.h" #include "core/io/marshalls.h" #include "scene/main/node.h" @@ -38,6 +38,140 @@ if (packet_cache.size() < m_amount) \ packet_cache.resize(m_amount); +Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer) { + ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); + SceneConfig &cfg = replications[p_scene_id]; + int full_size = 0; + bool same_size = true; + int last_size = 0; + bool all_raw = true; + struct EncodeInfo { + int size = 0; + bool raw = false; + List<Variant> state; + }; + Map<ObjectID, struct EncodeInfo> state; + if (tracked_objects.has(p_scene_id)) { + for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { + Object *obj = ObjectDB::get_instance(obj_id); + if (obj) { + struct EncodeInfo info; + Error err = _get_state(cfg.sync_properties, obj, info.state); + ERR_CONTINUE(err); + err = _encode_state(info.state, nullptr, info.size, &info.raw); + ERR_CONTINUE(err); + state[obj_id] = info; + full_size += info.size; + if (last_size && info.size != last_size) { + same_size = false; + } + all_raw = all_raw && info.raw; + last_size = info.size; + } + } + } + // Default implementation do not send empty updates. + if (!full_size) { + return OK; + } +#ifdef DEBUG_ENABLED + if (full_size > 4096 && cfg.sync_interval) { + WARN_PRINT_ONCE(vformat("The timed state update for scene %d is big (%d bytes) consider optimizing it", p_scene_id)); + } +#endif + if (same_size) { + // This is fast and small. Should we allow more than 256 objects per type? + // This costs us 1 byte. + MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + 2 + full_size); + } else { + MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + state.size() * 2 + full_size); + } + int ofs = 0; + uint8_t *ptr = packet_cache.ptrw(); + ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC | (same_size ? BYTE_OR_ZERO_FLAG : 0); + ofs = 1; + ofs += encode_uint64(p_scene_id, &ptr[ofs]); + ptr[ofs] = cfg.sync_recv++; + ofs += 1; + ofs += encode_uint16(state.size(), &ptr[ofs]); + if (same_size) { + ofs += encode_uint16(last_size + (all_raw ? 1 << 15 : 0), &ptr[ofs]); + } + for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { + if (!state.has(obj_id)) { + continue; + } + struct EncodeInfo &info = state[obj_id]; + Object *obj = ObjectDB::get_instance(obj_id); + ERR_CONTINUE(!obj); + int size = 0; + if (!same_size) { + // We need to encode the size of every object. + ofs += encode_uint16(info.size + (info.raw ? 1 << 15 : 0), &ptr[ofs]); + } + Error err = _encode_state(info.state, &ptr[ofs], size, &info.raw); + ERR_CONTINUE(err); + ofs += size; + } + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + peer->set_target_peer(p_peer); + peer->set_transfer_channel(0); + peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_UNRELIABLE); + return peer->put_packet(ptr, ofs); +} + +void MultiplayerReplicator::_process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < SYNC_CMD_OFFSET + 5, "Invalid spawn packet received"); + ERR_FAIL_COND_MSG(!replications.has(p_id), "Invalid spawn ID received " + itos(p_id)); + SceneConfig &cfg = replications[p_id]; + ERR_FAIL_COND_MSG(cfg.mode != REPLICATION_MODE_SERVER || multiplayer->is_server(), "The defualt implementation only allows sync packets from the server"); + const bool same_size = p_packet[0] & BYTE_OR_ZERO_FLAG; + int ofs = SYNC_CMD_OFFSET; + int time = p_packet[ofs]; + // Skip old update. + if (time < cfg.sync_recv && cfg.sync_recv - time < 127) { + return; + } + cfg.sync_recv = time; + ofs += 1; + int count = decode_uint16(&p_packet[ofs]); + ofs += 2; +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count); +#else + if (!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count) { + return; + } +#endif + int data_size = 0; + bool raw = false; + if (same_size) { + // This is fast and optimized. + data_size = decode_uint16(&p_packet[ofs]); + raw = (data_size & (1 << 15)) != 0; + data_size = data_size & ~(1 << 15); + ofs += 2; + ERR_FAIL_COND(p_packet_len - ofs < data_size * count); + } + for (const ObjectID &obj_id : tracked_objects[p_id]) { + Object *obj = ObjectDB::get_instance(obj_id); + ERR_CONTINUE(!obj); + if (!same_size) { + // This is slow and wasteful. + data_size = decode_uint16(&p_packet[ofs]); + raw = (data_size & (1 << 15)) != 0; + data_size = data_size & ~(1 << 15); + ofs += 2; + ERR_FAIL_COND(p_packet_len - ofs < data_size); + } + int size = 0; + Error err = _decode_state(cfg.sync_properties, obj, &p_packet[ofs], data_size, size, raw); + ofs += data_size; + ERR_CONTINUE(err); + ERR_CONTINUE(size != data_size); + } +} + Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn) { ERR_FAIL_COND_V(p_spawn && !p_obj, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); @@ -55,6 +189,8 @@ Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const Re bool is_raw = false; if (state_variants.size() == 1 && state_variants[0].get_type() == Variant::PACKED_BYTE_ARRAY) { is_raw = true; + const PackedByteArray pba = state_variants[0]; + state_len = pba.size(); } else if (state_variants.size()) { err = _encode_state(state_variants, nullptr, state_len); ERR_FAIL_COND_V(err, err); @@ -82,7 +218,7 @@ Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const Re int nlen = encode_cstring(cname.get_data(), nullptr); MAKE_ROOM(SPAWN_CMD_OFFSET + 4 + 4 + nlen + state_len); uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); + ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) | (is_raw ? BYTE_OR_ZERO_FLAG : 0); ofs = 1; ofs += encode_uint64(p_scene_id, &ptr[ofs]); ofs += encode_uint32(path_id, &ptr[ofs]); @@ -97,11 +233,11 @@ Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const Re memcpy(&ptr[ofs], pba.ptr(), state_len); } - Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); - network_peer->set_target_peer(p_peer_id); - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - return network_peer->put_packet(ptr, ofs + state_len); + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + peer->set_target_peer(p_peer_id); + peer->set_transfer_channel(0); + peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + return peer->put_packet(ptr, ofs + state_len); } void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { @@ -126,7 +262,7 @@ void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const Res if (cfg.mode == REPLICATION_MODE_SERVER && p_from == 1) { String scene_path = ResourceUID::get_singleton()->get_id_path(p_scene_id); if (p_spawn) { - const bool is_raw = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; + const bool is_raw = ((p_packet[0] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_SHIFT) == 1; ERR_FAIL_COND_MSG(parent->has_node(name), vformat("Unable to spawn node. Node already exists: %s/%s", parent->get_path(), name)); RES res = ResourceLoader::load(scene_path); @@ -136,6 +272,7 @@ void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const Res Node *node = scene->instantiate(); ERR_FAIL_COND(!node); replicated_nodes[node->get_instance_id()] = p_scene_id; + _track(p_scene_id, node); int size; _decode_state(cfg.properties, node, &p_packet[ofs], p_packet_len - ofs, size, is_raw); parent->_add_child_nocheck(node, name); @@ -145,6 +282,7 @@ void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const Res Node *node = parent->get_node(name); ERR_FAIL_COND_MSG(!replicated_nodes.has(node->get_instance_id()), vformat("Trying to despawn a Node that was not replicated: %s/%s", parent->get_path(), name)); emit_signal(SNAME("despawned"), p_scene_id, node); + _untrack(p_scene_id, node); replicated_nodes.erase(node->get_instance_id()); node->queue_delete(); } @@ -170,7 +308,7 @@ void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_p const SceneConfig &cfg = replications[id]; if (cfg.on_spawn_despawn_receive.is_valid()) { int ofs = SPAWN_CMD_OFFSET; - bool is_raw = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; + bool is_raw = ((p_packet[0] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_SHIFT) == 1; Variant data; int left = p_packet_len - ofs; if (is_raw && left) { @@ -197,6 +335,37 @@ void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_p } } +void MultiplayerReplicator::process_sync(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received"); + ResourceUID::ID id = decode_uint64(&p_packet[1]); + ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id)); + const SceneConfig &cfg = replications[id]; + if (cfg.on_sync_receive.is_valid()) { + Array objs; + if (tracked_objects.has(id)) { + objs.resize(tracked_objects[id].size()); + int idx = 0; + for (const ObjectID &obj_id : tracked_objects[id]) { + objs[idx++] = ObjectDB::get_instance(obj_id); + } + } + PackedByteArray pba; + pba.resize(p_packet_len - SPAWN_CMD_OFFSET); + if (pba.size()) { + memcpy(pba.ptrw(), p_packet, p_packet_len - SPAWN_CMD_OFFSET); + } + Variant args[4] = { p_from, id, objs, pba }; + Variant *argp[4] = { args, &args[1], &args[2], &args[3] }; + Callable::CallError ce; + Variant ret; + cfg.on_sync_receive.call((const Variant **)argp, 4, ret, ce); + ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom sync function failed"); + } else { + ERR_FAIL_COND_MSG(p_from != 1, "Default sync implementation only allow syncing from server to client"); + _process_default_sync(id, p_packet, p_packet_len); + } +} + Error MultiplayerReplicator::_get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant) { ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Cannot encode null object"); for (const StringName &prop : p_properties) { @@ -297,7 +466,7 @@ Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, Replicati SceneConfig cfg; cfg.mode = p_mode; for (int i = 0; i < p_props.size(); i++) { - cfg.properties.push_back(StringName(p_props[i])); + cfg.properties.push_back(p_props[i]); } cfg.on_spawn_despawn_send = p_on_send; cfg.on_spawn_despawn_receive = p_on_recv; @@ -306,6 +475,21 @@ Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, Replicati return OK; } +Error MultiplayerReplicator::sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props, const Callable &p_on_send, const Callable &p_on_recv) { + ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty"); + ERR_FAIL_COND_V(!replications.has(p_id), ERR_UNCONFIGURED); + SceneConfig &cfg = replications[p_id]; + ERR_FAIL_COND_V_MSG(p_interval && cfg.mode != REPLICATION_MODE_SERVER && !p_on_send.is_valid(), ERR_INVALID_PARAMETER, "Timed updates in custom mode are only allowed if custom callbacks are also specified"); + for (int i = 0; i < p_props.size(); i++) { + cfg.sync_properties.push_back(p_props[i]); + } + cfg.on_sync_send = p_on_send; + cfg.on_sync_receive = p_on_recv; + cfg.sync_interval = p_interval * 1000; + return OK; +} + Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn) { int data_size = 0; int is_raw = false; @@ -321,7 +505,7 @@ Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUI } MAKE_ROOM(SPAWN_CMD_OFFSET + data_size); uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); + ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << BYTE_OR_ZERO_SHIFT); encode_uint64(p_scene_id, &ptr[1]); if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) { const PackedByteArray pba = p_data; @@ -329,20 +513,21 @@ Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUI } else if (data_size) { encode_variant(p_data, &ptr[SPAWN_CMD_OFFSET], data_size); } - Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); - network_peer->set_target_peer(p_peer_id); - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - return network_peer->put_packet(ptr, SPAWN_CMD_OFFSET + data_size); + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + peer->set_target_peer(p_peer_id); + peer->set_transfer_channel(0); + peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + return peer->put_packet(ptr, SPAWN_CMD_OFFSET + data_size); } Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { + ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); const SceneConfig &cfg = replications[p_scene_id]; if (cfg.on_spawn_despawn_send.is_valid()) { return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, true); } else { - ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server(), ERR_UNAVAILABLE, "Manual despawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); + ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server(), ERR_UNAVAILABLE, "Manual despawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); NodePath path = p_path; Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr; if (path.is_empty() && obj) { @@ -357,12 +542,13 @@ Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID & } Error MultiplayerReplicator::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { + ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); const SceneConfig &cfg = replications[p_scene_id]; if (cfg.on_spawn_despawn_send.is_valid()) { return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, false); } else { - ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server(), ERR_UNAVAILABLE, "Manual spawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); + ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server(), ERR_UNAVAILABLE, "Manual spawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); NodePath path = p_path; Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr; ERR_FAIL_COND_V_MSG(!obj, ERR_INVALID_PARAMETER, "Spawn default implementation requires the data to be an object."); @@ -408,13 +594,14 @@ Error MultiplayerReplicator::despawn(ResourceUID::ID p_scene_id, Object *p_obj, return _spawn_despawn(p_scene_id, p_obj, p_peer, false); } -PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj) { +PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj, bool p_initial) { PackedByteArray state; ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), state, vformat("Spawnable not found: %d", p_scene_id)); const SceneConfig &cfg = replications[p_scene_id]; int len = 0; List<Variant> state_vars; - Error err = _get_state(cfg.properties, p_obj, state_vars); + const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties; + Error err = _get_state(props, p_obj, state_vars); ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to retrieve object state."); err = _encode_state(state_vars, nullptr, len); ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to encode object state."); @@ -423,15 +610,16 @@ PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_sce return state; } -Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data) { +Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data, bool p_initial) { ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); const SceneConfig &cfg = replications[p_scene_id]; + const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties; int size; - return _decode_state(cfg.properties, p_obj, p_data.ptr(), p_data.size(), size); + return _decode_state(props, p_obj, p_data.ptr(), p_data.size(), size); } void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { - if (!multiplayer->has_network_peer()) { + if (!multiplayer->has_multiplayer_peer()) { return; } Node *root_node = multiplayer->get_root_node(); @@ -446,14 +634,16 @@ void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node } const SceneConfig &cfg = replications[id]; if (p_enter) { - if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server()) { + if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server()) { replicated_nodes[p_node->get_instance_id()] = id; + _track(id, p_node); spawn(id, p_node, 0); } emit_signal(SNAME("replicated_instance_added"), id, p_node); } else { - if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server() && replicated_nodes.has(p_node->get_instance_id())) { + if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server() && replicated_nodes.has(p_node->get_instance_id())) { replicated_nodes.erase(p_node->get_instance_id()); + _untrack(id, p_node); despawn(id, p_node, 0); } emit_signal(SNAME("replicated_instance_removed"), id, p_node); @@ -471,18 +661,119 @@ void MultiplayerReplicator::spawn_all(int p_peer) { } } +void MultiplayerReplicator::poll() { + for (KeyValue<ResourceUID::ID, SceneConfig> &E : replications) { + if (!E.value.sync_interval) { + continue; + } + if (E.value.mode == REPLICATION_MODE_SERVER && !multiplayer->is_server()) { + continue; + } + uint64_t time = OS::get_singleton()->get_ticks_usec(); + if (E.value.sync_last + E.value.sync_interval <= time) { + sync_all(E.key, 0); + E.value.sync_last = time; + } + // Handle wrapping. + if (E.value.sync_last > time) { + E.value.sync_last = time; + } + } +} + +void MultiplayerReplicator::track(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!replications.has(p_scene_id)); + const SceneConfig &cfg = replications[p_scene_id]; + ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode."); + _track(p_scene_id, p_obj); +} + +void MultiplayerReplicator::_track(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!p_obj); + ERR_FAIL_COND(!replications.has(p_scene_id)); + if (!tracked_objects.has(p_scene_id)) { + tracked_objects[p_scene_id] = List<ObjectID>(); + } + tracked_objects[p_scene_id].push_back(p_obj->get_instance_id()); +} + +void MultiplayerReplicator::untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!replications.has(p_scene_id)); + const SceneConfig &cfg = replications[p_scene_id]; + ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode."); + _untrack(p_scene_id, p_obj); +} + +void MultiplayerReplicator::_untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!p_obj); + ERR_FAIL_COND(!replications.has(p_scene_id)); + if (tracked_objects.has(p_scene_id)) { + tracked_objects[p_scene_id].erase(p_obj->get_instance_id()); + } +} + +Error MultiplayerReplicator::sync_all(const ResourceUID::ID &p_scene_id, int p_peer) { + ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); + if (!tracked_objects.has(p_scene_id)) { + return OK; + } + const SceneConfig &cfg = replications[p_scene_id]; + if (cfg.on_sync_send.is_valid()) { + Array objs; + if (tracked_objects.has(p_scene_id)) { + objs.resize(tracked_objects[p_scene_id].size()); + int idx = 0; + for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { + objs[idx++] = ObjectDB::get_instance(obj_id); + } + } + Variant args[3] = { p_scene_id, objs, p_peer }; + Variant *argp[3] = { args, &args[1], &args[2] }; + Callable::CallError ce; + Variant ret; + cfg.on_sync_send.call((const Variant **)argp, 3, ret, ce); + ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom sync function failed"); + return OK; + } else if (cfg.sync_properties.size()) { + return _sync_all_default(p_scene_id, p_peer); + } + return OK; +} + +Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, Multiplayer::TransferMode p_transfer_mode, int p_channel) { + ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); + const SceneConfig &cfg = replications[p_scene_id]; + ERR_FAIL_COND_V_MSG(!cfg.on_sync_send.is_valid(), ERR_UNCONFIGURED, "Sending raw sync messages is only available with custom functions"); + MAKE_ROOM(SYNC_CMD_OFFSET + p_data.size()); + uint8_t *ptr = packet_cache.ptrw(); + ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC; + encode_uint64(p_scene_id, &ptr[1]); + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + peer->set_target_peer(p_peer_id); + peer->set_transfer_channel(p_channel); + peer->set_transfer_mode(p_transfer_mode); + return peer->put_packet(ptr, SYNC_CMD_OFFSET + p_data.size()); +} + void MultiplayerReplicator::clear() { + tracked_objects.clear(); replicated_nodes.clear(); } void MultiplayerReplicator::_bind_methods() { ClassDB::bind_method(D_METHOD("spawn_config", "scene_id", "spawn_mode", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::spawn_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable())); + ClassDB::bind_method(D_METHOD("sync_config", "scene_id", "interval", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::sync_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable())); ClassDB::bind_method(D_METHOD("despawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::despawn, DEFVAL(0)); ClassDB::bind_method(D_METHOD("spawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::spawn, DEFVAL(0)); ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_despawn, DEFVAL(Variant()), DEFVAL(NodePath())); ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_spawn, DEFVAL(Variant()), DEFVAL(NodePath())); - ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object"), &MultiplayerReplicator::encode_state); - ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data"), &MultiplayerReplicator::decode_state); + ClassDB::bind_method(D_METHOD("send_sync", "peer_id", "scene_id", "data", "transfer_mode", "channel"), &MultiplayerReplicator::send_sync, DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("sync_all", "scene_id", "peer_id"), &MultiplayerReplicator::sync_all, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("track", "scene_id", "object"), &MultiplayerReplicator::track); + ClassDB::bind_method(D_METHOD("untrack", "scene_id", "object"), &MultiplayerReplicator::untrack); + ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object", "initial"), &MultiplayerReplicator::encode_state, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data", "initial"), &MultiplayerReplicator::decode_state, DEFVAL(true)); ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); diff --git a/core/io/multiplayer_replicator.h b/core/multiplayer/multiplayer_replicator.h index e19dd80602..7fa774fdf4 100644 --- a/core/io/multiplayer_replicator.h +++ b/core/multiplayer/multiplayer_replicator.h @@ -31,7 +31,10 @@ #ifndef MULTIPLAYER_REPLICATOR_H #define MULTIPLAYER_REPLICATOR_H -#include "core/io/multiplayer_api.h" +#include "core/multiplayer/multiplayer_api.h" + +#include "core/io/resource_uid.h" +#include "core/templates/hash_map.h" #include "core/variant/typed_array.h" class MultiplayerReplicator : public Object { @@ -40,6 +43,7 @@ class MultiplayerReplicator : public Object { public: enum { SPAWN_CMD_OFFSET = 9, + SYNC_CMD_OFFSET = 9, }; enum ReplicationMode { @@ -50,44 +54,79 @@ public: struct SceneConfig { ReplicationMode mode; + uint64_t sync_interval = 0; + uint64_t sync_last = 0; + uint8_t sync_recv = 0; List<StringName> properties; + List<StringName> sync_properties; Callable on_spawn_despawn_send; Callable on_spawn_despawn_receive; + Callable on_sync_send; + Callable on_sync_receive; }; protected: static void _bind_methods(); private: + enum { + BYTE_OR_ZERO_SHIFT = MultiplayerAPI::CMD_FLAG_0_SHIFT, + }; + + enum { + BYTE_OR_ZERO_FLAG = 1 << BYTE_OR_ZERO_SHIFT, + }; + MultiplayerAPI *multiplayer = nullptr; Vector<uint8_t> packet_cache; Map<ResourceUID::ID, SceneConfig> replications; Map<ObjectID, ResourceUID::ID> replicated_nodes; + HashMap<ResourceUID::ID, List<ObjectID>> tracked_objects; + // Encoding + Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant); Error _encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr); Error _decode_state(const List<StringName> &p_cfg, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false); - Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant); + + // Spawn Error _spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn); Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn); void _process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn); Error _send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn); + // Sync + void _process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len); + Error _sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer); + void _track(const ResourceUID::ID &p_scene_id, Object *p_object); + void _untrack(const ResourceUID::ID &p_scene_id, Object *p_object); + public: void clear(); + // Encoding + PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node, bool p_initial); + Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data, bool p_initial); + + // Spawn Error spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable()); Error spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0); Error despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0); - Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath()); Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath()); - PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node); - Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data); + + // Sync + Error sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable()); + Error sync_all(const ResourceUID::ID &p_scene_id, int p_peer); + Error send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, Multiplayer::TransferMode p_mode, int p_channel); + void track(const ResourceUID::ID &p_scene_id, Object *p_object); + void untrack(const ResourceUID::ID &p_scene_id, Object *p_object); // Used by MultiplayerAPI void spawn_all(int p_peer); void process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn); + void process_sync(int p_from, const uint8_t *p_packet, int p_packet_len); void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); + void poll(); MultiplayerReplicator(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; diff --git a/core/multiplayer/rpc_manager.cpp b/core/multiplayer/rpc_manager.cpp new file mode 100644 index 0000000000..915571375e --- /dev/null +++ b/core/multiplayer/rpc_manager.cpp @@ -0,0 +1,525 @@ +/*************************************************************************/ +/* rpc_manager.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 "core/multiplayer/rpc_manager.h" + +#include "core/debugger/engine_debugger.h" +#include "core/io/marshalls.h" +#include "core/multiplayer/multiplayer_api.h" +#include "scene/main/node.h" + +#ifdef DEBUG_ENABLED +_FORCE_INLINE_ void RPCManager::_profile_node_data(const String &p_what, ObjectID p_id) { + if (EngineDebugger::is_profiling("multiplayer")) { + Array values; + values.push_back("node"); + values.push_back(p_id); + values.push_back(p_what); + EngineDebugger::profiler_add_frame_data("multiplayer", values); + } +} +#else +_FORCE_INLINE_ void RPCManager::_profile_node_data(const String &p_what, ObjectID p_id) {} +#endif + +// Returns the packet size stripping the node path added when the node is not yet cached. +int get_packet_len(uint32_t p_node_target, int p_packet_len) { + if (p_node_target & 0x80000000) { + int ofs = p_node_target & 0x7FFFFFFF; + return p_packet_len - (p_packet_len - ofs); + } else { + return p_packet_len; + } +} + +const Multiplayer::RPCConfig _get_rpc_config(const Node *p_node, const StringName &p_method, uint16_t &r_id) { + const Vector<Multiplayer::RPCConfig> node_config = p_node->get_node_rpc_methods(); + for (int i = 0; i < node_config.size(); i++) { + if (node_config[i].name == p_method) { + r_id = ((uint16_t)i) | (1 << 15); + return node_config[i]; + } + } + if (p_node->get_script_instance()) { + const Vector<Multiplayer::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); + for (int i = 0; i < script_config.size(); i++) { + if (script_config[i].name == p_method) { + r_id = (uint16_t)i; + return script_config[i]; + } + } + } + return Multiplayer::RPCConfig(); +} + +const Multiplayer::RPCConfig _get_rpc_config_by_id(Node *p_node, uint16_t p_id) { + Vector<Multiplayer::RPCConfig> config; + uint16_t id = p_id; + if (id & (1 << 15)) { + id = id & ~(1 << 15); + config = p_node->get_node_rpc_methods(); + } else if (p_node->get_script_instance()) { + config = p_node->get_script_instance()->get_rpc_methods(); + } + if (id < config.size()) { + return config[id]; + } + return Multiplayer::RPCConfig(); +} + +_FORCE_INLINE_ bool _can_call_mode(Node *p_node, Multiplayer::RPCMode mode, int p_remote_id) { + switch (mode) { + case Multiplayer::RPC_MODE_DISABLED: { + return false; + } break; + case Multiplayer::RPC_MODE_ANY: { + return true; + } break; + case Multiplayer::RPC_MODE_AUTHORITY: { + return !p_node->is_multiplayer_authority() && p_remote_id == p_node->get_multiplayer_authority(); + } break; + } + + return false; +} + +String RPCManager::get_rpc_md5(const Node *p_node) { + String rpc_list; + const Vector<Multiplayer::RPCConfig> node_config = p_node->get_node_rpc_methods(); + for (int i = 0; i < node_config.size(); i++) { + rpc_list += String(node_config[i].name); + } + if (p_node->get_script_instance()) { + const Vector<Multiplayer::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); + for (int i = 0; i < script_config.size(); i++) { + rpc_list += String(script_config[i].name); + } + } + return rpc_list.md5_text(); +} + +Node *RPCManager::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) { + Node *node = nullptr; + + if (p_node_target & 0x80000000) { + // Use full path (not cached yet). + int ofs = p_node_target & 0x7FFFFFFF; + + ERR_FAIL_COND_V_MSG(ofs >= p_packet_len, nullptr, "Invalid packet received. Size smaller than declared."); + + String paths; + paths.parse_utf8((const char *)&p_packet[ofs], p_packet_len - ofs); + + NodePath np = paths; + + node = multiplayer->get_root_node()->get_node(np); + + if (!node) { + ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); + } + return node; + } else { + // Use cached path. + return multiplayer->get_cached_node(p_from, p_node_target); + } +} + +void RPCManager::process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len) { + // Extract packet meta + int packet_min_size = 1; + int name_id_offset = 1; + ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); + // Compute the meta size, which depends on the compression level. + int node_id_compression = (p_packet[0] & NODE_ID_COMPRESSION_FLAG) >> NODE_ID_COMPRESSION_SHIFT; + int name_id_compression = (p_packet[0] & NAME_ID_COMPRESSION_FLAG) >> NAME_ID_COMPRESSION_SHIFT; + + switch (node_id_compression) { + case NETWORK_NODE_ID_COMPRESSION_8: + packet_min_size += 1; + name_id_offset += 1; + break; + case NETWORK_NODE_ID_COMPRESSION_16: + packet_min_size += 2; + name_id_offset += 2; + break; + case NETWORK_NODE_ID_COMPRESSION_32: + packet_min_size += 4; + name_id_offset += 4; + break; + default: + ERR_FAIL_MSG("Was not possible to extract the node id compression mode."); + } + switch (name_id_compression) { + case NETWORK_NAME_ID_COMPRESSION_8: + packet_min_size += 1; + break; + case NETWORK_NAME_ID_COMPRESSION_16: + packet_min_size += 2; + break; + default: + ERR_FAIL_MSG("Was not possible to extract the name id compression mode."); + } + ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); + + uint32_t node_target = 0; + switch (node_id_compression) { + case NETWORK_NODE_ID_COMPRESSION_8: + node_target = p_packet[1]; + break; + case NETWORK_NODE_ID_COMPRESSION_16: + node_target = decode_uint16(p_packet + 1); + break; + case NETWORK_NODE_ID_COMPRESSION_32: + node_target = decode_uint32(p_packet + 1); + break; + default: + // Unreachable, checked before. + CRASH_NOW(); + } + + Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len); + ERR_FAIL_COND_MSG(node == nullptr, "Invalid packet received. Requested node was not found."); + + uint16_t name_id = 0; + switch (name_id_compression) { + case NETWORK_NAME_ID_COMPRESSION_8: + name_id = p_packet[name_id_offset]; + break; + case NETWORK_NAME_ID_COMPRESSION_16: + name_id = decode_uint16(p_packet + name_id_offset); + break; + default: + // Unreachable, checked before. + CRASH_NOW(); + } + + const int packet_len = get_packet_len(node_target, p_packet_len); + _process_rpc(node, name_id, p_from, p_packet, packet_len, packet_min_size); +} + +void RPCManager::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) { + ERR_FAIL_COND_MSG(p_offset > p_packet_len, "Invalid packet received. Size too small."); + + // Check that remote can call the RPC on this node. + const Multiplayer::RPCConfig config = _get_rpc_config_by_id(p_node, p_rpc_method_id); + ERR_FAIL_COND(config.name == StringName()); + + bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from); + ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_multiplayer_authority()) + "."); + + int argc = 0; + bool byte_only = false; + + const bool byte_only_or_no_args = p_packet[0] & BYTE_ONLY_OR_NO_ARGS_FLAG; + if (byte_only_or_no_args) { + if (p_offset < p_packet_len) { + // This packet contains only bytes. + argc = 1; + byte_only = true; + } else { + // This rpc calls a method without parameters. + } + } else { + // Normal variant, takes the argument count from the packet. + ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); + argc = p_packet[p_offset]; + p_offset += 1; + } + + Vector<Variant> args; + Vector<const Variant *> argp; + args.resize(argc); + argp.resize(argc); + +#ifdef DEBUG_ENABLED + _profile_node_data("in_rpc", p_node->get_instance_id()); +#endif + + if (byte_only) { + Vector<uint8_t> pure_data; + const int len = p_packet_len - p_offset; + pure_data.resize(len); + memcpy(pure_data.ptrw(), &p_packet[p_offset], len); + args.write[0] = pure_data; + argp.write[0] = &args[0]; + p_offset += len; + } else { + for (int i = 0; i < argc; i++) { + ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); + + int vlen; + Error err = multiplayer->decode_and_decompress_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen); + ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RPC argument."); + + argp.write[i] = &args[i]; + p_offset += vlen; + } + } + + Callable::CallError ce; + + p_node->call(config.name, (const Variant **)argp.ptr(), argc, ce); + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, config.name, (const Variant **)argp.ptr(), argc, ce); + error = "RPC - " + error; + ERR_PRINT(error); + } +} + +void RPCManager::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Multiplayer::RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) { + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND_MSG(peer.is_null(), "Attempt to call RPC without active multiplayer peer."); + + ERR_FAIL_COND_MSG(peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTING, "Attempt to call RPC while multiplayer peer is not connected yet."); + + ERR_FAIL_COND_MSG(peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, "Attempt to call RPC while multiplayer peer is disconnected."); + + ERR_FAIL_COND_MSG(p_argcount > 255, "Too many arguments (>255)."); + + if (p_to != 0 && !multiplayer->get_connected_peers().has(ABS(p_to))) { + ERR_FAIL_COND_MSG(p_to == peer->get_unique_id(), "Attempt to call RPC on yourself! Peer unique ID: " + itos(peer->get_unique_id()) + "."); + + ERR_FAIL_MSG("Attempt to call RPC with unknown peer ID: " + itos(p_to) + "."); + } + + NodePath from_path = (multiplayer->get_root_node()->get_path()).rel_path_to(p_from->get_path()); + ERR_FAIL_COND_MSG(from_path.is_empty(), "Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE!"); + + // See if all peers have cached path (if so, call can be fast). + int psc_id; + const bool has_all_peers = multiplayer->send_confirm_path(p_from, from_path, p_to, psc_id); + + // Create base packet, lots of hardcode because it must be tight. + + int ofs = 0; + +#define MAKE_ROOM(m_amount) \ + if (packet_cache.size() < m_amount) \ + packet_cache.resize(m_amount); + + // Encode meta. + uint8_t command_type = MultiplayerAPI::NETWORK_COMMAND_REMOTE_CALL; + uint8_t node_id_compression = UINT8_MAX; + uint8_t name_id_compression = UINT8_MAX; + bool byte_only_or_no_args = false; + + MAKE_ROOM(1); + // The meta is composed along the way, so just set 0 for now. + packet_cache.write[0] = 0; + ofs += 1; + + // Encode Node ID. + if (has_all_peers) { + // Compress the node ID only if all the target peers already know it. + if (psc_id >= 0 && psc_id <= 255) { + // We can encode the id in 1 byte + node_id_compression = NETWORK_NODE_ID_COMPRESSION_8; + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = static_cast<uint8_t>(psc_id); + ofs += 1; + } else if (psc_id >= 0 && psc_id <= 65535) { + // We can encode the id in 2 bytes + node_id_compression = NETWORK_NODE_ID_COMPRESSION_16; + MAKE_ROOM(ofs + 2); + encode_uint16(static_cast<uint16_t>(psc_id), &(packet_cache.write[ofs])); + ofs += 2; + } else { + // Too big, let's use 4 bytes. + node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; + MAKE_ROOM(ofs + 4); + encode_uint32(psc_id, &(packet_cache.write[ofs])); + ofs += 4; + } + } else { + // The targets don't know the node yet, so we need to use 32 bits int. + node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; + MAKE_ROOM(ofs + 4); + encode_uint32(psc_id, &(packet_cache.write[ofs])); + ofs += 4; + } + + // Encode method ID + if (p_rpc_id <= UINT8_MAX) { + // The ID fits in 1 byte + name_id_compression = NETWORK_NAME_ID_COMPRESSION_8; + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = static_cast<uint8_t>(p_rpc_id); + ofs += 1; + } else { + // The ID is larger, let's use 2 bytes + name_id_compression = NETWORK_NAME_ID_COMPRESSION_16; + MAKE_ROOM(ofs + 2); + encode_uint16(p_rpc_id, &(packet_cache.write[ofs])); + ofs += 2; + } + + if (p_argcount == 0) { + byte_only_or_no_args = true; + } else if (p_argcount == 1 && p_arg[0]->get_type() == Variant::PACKED_BYTE_ARRAY) { + byte_only_or_no_args = true; + // Special optimization when only the byte vector is sent. + const Vector<uint8_t> data = *p_arg[0]; + MAKE_ROOM(ofs + data.size()); + memcpy(&(packet_cache.write[ofs]), data.ptr(), sizeof(uint8_t) * data.size()); + ofs += data.size(); + } else { + // Arguments + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = p_argcount; + ofs += 1; + for (int i = 0; i < p_argcount; i++) { + int len(0); + Error err = multiplayer->encode_and_compress_variant(*p_arg[i], nullptr, len); + ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC argument. THIS IS LIKELY A BUG IN THE ENGINE!"); + MAKE_ROOM(ofs + len); + multiplayer->encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len); + ofs += len; + } + } + + ERR_FAIL_COND(command_type > 7); + ERR_FAIL_COND(node_id_compression > 3); + ERR_FAIL_COND(name_id_compression > 1); + + // We can now set the meta + packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + (byte_only_or_no_args ? BYTE_ONLY_OR_NO_ARGS_FLAG : 0); + +#ifdef DEBUG_ENABLED + multiplayer->profile_bandwidth("out", ofs); +#endif + + // Take chance and set transfer mode, since all send methods will use it. + peer->set_transfer_channel(p_config.channel); + peer->set_transfer_mode(p_config.transfer_mode); + + if (has_all_peers) { + // They all have verified paths, so send fast. + peer->set_target_peer(p_to); // To all of you. + peer->put_packet(packet_cache.ptr(), ofs); // A message with love. + } else { + // Unreachable because the node ID is never compressed if the peers doesn't know it. + CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32); + + // Not all verified path, so send one by one. + + // Append path at the end, since we will need it for some packets. + CharString pname = String(from_path).utf8(); + int path_len = encode_cstring(pname.get_data(), nullptr); + MAKE_ROOM(ofs + path_len); + encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); + + for (const int &P : multiplayer->get_connected_peers()) { + if (p_to < 0 && P == -p_to) { + continue; // Continue, excluded. + } + + if (p_to > 0 && P != p_to) { + continue; // Continue, not for this peer. + } + + bool confirmed = multiplayer->is_cache_confirmed(from_path, P); + + peer->set_target_peer(P); // To this one specifically. + + if (confirmed) { + // This one confirmed path, so use id. + encode_uint32(psc_id, &(packet_cache.write[1])); + peer->put_packet(packet_cache.ptr(), ofs); + } else { + // This one did not confirm path yet, so use entire path (sorry!). + encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag. + peer->put_packet(packet_cache.ptr(), ofs + path_len); + } + } + } +} + +void RPCManager::rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND_MSG(!peer.is_valid(), "Trying to call an RPC while no multiplayer peer is active."); + ERR_FAIL_COND_MSG(!p_node->is_inside_tree(), "Trying to call an RPC on a node which is not inside SceneTree."); + ERR_FAIL_COND_MSG(peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, "Trying to call an RPC via a multiplayer peer which is not connected."); + + int node_id = peer->get_unique_id(); + bool call_local_native = false; + bool call_local_script = false; + uint16_t rpc_id = UINT16_MAX; + const Multiplayer::RPCConfig config = _get_rpc_config(p_node, p_method, rpc_id); + ERR_FAIL_COND_MSG(config.name == StringName(), + vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, p_node->get_path())); + if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { + if (rpc_id & (1 << 15)) { + call_local_native = config.sync; + } else { + call_local_script = config.sync; + } + } + + if (p_peer_id != node_id) { +#ifdef DEBUG_ENABLED + _profile_node_data("out_rpc", p_node->get_instance_id()); +#endif + + _send_rpc(p_node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); + } + + if (call_local_native) { + Callable::CallError ce; + + multiplayer->set_remote_sender_override(peer->get_unique_id()); + p_node->call(p_method, p_arg, p_argcount, ce); + multiplayer->set_remote_sender_override(0); + + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in local call: - " + error + "."; + ERR_PRINT(error); + return; + } + } + + if (call_local_script) { + Callable::CallError ce; + ce.error = Callable::CallError::CALL_OK; + + multiplayer->set_remote_sender_override(peer->get_unique_id()); + p_node->get_script_instance()->call(p_method, p_arg, p_argcount, ce); + multiplayer->set_remote_sender_override(0); + + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in script local call: - " + error + "."; + ERR_PRINT(error); + return; + } + } + + ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.sync, "RPC '" + p_method + "' on yourself is not allowed by selected mode."); +} diff --git a/core/multiplayer/rpc_manager.h b/core/multiplayer/rpc_manager.h new file mode 100644 index 0000000000..7b99dee226 --- /dev/null +++ b/core/multiplayer/rpc_manager.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* rpc_manager.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 MULTIPLAYER_RPC_H +#define MULTIPLAYER_RPC_H + +#include "core/multiplayer/multiplayer.h" +#include "core/multiplayer/multiplayer_api.h" +#include "core/object/ref_counted.h" + +class RPCManager : public RefCounted { + GDCLASS(RPCManager, RefCounted); + +private: + enum NetworkNodeIdCompression { + NETWORK_NODE_ID_COMPRESSION_8 = 0, + NETWORK_NODE_ID_COMPRESSION_16, + NETWORK_NODE_ID_COMPRESSION_32, + }; + + enum NetworkNameIdCompression { + NETWORK_NAME_ID_COMPRESSION_8 = 0, + NETWORK_NAME_ID_COMPRESSION_16, + }; + + // The RPC meta is composed by a single byte that contains (starting from the least significant bit): + // - `NetworkCommands` in the first four bits. + // - `NetworkNodeIdCompression` in the next 2 bits. + // - `NetworkNameIdCompression` in the next 1 bit. + // - `byte_only_or_no_args` in the next 1 bit. + enum { + NODE_ID_COMPRESSION_SHIFT = MultiplayerAPI::CMD_FLAG_0_SHIFT, // 2 bits for this. + NAME_ID_COMPRESSION_SHIFT = MultiplayerAPI::CMD_FLAG_2_SHIFT, + BYTE_ONLY_OR_NO_ARGS_SHIFT = MultiplayerAPI::CMD_FLAG_3_SHIFT, + }; + + enum { + NODE_ID_COMPRESSION_FLAG = (1 << NODE_ID_COMPRESSION_SHIFT) | (1 << (NODE_ID_COMPRESSION_SHIFT + 1)), // 2 bits for this. + NAME_ID_COMPRESSION_FLAG = (1 << NAME_ID_COMPRESSION_SHIFT), + BYTE_ONLY_OR_NO_ARGS_FLAG = (1 << BYTE_ONLY_OR_NO_ARGS_SHIFT), + }; + + MultiplayerAPI *multiplayer = nullptr; + Vector<uint8_t> packet_cache; + +protected: + _FORCE_INLINE_ void _profile_node_data(const String &p_what, ObjectID p_id); + void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); + + void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Multiplayer::RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount); + Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len); + +public: + // Called by Node.rpc + void rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount); + void process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len); + + String get_rpc_md5(const Node *p_node); + RPCManager(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; } +}; + +#endif // MULTIPLAYER_RPC_H diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index b29b2bd421..8e92340c1e 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -892,6 +892,32 @@ void ClassDB::get_enum_constants(const StringName &p_class, const StringName &p_ } } +void ClassDB::set_method_error_return_values(const StringName &p_class, const StringName &p_method, const Vector<Error> &p_values) { + OBJTYPE_RLOCK; +#ifdef DEBUG_METHODS_ENABLED + ClassInfo *type = classes.getptr(p_class); + + ERR_FAIL_COND(!type); + + type->method_error_values[p_method] = p_values; +#endif +} + +Vector<Error> ClassDB::get_method_error_return_values(const StringName &p_class, const StringName &p_method) { +#ifdef DEBUG_METHODS_ENABLED + ClassInfo *type = classes.getptr(p_class); + + ERR_FAIL_COND_V(!type, Vector<Error>()); + + if (!type->method_error_values.has(p_method)) { + return Vector<Error>(); + } + return type->method_error_values[p_method]; +#else + return Vector<Error>(); +#endif +} + bool ClassDB::has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) { OBJTYPE_RLOCK; @@ -1002,6 +1028,18 @@ void ClassDB::add_property_subgroup(const StringName &p_class, const String &p_n type->property_list.push_back(PropertyInfo(Variant::NIL, p_name, PROPERTY_HINT_NONE, p_prefix, PROPERTY_USAGE_SUBGROUP)); } +void ClassDB::add_property_array_count(const StringName &p_class, const String &p_label, const StringName &p_count_property, const StringName &p_count_setter, const StringName &p_count_getter, const String &p_array_element_prefix, uint32_t p_count_usage) { + add_property(p_class, PropertyInfo(Variant::INT, p_count_property, PROPERTY_HINT_NONE, "", p_count_usage | PROPERTY_USAGE_ARRAY, vformat("%s,%s", p_label, p_array_element_prefix)), p_count_setter, p_count_getter); +} + +void ClassDB::add_property_array(const StringName &p_class, const StringName &p_path, const String &p_array_element_prefix) { + OBJTYPE_WLOCK; + ClassInfo *type = classes.getptr(p_class); + ERR_FAIL_COND(!type); + + type->property_list.push_back(PropertyInfo(Variant::NIL, p_path, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, p_array_element_prefix)); +} + // NOTE: For implementation simplicity reasons, this method doesn't allow setters to have optional arguments at the end. void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index) { lock.read_lock(); diff --git a/core/object/class_db.h b/core/object/class_db.h index 39e523da27..e89c7fffd7 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -132,6 +132,7 @@ public: List<MethodInfo> virtual_methods; Map<StringName, MethodInfo> virtual_methods_map; StringName category; + Map<StringName, Vector<Error>> method_error_values; #endif HashMap<StringName, PropertySetGet> property_setget; @@ -352,6 +353,8 @@ public: static void add_property_group(const StringName &p_class, const String &p_name, const String &p_prefix = ""); static void add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_prefix = ""); + static void add_property_array_count(const StringName &p_class, const String &p_label, const StringName &p_count_property, const StringName &p_count_setter, const StringName &p_count_getter, const String &p_array_element_prefix, uint32_t p_count_usage = PROPERTY_USAGE_EDITOR); + static void add_property_array(const StringName &p_class, const StringName &p_path, const String &p_array_element_prefix); static void add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index = -1); static void set_property_default_value(const StringName &p_class, const StringName &p_name, const Variant &p_default); static void add_linked_property(const StringName &p_class, const String &p_property, const String &p_linked_property); @@ -385,6 +388,8 @@ public: static void get_enum_constants(const StringName &p_class, const StringName &p_enum, List<StringName> *p_constants, bool p_no_inheritance = false); static bool has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false); + static void set_method_error_return_values(const StringName &p_class, const StringName &p_method, const Vector<Error> &p_values); + static Vector<Error> get_method_error_return_values(const StringName &p_class, const StringName &p_method); static Variant class_get_default_property_value(const StringName &p_class, const StringName &p_property, bool *r_valid = nullptr); static StringName get_category(const StringName &p_node); @@ -415,6 +420,29 @@ public: #define BIND_ENUM_CONSTANT(m_constant) \ ::ClassDB::bind_integer_constant(get_class_static(), __constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant); +_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr) { +} + +_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr, const Error &p_err) { + arr.push_back(p_err); +} + +template <class... P> +_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr, const Error &p_err, P... p_args) { + arr.push_back(p_err); + errarray_add_str(arr, p_args...); +} + +template <class... P> +_FORCE_INLINE_ Vector<Error> errarray(P... p_args) { + Vector<Error> arr; + errarray_add_str(arr, p_args...); + return arr; +} + +#define BIND_METHOD_ERR_RETURN_DOC(m_method, ...) \ + ::ClassDB::set_method_error_return_values(get_class_static(), m_method, errarray(__VA_ARGS__)); + #else #define BIND_CONSTANT(m_constant) \ @@ -423,16 +451,7 @@ public: #define BIND_ENUM_CONSTANT(m_constant) \ ::ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); -#endif - -#ifdef TOOLS_ENABLED - -#define BIND_VMETHOD(m_method) \ - ::ClassDB::add_virtual_method(get_class_static(), m_method); - -#else - -#define BIND_VMETHOD(m_method) +#define BIND_METHOD_ERR_RETURN_DOC(m_method, ...) #endif diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index af90593140..86c2891e5d 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -2,7 +2,7 @@ proto = """ #define GDVIRTUAL$VER($RET m_name $ARG) \\ StringName _gdvirtual_##m_name##_sn = #m_name;\\ GDNativeExtensionClassCallVirtual _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, #m_name) : (GDNativeExtensionClassCallVirtual) nullptr;\\ -bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ +_FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ ScriptInstance *script_instance = ((Object*)(this))->get_script_instance();\\ if (script_instance) {\\ Callable::CallError ce; \\ @@ -23,7 +23,7 @@ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ \\ return false;\\ }\\ -bool _gdvirtual_##m_name##_overriden() const { \\ +_FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const { \\ ScriptInstance *script_instance = ((Object*)(this))->get_script_instance();\\ if (script_instance) {\\ return script_instance->has_method(_gdvirtual_##m_name##_sn);\\ @@ -42,7 +42,6 @@ _FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() { \\ return method_info;\\ } - """ diff --git a/core/object/object.h b/core/object/object.h index aede48343c..a44d921bff 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -97,6 +97,7 @@ enum PropertyHint { PROPERTY_HINT_SAVE_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,". This opens a save dialog PROPERTY_HINT_INT_IS_OBJECTID, PROPERTY_HINT_ARRAY_TYPE, + PROPERTY_HINT_INT_IS_POINTER, PROPERTY_HINT_MAX, // When updating PropertyHint, also sync the hardcoded list in VisualScriptEditorVariableEdit }; @@ -131,6 +132,8 @@ enum PropertyUsageFlags { PROPERTY_USAGE_DEFERRED_SET_RESOURCE = 1 << 26, // when loading, the resource for this property can be set at the end of loading PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT = 1 << 27, // For Object properties, instantiate them when creating in editor. PROPERTY_USAGE_EDITOR_BASIC_SETTING = 1 << 28, //for project or editor settings, show when basic settings are selected + PROPERTY_USAGE_READ_ONLY = 1 << 29, // Mark a property as read-only in the inspector. + PROPERTY_USAGE_ARRAY = 1 << 30, // Used in the inspector to group properties as elements of an array. PROPERTY_USAGE_DEFAULT = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_NETWORK, PROPERTY_USAGE_DEFAULT_INTL = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_NETWORK | PROPERTY_USAGE_INTERNATIONALIZED, @@ -145,6 +148,10 @@ enum PropertyUsageFlags { #define ADD_SUBGROUP(m_name, m_prefix) ::ClassDB::add_property_subgroup(get_class_static(), m_name, m_prefix) #define ADD_LINKED_PROPERTY(m_property, m_linked_property) ::ClassDB::add_linked_property(get_class_static(), m_property, m_linked_property) +#define ADD_ARRAY_COUNT(m_label, m_count_property, m_count_property_setter, m_count_property_getter, m_prefix) ClassDB::add_property_array_count(get_class_static(), m_label, m_count_property, _scs_create(m_count_property_setter), _scs_create(m_count_property_getter), m_prefix) +#define ADD_ARRAY_COUNT_WITH_USAGE_FLAGS(m_label, m_count_property, m_count_property_setter, m_count_property_getter, m_prefix, m_property_usage_flags) ClassDB::add_property_array_count(get_class_static(), m_label, m_count_property, _scs_create(m_count_property_setter), _scs_create(m_count_property_getter), m_prefix, m_property_usage_flags) +#define ADD_ARRAY(m_array_path, m_prefix) ClassDB::add_property_array(get_class_static(), m_array_path, m_prefix) + struct PropertyInfo { Variant::Type type = Variant::NIL; String name; @@ -288,8 +295,8 @@ struct ObjectNativeExtension { #else #define GDVIRTUAL_BIND(m_name, ...) #endif -#define GDVIRTUAL_IS_OVERRIDEN(m_name) _gdvirtual_##m_name##_overriden() -#define GDVIRTUAL_IS_OVERRIDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overriden() +#define GDVIRTUAL_IS_OVERRIDDEN(m_name) _gdvirtual_##m_name##_overridden() +#define GDVIRTUAL_IS_OVERRIDDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overridden() /* the following is an incomprehensible blob of hacks and workarounds to compensate for many of the fallencies in C++. As a plus, this macro pretty much alone defines the object model. diff --git a/core/object/script_language.h b/core/object/script_language.h index 385bf79c1a..8d76cbf479 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -32,8 +32,8 @@ #define SCRIPT_LANGUAGE_H #include "core/doc_data.h" -#include "core/io/multiplayer_api.h" #include "core/io/resource.h" +#include "core/multiplayer/multiplayer.h" #include "core/templates/map.h" #include "core/templates/pair.h" @@ -159,7 +159,7 @@ public: virtual bool is_placeholder_fallback_enabled() const { return false; } - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const = 0; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const = 0; Script() {} }; @@ -200,7 +200,7 @@ public: virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid); virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const = 0; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const = 0; virtual ScriptLanguage *get_language() = 0; virtual ~ScriptInstance(); @@ -419,7 +419,7 @@ public: virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid = nullptr); virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid = nullptr); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const { return Vector<MultiplayerAPI::RPCConfig>(); } + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const { return Vector<Multiplayer::RPCConfig>(); } PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner); ~PlaceHolderScriptInstance(); diff --git a/core/os/os.cpp b/core/os/os.cpp index 63390919f4..89ba73b35e 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -357,9 +357,17 @@ void OS::set_has_server_feature_callback(HasServerFeatureCallback p_callback) { } bool OS::has_feature(const String &p_feature) { - if (p_feature == get_name()) { + // Feature tags are always lowercase for consistency. + if (p_feature == get_name().to_lower()) { return true; } + + // Catch-all `linuxbsd` feature tag that matches on both Linux and BSD. + // This is the one exposed in the project settings dialog. + if (p_feature == "linuxbsd" && (get_name() == "Linux" || get_name() == "FreeBSD" || get_name() == "NetBSD" || get_name() == "OpenBSD" || get_name() == "BSD")) { + return true; + } + #ifdef DEBUG_ENABLED if (p_feature == "debug") { return true; diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index e2a097f883..3a037f9dd1 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -41,15 +41,13 @@ #include "core/extension/native_extension_manager.h" #include "core/input/input.h" #include "core/input/input_map.h" +#include "core/input/shortcut.h" #include "core/io/config_file.h" #include "core/io/dtls_server.h" #include "core/io/http_client.h" #include "core/io/image_loader.h" #include "core/io/json.h" #include "core/io/marshalls.h" -#include "core/io/multiplayer_api.h" -#include "core/io/multiplayer_peer.h" -#include "core/io/multiplayer_replicator.h" #include "core/io/packed_data_container.h" #include "core/io/packet_peer.h" #include "core/io/packet_peer_dtls.h" @@ -69,6 +67,9 @@ #include "core/math/geometry_3d.h" #include "core/math/random_number_generator.h" #include "core/math/triangle_mesh.h" +#include "core/multiplayer/multiplayer_api.h" +#include "core/multiplayer/multiplayer_peer.h" +#include "core/multiplayer/multiplayer_replicator.h" #include "core/object/class_db.h" #include "core/object/undo_redo.h" #include "core/os/main_loop.h" @@ -145,10 +146,12 @@ void register_core_types() { GDREGISTER_CLASS(Resource); GDREGISTER_CLASS(Image); + GDREGISTER_CLASS(Shortcut); GDREGISTER_VIRTUAL_CLASS(InputEvent); GDREGISTER_VIRTUAL_CLASS(InputEventWithModifiers); GDREGISTER_VIRTUAL_CLASS(InputEventFromWindow); GDREGISTER_CLASS(InputEventKey); + GDREGISTER_CLASS(InputEventShortcut); GDREGISTER_VIRTUAL_CLASS(InputEventMouse); GDREGISTER_CLASS(InputEventMouseButton); GDREGISTER_CLASS(InputEventMouseMotion); @@ -305,13 +308,7 @@ void register_core_singletons() { void register_core_extensions() { // Hardcoded for now. NativeExtension::initialize_native_extensions(); - if (ProjectSettings::get_singleton()->has_setting("native_extensions/paths")) { - Vector<String> paths = ProjectSettings::get_singleton()->get("native_extensions/paths"); - for (int i = 0; i < paths.size(); i++) { - NativeExtensionManager::LoadStatus status = native_extension_manager->load_extension(paths[i]); - ERR_CONTINUE_MSG(status != NativeExtensionManager::LOAD_STATUS_OK, "Error loading extension: " + paths[i]); - } - } + native_extension_manager->load_extensions(); native_extension_manager->initialize_extensions(NativeExtension::INITIALIZATION_LEVEL_CORE); } diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index a30d6b9102..ed6fd13cc8 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -1653,13 +1653,13 @@ String String::num_scientific(double p_num) { #if defined(__GNUC__) || defined(_MSC_VER) -#if (defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER < 1900)) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) - // MinGW and old MSC require _set_output_format() to conform to C99 output for printf +#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) + // MinGW requires _set_output_format() to conform to C99 output for printf unsigned int old_exponent_format = _set_output_format(_TWO_DIGIT_EXPONENT); #endif snprintf(buf, 256, "%lg", p_num); -#if (defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER < 1900)) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) +#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) _set_output_format(old_exponent_format); #endif @@ -4324,7 +4324,7 @@ bool String::is_resource_file() const { return begins_with("res://") && find("::") == -1; } -bool String::is_rel_path() const { +bool String::is_relative_path() const { return !is_absolute_path(); } diff --git a/core/string/ustring.h b/core/string/ustring.h index ffb354d6e1..24da6b82af 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -398,7 +398,7 @@ public: // path functions bool is_absolute_path() const; - bool is_rel_path() const; + bool is_relative_path() const; bool is_resource_file() const; String path_to(const String &p_path) const; String path_to_file(const String &p_path) const; @@ -523,10 +523,10 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St #define TTRGET(m_value) TTR(m_value) #else -#define TTR(m_value) (String()) -#define TTRN(m_value) (String()) -#define DTR(m_value) (String()) -#define DTRN(m_value) (String()) +#define TTR(m_value) String() +#define TTRN(m_value) String() +#define DTR(m_value) String() +#define DTRN(m_value) String() #define TTRC(m_value) (m_value) #define TTRGET(m_value) (m_value) #endif diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h index 668ec513d6..5704b8f230 100644 --- a/core/templates/local_vector.h +++ b/core/templates/local_vector.h @@ -170,7 +170,7 @@ public: push_back(p_val); } else { resize(count + 1); - for (U i = count; i > p_pos; i--) { + for (U i = count - 1; i > p_pos; i--) { data[i] = data[i - 1]; } data[p_pos] = p_val; diff --git a/core/templates/map.h b/core/templates/map.h index a47547d355..badb407e5d 100644 --- a/core/templates/map.h +++ b/core/templates/map.h @@ -36,7 +36,7 @@ #include "core/templates/pair.h" // based on the very nice implementation of rb-trees by: -// https://web.archive.org/web/20120507164830/http://web.mit.edu/~emin/www/source_code/red_black_tree/index.html +// https://web.archive.org/web/20120507164830/https://web.mit.edu/~emin/www/source_code/red_black_tree/index.html template <class K, class V, class C = Comparator<K>, class A = DefaultAllocator> class Map { diff --git a/core/templates/rid_owner.h b/core/templates/rid_owner.h index 8d139551ef..e947d2211c 100644 --- a/core/templates/rid_owner.h +++ b/core/templates/rid_owner.h @@ -53,14 +53,16 @@ protected: return rid; } - static uint64_t _gen_id() { - return base_id.increment(); - } - static RID _gen_rid() { return _make_from_id(_gen_id()); } + friend struct VariantUtilityFunctions; + + static uint64_t _gen_id() { + return base_id.increment(); + } + public: virtual ~RID_AllocBase() {} }; diff --git a/core/templates/safe_list.h b/core/templates/safe_list.h new file mode 100644 index 0000000000..d8f010663b --- /dev/null +++ b/core/templates/safe_list.h @@ -0,0 +1,375 @@ +/*************************************************************************/ +/* safe_list.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 SAFE_LIST_H +#define SAFE_LIST_H + +#include "core/os/memory.h" +#include "core/typedefs.h" +#include <functional> + +#if !defined(NO_THREADS) + +#include <atomic> +#include <type_traits> + +// Design goals for these classes: +// - Accessing this list with an iterator will never result in a use-after free, +// even if the element being accessed has been logically removed from the list on +// another thread. +// - Logical deletion from the list will not result in deallocation at that time, +// instead the node will be deallocated at a later time when it is safe to do so. +// - No blocking synchronization primitives will be used. + +// This is used in very specific areas of the engine where it's critical that these guarantees are held. + +template <class T, class A = DefaultAllocator> +class SafeList { + struct SafeListNode { + std::atomic<SafeListNode *> next = nullptr; + + // If the node is logically deleted, this pointer will typically point + // to the previous list item in time that was also logically deleted. + std::atomic<SafeListNode *> graveyard_next = nullptr; + + std::function<void(T)> deletion_fn = [](T t) { return; }; + + T val; + }; + + static_assert(std::atomic<T>::is_always_lock_free); + + std::atomic<SafeListNode *> head = nullptr; + std::atomic<SafeListNode *> graveyard_head = nullptr; + + std::atomic_uint active_iterator_count = 0; + +public: + class Iterator { + friend class SafeList; + + SafeListNode *cursor; + SafeList *list; + + Iterator(SafeListNode *p_cursor, SafeList *p_list) : + cursor(p_cursor), list(p_list) { + list->active_iterator_count++; + } + + public: + Iterator(const Iterator &p_other) : + cursor(p_other.cursor), list(p_other.list) { + list->active_iterator_count++; + } + + ~Iterator() { + list->active_iterator_count--; + } + + public: + T &operator*() { + return cursor->val; + } + + Iterator &operator++() { + cursor = cursor->next; + return *this; + } + + // These two operators are mostly useful for comparisons to nullptr. + bool operator==(const void *p_other) const { + return cursor == p_other; + } + + bool operator!=(const void *p_other) const { + return cursor != p_other; + } + + // These two allow easy range-based for loops. + bool operator==(const Iterator &p_other) const { + return cursor == p_other.cursor; + } + + bool operator!=(const Iterator &p_other) const { + return cursor != p_other.cursor; + } + }; + +public: + // Calling this will cause an allocation. + void insert(T p_value) { + SafeListNode *new_node = memnew_allocator(SafeListNode, A); + new_node->val = p_value; + SafeListNode *expected_head = nullptr; + do { + expected_head = head.load(); + new_node->next.store(expected_head); + } while (!head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ new_node)); + } + + Iterator find(T p_value) { + for (Iterator it = begin(); it != end(); ++it) { + if (*it == p_value) { + return it; + } + } + return end(); + } + + void erase(T p_value, std::function<void(T)> p_deletion_fn) { + Iterator tmp = find(p_value); + erase(tmp, p_deletion_fn); + } + + void erase(T p_value) { + Iterator tmp = find(p_value); + erase(tmp, [](T t) { return; }); + } + + void erase(Iterator &p_iterator, std::function<void(T)> p_deletion_fn) { + p_iterator.cursor->deletion_fn = p_deletion_fn; + erase(p_iterator); + } + + void erase(Iterator &p_iterator) { + if (find(p_iterator.cursor->val) == nullptr) { + // Not in the list, nothing to do. + return; + } + // First, remove the node from the list. + while (true) { + Iterator prev = begin(); + SafeListNode *expected_head = prev.cursor; + for (; prev != end(); ++prev) { + if (prev.cursor && prev.cursor->next == p_iterator.cursor) { + break; + } + } + if (prev != end()) { + // There exists a node before this. + prev.cursor->next.store(p_iterator.cursor->next.load()); + // Done. + break; + } else { + if (head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ p_iterator.cursor->next.load())) { + // Successfully reassigned the head pointer before another thread changed it to something else. + break; + } + // Fall through upon failure, try again. + } + } + // Then queue it for deletion by putting it in the node graveyard. + // Don't touch `next` because an iterator might still be pointing at this node. + SafeListNode *expected_head = nullptr; + do { + expected_head = graveyard_head.load(); + p_iterator.cursor->graveyard_next.store(expected_head); + } while (!graveyard_head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ p_iterator.cursor)); + } + + Iterator begin() { + return Iterator(head.load(), this); + } + + Iterator end() { + return Iterator(nullptr, this); + } + + // Calling this will cause zero to many deallocations. + void maybe_cleanup() { + SafeListNode *cursor = nullptr; + SafeListNode *new_graveyard_head = nullptr; + do { + // The access order here is theoretically important. + cursor = graveyard_head.load(); + if (active_iterator_count.load() != 0) { + // It's not safe to clean up with an active iterator, because that iterator + // could be pointing to an element that we want to delete. + return; + } + // Any iterator created after this point will never point to a deleted node. + // Swap it out with the current graveyard head. + } while (!graveyard_head.compare_exchange_strong(/* expected= */ cursor, /* new= */ new_graveyard_head)); + // Our graveyard list is now unreachable by any active iterators, + // detached from the main graveyard head and ready for deletion. + while (cursor) { + SafeListNode *tmp = cursor; + cursor = cursor->graveyard_next; + tmp->deletion_fn(tmp->val); + memdelete_allocator<SafeListNode, A>(tmp); + } + } +}; + +#else // NO_THREADS + +// Effectively the same structure without the atomics. It's probably possible to simplify it but the semantics shouldn't differ greatly. +template <class T, class A = DefaultAllocator> +class SafeList { + struct SafeListNode { + SafeListNode *next = nullptr; + + // If the node is logically deleted, this pointer will typically point to the previous list item in time that was also logically deleted. + SafeListNode *graveyard_next = nullptr; + + std::function<void(T)> deletion_fn = [](T t) { return; }; + + T val; + }; + + SafeListNode *head = nullptr; + SafeListNode *graveyard_head = nullptr; + + unsigned int active_iterator_count = 0; + +public: + class Iterator { + friend class SafeList; + + SafeListNode *cursor; + SafeList *list; + + public: + Iterator(SafeListNode *p_cursor, SafeList *p_list) : + cursor(p_cursor), list(p_list) { + list->active_iterator_count++; + } + + ~Iterator() { + list->active_iterator_count--; + } + + T &operator*() { + return cursor->val; + } + + Iterator &operator++() { + cursor = cursor->next; + return *this; + } + + // These two operators are mostly useful for comparisons to nullptr. + bool operator==(const void *p_other) const { + return cursor == p_other; + } + + bool operator!=(const void *p_other) const { + return cursor != p_other; + } + + // These two allow easy range-based for loops. + bool operator==(const Iterator &p_other) const { + return cursor == p_other.cursor; + } + + bool operator!=(const Iterator &p_other) const { + return cursor != p_other.cursor; + } + }; + +public: + // Calling this will cause an allocation. + void insert(T p_value) { + SafeListNode *new_node = memnew_allocator(SafeListNode, A); + new_node->val = p_value; + new_node->next = head; + head = new_node; + } + + Iterator find(T p_value) { + for (Iterator it = begin(); it != end(); ++it) { + if (*it == p_value) { + return it; + } + } + return end(); + } + + void erase(T p_value, std::function<void(T)> p_deletion_fn) { + erase(find(p_value), p_deletion_fn); + } + + void erase(T p_value) { + erase(find(p_value), [](T t) { return; }); + } + + void erase(Iterator p_iterator, std::function<void(T)> p_deletion_fn) { + p_iterator.cursor->deletion_fn = p_deletion_fn; + erase(p_iterator); + } + + void erase(Iterator p_iterator) { + Iterator prev = begin(); + for (; prev != end(); ++prev) { + if (prev.cursor && prev.cursor->next == p_iterator.cursor) { + break; + } + } + if (prev == end()) { + // Not in the list, nothing to do. + return; + } + // First, remove the node from the list. + prev.cursor->next = p_iterator.cursor->next; + + // Then queue it for deletion by putting it in the node graveyard. Don't touch `next` because an iterator might still be pointing at this node. + p_iterator.cursor->graveyard_next = graveyard_head; + graveyard_head = p_iterator.cursor; + } + + Iterator begin() { + return Iterator(head, this); + } + + Iterator end() { + return Iterator(nullptr, this); + } + + // Calling this will cause zero to many deallocations. + void maybe_cleanup() { + SafeListNode *cursor = graveyard_head; + if (active_iterator_count != 0) { + // It's not safe to clean up with an active iterator, because that iterator could be pointing to an element that we want to delete. + return; + } + graveyard_head = nullptr; + // Our graveyard list is now unreachable by any active iterators, detached from the main graveyard head and ready for deletion. + while (cursor) { + SafeListNode *tmp = cursor; + cursor = cursor->next; + tmp->deletion_fn(tmp->val); + memdelete_allocator<SafeListNode, A>(tmp); + } + } +}; + +#endif + +#endif // SAFE_LIST_H diff --git a/core/templates/set.h b/core/templates/set.h index 9261d2d3d2..0a80ceefb5 100644 --- a/core/templates/set.h +++ b/core/templates/set.h @@ -35,7 +35,7 @@ #include "core/typedefs.h" // based on the very nice implementation of rb-trees by: -// https://web.archive.org/web/20120507164830/http://web.mit.edu/~emin/www/source_code/red_black_tree/index.html +// https://web.archive.org/web/20120507164830/https://web.mit.edu/~emin/www/source_code/red_black_tree/index.html template <class T, class C = Comparator<T>, class A = DefaultAllocator> class Set { diff --git a/core/templates/vector.h b/core/templates/vector.h index 08cbef6ba4..2600604eb7 100644 --- a/core/templates/vector.h +++ b/core/templates/vector.h @@ -92,7 +92,7 @@ public: void append_array(Vector<T> p_other); - bool has(const T &p_val) { + bool has(const T &p_val) const { return find(p_val, 0) != -1; } @@ -229,7 +229,7 @@ public: _FORCE_INLINE_ bool operator==(const ConstIterator &b) const { return elem_ptr == b.elem_ptr; } _FORCE_INLINE_ bool operator!=(const ConstIterator &b) const { return elem_ptr != b.elem_ptr; } - ConstIterator(T *p_ptr) { elem_ptr = p_ptr; } + ConstIterator(const T *p_ptr) { elem_ptr = p_ptr; } ConstIterator() {} ConstIterator(const ConstIterator &p_it) { elem_ptr = p_it.elem_ptr; } diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 09cf785390..8373cbd4e8 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -203,9 +203,9 @@ Error Array::resize(int p_new_size) { return _p->array.resize(p_new_size); } -void Array::insert(int p_pos, const Variant &p_value) { - ERR_FAIL_COND(!_p->typed.validate(p_value, "insert")); - _p->array.insert(p_pos, p_value); +Error Array::insert(int p_pos, const Variant &p_value) { + ERR_FAIL_COND_V(!_p->typed.validate(p_value, "insert"), ERR_INVALID_PARAMETER); + return _p->array.insert(p_pos, p_value); } void Array::fill(const Variant &p_value) { @@ -535,8 +535,8 @@ void Array::push_front(const Variant &p_value) { Variant Array::pop_back() { if (!_p->array.is_empty()) { - int n = _p->array.size() - 1; - Variant ret = _p->array.get(n); + const int n = _p->array.size() - 1; + const Variant ret = _p->array.get(n); _p->array.resize(n); return ret; } @@ -545,13 +545,38 @@ Variant Array::pop_back() { Variant Array::pop_front() { if (!_p->array.is_empty()) { - Variant ret = _p->array.get(0); + const Variant ret = _p->array.get(0); _p->array.remove(0); return ret; } return Variant(); } +Variant Array::pop_at(int p_pos) { + if (_p->array.is_empty()) { + // Return `null` without printing an error to mimic `pop_back()` and `pop_front()` behavior. + return Variant(); + } + + if (p_pos < 0) { + // Relative offset from the end + p_pos = _p->array.size() + p_pos; + } + + ERR_FAIL_INDEX_V_MSG( + p_pos, + _p->array.size(), + Variant(), + vformat( + "The calculated index %s is out of bounds (the array has %s elements). Leaving the array untouched and returning `null`.", + p_pos, + _p->array.size())); + + const Variant ret = _p->array.get(p_pos); + _p->array.remove(p_pos); + return ret; +} + Variant Array::min() const { Variant minval; for (int i = 0; i < size(); i++) { diff --git a/core/variant/array.h b/core/variant/array.h index 540dcb1f4e..4a1b25c4a9 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -72,7 +72,7 @@ public: void append_array(const Array &p_array); Error resize(int p_new_size); - void insert(int p_pos, const Variant &p_value); + Error insert(int p_pos, const Variant &p_value); void remove(int p_pos); void fill(const Variant &p_value); @@ -97,6 +97,7 @@ public: void push_front(const Variant &p_value); Variant pop_back(); Variant pop_front(); + Variant pop_at(int p_pos); Array duplicate(bool p_deep = false) const; diff --git a/core/variant/callable_bind.cpp b/core/variant/callable_bind.cpp index 10446a5ec1..56eda6e703 100644 --- a/core/variant/callable_bind.cpp +++ b/core/variant/callable_bind.cpp @@ -169,7 +169,8 @@ CallableCustomUnbind::~CallableCustomUnbind() { } Callable callable_bind(const Callable &p_callable, const Variant &p_arg1) { - return p_callable.bind((const Variant **)&p_arg1, 1); + const Variant *args[1] = { &p_arg1 }; + return p_callable.bind(args, 1); } Callable callable_bind(const Callable &p_callable, const Variant &p_arg1, const Variant &p_arg2) { diff --git a/core/variant/method_ptrcall.h b/core/variant/method_ptrcall.h index 8836e257a9..98304621f2 100644 --- a/core/variant/method_ptrcall.h +++ b/core/variant/method_ptrcall.h @@ -191,6 +191,7 @@ struct PtrToArg<ObjectID> { // This is for the special cases used by Variant. +// No EncodeT because direct pointer conversion not possible. #define MAKE_VECARG(m_type) \ template <> \ struct PtrToArg<Vector<m_type>> { \ @@ -236,6 +237,7 @@ struct PtrToArg<ObjectID> { } \ } +// No EncodeT because direct pointer conversion not possible. #define MAKE_VECARG_ALT(m_type, m_type_alt) \ template <> \ struct PtrToArg<Vector<m_type_alt>> { \ @@ -285,6 +287,7 @@ MAKE_VECARG_ALT(String, StringName); // For stuff that gets converted to Array vectors. +// No EncodeT because direct pointer conversion not possible. #define MAKE_VECARR(m_type) \ template <> \ struct PtrToArg<Vector<m_type>> { \ @@ -325,6 +328,7 @@ MAKE_VECARR(Variant); MAKE_VECARR(RID); MAKE_VECARR(Plane); +// No EncodeT because direct pointer conversion not possible. #define MAKE_DVECARR(m_type) \ template <> \ struct PtrToArg<Vector<m_type>> { \ @@ -372,6 +376,7 @@ MAKE_VECARR(Plane); // Special case for IPAddress. +// No EncodeT because direct pointer conversion not possible. #define MAKE_STRINGCONV_BY_REFERENCE(m_type) \ template <> \ struct PtrToArg<m_type> { \ @@ -395,6 +400,7 @@ MAKE_VECARR(Plane); MAKE_STRINGCONV_BY_REFERENCE(IPAddress); +// No EncodeT because direct pointer conversion not possible. template <> struct PtrToArg<Vector<Face3>> { _FORCE_INLINE_ static Vector<Face3> convert(const void *p_ptr) { @@ -429,6 +435,7 @@ struct PtrToArg<Vector<Face3>> { } }; +// No EncodeT because direct pointer conversion not possible. template <> struct PtrToArg<const Vector<Face3> &> { _FORCE_INLINE_ static Vector<Face3> convert(const void *p_ptr) { diff --git a/core/variant/native_ptr.h b/core/variant/native_ptr.h new file mode 100644 index 0000000000..b4ec0df7d6 --- /dev/null +++ b/core/variant/native_ptr.h @@ -0,0 +1,130 @@ +/*************************************************************************/ +/* native_ptr.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 NATIVE_PTR_H +#define NATIVE_PTR_H + +#include "core/math/audio_frame.h" +#include "core/variant/method_ptrcall.h" +#include "core/variant/type_info.h" + +template <class T> +struct GDNativeConstPtr { + const T *data = nullptr; + GDNativeConstPtr(const T *p_assign) { data = p_assign; } + static const char *get_name() { return "const void"; } + operator const T *() const { return data; } + operator Variant() const { return uint64_t(data); } +}; + +template <class T> +struct GDNativePtr { + T *data = nullptr; + GDNativePtr(T *p_assign) { data = p_assign; } + static const char *get_name() { return "void"; } + operator T *() const { return data; } + operator Variant() const { return uint64_t(data); } +}; + +#define GDVIRTUAL_NATIVE_PTR(m_type) \ + template <> \ + struct GDNativeConstPtr<m_type> { \ + const m_type *data = nullptr; \ + GDNativeConstPtr(const m_type *p_assign) { data = p_assign; } \ + static const char *get_name() { return "const " #m_type; } \ + operator const m_type *() const { return data; } \ + operator Variant() const { return uint64_t(data); } \ + }; \ + template <> \ + struct GDNativePtr<m_type> { \ + m_type *data = nullptr; \ + GDNativePtr(m_type *p_assign) { data = p_assign; } \ + static const char *get_name() { return #m_type; } \ + operator m_type *() const { return data; } \ + operator Variant() const { return uint64_t(data); } \ + }; + +template <class T> +struct GetTypeInfo<GDNativeConstPtr<T>> { + static const Variant::Type VARIANT_TYPE = Variant::NIL; + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; + static inline PropertyInfo get_class_info() { + return PropertyInfo(Variant::INT, String(), PROPERTY_HINT_INT_IS_POINTER, GDNativeConstPtr<T>::get_name()); + } +}; + +template <class T> +struct GetTypeInfo<GDNativePtr<T>> { + static const Variant::Type VARIANT_TYPE = Variant::NIL; + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; + static inline PropertyInfo get_class_info() { + return PropertyInfo(Variant::INT, String(), PROPERTY_HINT_INT_IS_POINTER, GDNativePtr<T>::get_name()); + } +}; + +template <class T> +struct PtrToArg<GDNativeConstPtr<T>> { + _FORCE_INLINE_ static GDNativeConstPtr<T> convert(const void *p_ptr) { + return GDNativeConstPtr<T>(reinterpret_cast<const T *>(p_ptr)); + } + typedef const T *EncodeT; + _FORCE_INLINE_ static void encode(GDNativeConstPtr<T> p_val, void *p_ptr) { + *((const T **)p_ptr) = p_val.data; + } +}; +template <class T> +struct PtrToArg<GDNativePtr<T>> { + _FORCE_INLINE_ static GDNativePtr<T> convert(const void *p_ptr) { + return GDNativePtr<T>(reinterpret_cast<const T *>(p_ptr)); + } + typedef T *EncodeT; + _FORCE_INLINE_ static void encode(GDNativePtr<T> p_val, void *p_ptr) { + *((T **)p_ptr) = p_val.data; + } +}; + +GDVIRTUAL_NATIVE_PTR(AudioFrame) +GDVIRTUAL_NATIVE_PTR(bool) +GDVIRTUAL_NATIVE_PTR(char) +GDVIRTUAL_NATIVE_PTR(char16_t) +GDVIRTUAL_NATIVE_PTR(char32_t) +GDVIRTUAL_NATIVE_PTR(wchar_t) +GDVIRTUAL_NATIVE_PTR(uint8_t) +GDVIRTUAL_NATIVE_PTR(int8_t) +GDVIRTUAL_NATIVE_PTR(uint16_t) +GDVIRTUAL_NATIVE_PTR(int16_t) +GDVIRTUAL_NATIVE_PTR(uint32_t) +GDVIRTUAL_NATIVE_PTR(int32_t) +GDVIRTUAL_NATIVE_PTR(int64_t) +GDVIRTUAL_NATIVE_PTR(uint64_t) +GDVIRTUAL_NATIVE_PTR(float) +GDVIRTUAL_NATIVE_PTR(double) + +#endif // NATIVE_PTR_H diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index d538b9faff..c5cada674f 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -1627,9 +1627,9 @@ Variant::operator String() const { String Variant::stringify(List<const void *> &stack) const { switch (type) { case NIL: - return "Null"; + return "null"; case BOOL: - return _data._bool ? "True" : "False"; + return _data._bool ? "true" : "false"; case INT: return itos(_data._int); case FLOAT: diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index c3481d4896..39207df9e7 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1421,7 +1421,8 @@ static void _register_variant_builtin_methods() { //bind_method(String, humanize_size, sarray("size"), varray()); bind_method(String, is_absolute_path, sarray(), varray()); - bind_method(String, is_rel_path, sarray(), varray()); + bind_method(String, is_relative_path, sarray(), varray()); + bind_method(String, simplify_path, sarray(), varray()); bind_method(String, get_base_dir, sarray(), varray()); bind_method(String, get_file, sarray(), varray()); bind_method(String, xml_escape, sarray("escape_quotes"), varray(false)); @@ -1501,6 +1502,8 @@ static void _register_variant_builtin_methods() { bind_method(Vector2, clamp, sarray("min", "max"), varray()); bind_method(Vector2, snapped, sarray("step"), varray()); + bind_static_method(Vector2, from_angle, sarray("angle"), varray()); + /* Vector2i */ bind_method(Vector2i, aspect, sarray(), varray()); @@ -1806,6 +1809,7 @@ static void _register_variant_builtin_methods() { bind_method(Array, has, sarray("value"), varray()); bind_method(Array, pop_back, sarray(), varray()); bind_method(Array, pop_front, sarray(), varray()); + bind_method(Array, pop_at, sarray("position"), varray()); bind_method(Array, sort, sarray(), varray()); bind_method(Array, sort_custom, sarray("func"), varray()); bind_method(Array, shuffle, sarray(), varray()); diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp index 16c7428781..a245aff35b 100644 --- a/core/variant/variant_op.cpp +++ b/core/variant/variant_op.cpp @@ -171,7 +171,7 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorDiv<Vector2, Vector2, double>>(Variant::OP_DIVIDE, Variant::VECTOR2, Variant::FLOAT); register_op<OperatorEvaluatorDiv<Vector2, Vector2, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR2, Variant::INT); - register_op<OperatorEvaluatorDiv<Vector2i, Vector2i, Vector2i>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::VECTOR2I); + register_op<OperatorEvaluatorDivNZ<Vector2i, Vector2i, Vector2i>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::VECTOR2I); register_op<OperatorEvaluatorDivNZ<Vector2i, Vector2i, double>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::FLOAT); register_op<OperatorEvaluatorDivNZ<Vector2i, Vector2i, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::INT); @@ -183,7 +183,7 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorDiv<Vector3, Vector3, double>>(Variant::OP_DIVIDE, Variant::VECTOR3, Variant::FLOAT); register_op<OperatorEvaluatorDiv<Vector3, Vector3, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR3, Variant::INT); - register_op<OperatorEvaluatorDiv<Vector3i, Vector3i, Vector3i>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::VECTOR3I); + register_op<OperatorEvaluatorDivNZ<Vector3i, Vector3i, Vector3i>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::VECTOR3I); register_op<OperatorEvaluatorDivNZ<Vector3i, Vector3i, double>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::FLOAT); register_op<OperatorEvaluatorDivNZ<Vector3i, Vector3i, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::INT); @@ -195,10 +195,10 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorDiv<Color, Color, int64_t>>(Variant::OP_DIVIDE, Variant::COLOR, Variant::INT); register_op<OperatorEvaluatorModNZ<int64_t, int64_t, int64_t>>(Variant::OP_MODULE, Variant::INT, Variant::INT); - register_op<OperatorEvaluatorMod<Vector2i, Vector2i, Vector2i>>(Variant::OP_MODULE, Variant::VECTOR2I, Variant::VECTOR2I); + register_op<OperatorEvaluatorModNZ<Vector2i, Vector2i, Vector2i>>(Variant::OP_MODULE, Variant::VECTOR2I, Variant::VECTOR2I); register_op<OperatorEvaluatorModNZ<Vector2i, Vector2i, int64_t>>(Variant::OP_MODULE, Variant::VECTOR2I, Variant::INT); - register_op<OperatorEvaluatorMod<Vector3i, Vector3i, Vector3i>>(Variant::OP_MODULE, Variant::VECTOR3I, Variant::VECTOR3I); + register_op<OperatorEvaluatorModNZ<Vector3i, Vector3i, Vector3i>>(Variant::OP_MODULE, Variant::VECTOR3I, Variant::VECTOR3I); register_op<OperatorEvaluatorModNZ<Vector3i, Vector3i, int64_t>>(Variant::OP_MODULE, Variant::VECTOR3I, Variant::INT); register_op<OperatorEvaluatorStringModNil>(Variant::OP_MODULE, Variant::STRING, Variant::NIL); diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h index cbdd60f404..3c9f849a4f 100644 --- a/core/variant/variant_op.h +++ b/core/variant/variant_op.h @@ -168,6 +168,54 @@ public: static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; } }; +template <> +class OperatorEvaluatorDivNZ<Vector2i, Vector2i, Vector2i> { +public: + static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { + const Vector2i &a = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_left); + const Vector2i &b = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_right); + if (unlikely(b.x == 0 || b.y == 0)) { + r_valid = false; + *r_ret = "Division by zero error"; + return; + } + *r_ret = a / b; + r_valid = true; + } + static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { + VariantTypeChanger<Vector2i>::change(r_ret); + *VariantGetInternalPtr<Vector2i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector2i>::get_ptr(left) / *VariantGetInternalPtr<Vector2i>::get_ptr(right); + } + static void ptr_evaluate(const void *left, const void *right, void *r_ret) { + PtrToArg<Vector2i>::encode(PtrToArg<Vector2i>::convert(left) / PtrToArg<Vector2i>::convert(right), r_ret); + } + static Variant::Type get_return_type() { return GetTypeInfo<Vector2i>::VARIANT_TYPE; } +}; + +template <> +class OperatorEvaluatorDivNZ<Vector3i, Vector3i, Vector3i> { +public: + static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { + const Vector3i &a = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_left); + const Vector3i &b = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_right); + if (unlikely(b.x == 0 || b.y == 0 || b.z == 0)) { + r_valid = false; + *r_ret = "Division by zero error"; + return; + } + *r_ret = a / b; + r_valid = true; + } + static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { + VariantTypeChanger<Vector3i>::change(r_ret); + *VariantGetInternalPtr<Vector3i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector3i>::get_ptr(left) / *VariantGetInternalPtr<Vector3i>::get_ptr(right); + } + static void ptr_evaluate(const void *left, const void *right, void *r_ret) { + PtrToArg<Vector3i>::encode(PtrToArg<Vector3i>::convert(left) / PtrToArg<Vector3i>::convert(right), r_ret); + } + static Variant::Type get_return_type() { return GetTypeInfo<Vector3i>::VARIANT_TYPE; } +}; + template <class R, class A, class B> class OperatorEvaluatorMod { public: @@ -209,6 +257,54 @@ public: static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; } }; +template <> +class OperatorEvaluatorModNZ<Vector2i, Vector2i, Vector2i> { +public: + static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { + const Vector2i &a = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_left); + const Vector2i &b = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_right); + if (unlikely(b.x == 0 || b.y == 0)) { + r_valid = false; + *r_ret = "Module by zero error"; + return; + } + *r_ret = a % b; + r_valid = true; + } + static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { + VariantTypeChanger<Vector2i>::change(r_ret); + *VariantGetInternalPtr<Vector2i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector2i>::get_ptr(left) % *VariantGetInternalPtr<Vector2i>::get_ptr(right); + } + static void ptr_evaluate(const void *left, const void *right, void *r_ret) { + PtrToArg<Vector2i>::encode(PtrToArg<Vector2i>::convert(left) / PtrToArg<Vector2i>::convert(right), r_ret); + } + static Variant::Type get_return_type() { return GetTypeInfo<Vector2i>::VARIANT_TYPE; } +}; + +template <> +class OperatorEvaluatorModNZ<Vector3i, Vector3i, Vector3i> { +public: + static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { + const Vector3i &a = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_left); + const Vector3i &b = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_right); + if (unlikely(b.x == 0 || b.y == 0 || b.z == 0)) { + r_valid = false; + *r_ret = "Module by zero error"; + return; + } + *r_ret = a % b; + r_valid = true; + } + static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { + VariantTypeChanger<Vector3i>::change(r_ret); + *VariantGetInternalPtr<Vector3i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector3i>::get_ptr(left) % *VariantGetInternalPtr<Vector3i>::get_ptr(right); + } + static void ptr_evaluate(const void *left, const void *right, void *r_ret) { + PtrToArg<Vector3i>::encode(PtrToArg<Vector3i>::convert(left) % PtrToArg<Vector3i>::convert(right), r_ret); + } + static Variant::Type get_return_type() { return GetTypeInfo<Vector3i>::VARIANT_TYPE; } +}; + template <class R, class A> class OperatorEvaluatorNeg { public: diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 6c57d1de10..232054d0ca 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -35,6 +35,8 @@ #include "core/object/ref_counted.h" #include "core/os/os.h" #include "core/templates/oa_hash_map.h" +#include "core/templates/rid.h" +#include "core/templates/rid_owner.h" #include "core/variant/binder_common.h" #include "core/variant/variant_parser.h" @@ -265,14 +267,6 @@ struct VariantUtilityFunctions { return Math::db2linear(db); } - static inline Vector2 polar2cartesian(double r, double th) { - return Vector2(r * Math::cos(th), r * Math::sin(th)); - } - - static inline Vector2 cartesian2polar(double x, double y) { - return Vector2(Math::sqrt(x * x + y * y), Math::atan2(y, x)); - } - static inline int64_t wrapi(int64_t value, int64_t min, int64_t max) { return Math::wrapi(value, min, max); } @@ -728,6 +722,13 @@ struct VariantUtilityFunctions { } return p_instance.get_validated_object() != nullptr; } + + static inline uint64_t rid_allocate_id() { + return RID_AllocBase::_gen_id(); + } + static inline RID rid_from_int64(uint64_t p_base) { + return RID::from_uint64(p_base); + } }; #ifdef DEBUG_METHODS_ENABLED @@ -1205,9 +1206,6 @@ void Variant::_register_variant_utility_functions() { FUNCBINDR(linear2db, sarray("lin"), Variant::UTILITY_FUNC_TYPE_MATH); FUNCBINDR(db2linear, sarray("db"), Variant::UTILITY_FUNC_TYPE_MATH); - FUNCBINDR(polar2cartesian, sarray("r", "th"), Variant::UTILITY_FUNC_TYPE_MATH); - FUNCBINDR(cartesian2polar, sarray("x", "y"), Variant::UTILITY_FUNC_TYPE_MATH); - FUNCBINDR(wrapi, sarray("value", "min", "max"), Variant::UTILITY_FUNC_TYPE_MATH); FUNCBINDR(wrapf, sarray("value", "min", "max"), Variant::UTILITY_FUNC_TYPE_MATH); @@ -1265,6 +1263,9 @@ void Variant::_register_variant_utility_functions() { FUNCBINDR(instance_from_id, sarray("instance_id"), Variant::UTILITY_FUNC_TYPE_GENERAL); FUNCBINDR(is_instance_id_valid, sarray("id"), Variant::UTILITY_FUNC_TYPE_GENERAL); FUNCBINDR(is_instance_valid, sarray("instance"), Variant::UTILITY_FUNC_TYPE_GENERAL); + + FUNCBINDR(rid_allocate_id, Vector<String>(), Variant::UTILITY_FUNC_TYPE_GENERAL); + FUNCBINDR(rid_from_int64, sarray("base"), Variant::UTILITY_FUNC_TYPE_GENERAL); } void Variant::_unregister_variant_utility_functions() { diff --git a/doc/Doxyfile b/doc/Doxyfile index a7aa204741..26d86cb127 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -20,7 +20,7 @@ # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# built into libc) for the transcoding. See https://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. @@ -295,7 +295,7 @@ EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. +# documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. @@ -692,7 +692,7 @@ LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. @@ -773,7 +773,7 @@ INPUT = ../core/ ../main/ ../scene/ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# documentation (see: https://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. @@ -1006,7 +1006,7 @@ SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version +# (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: @@ -1034,7 +1034,7 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES # If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# clang parser (see: https://clang.llvm.org/) for more accurate parsing at the # cost of reduced performance. This can be particularly helpful with template # rich C++ code for which doxygen's built-in parser lacks the necessary type # information. @@ -1170,7 +1170,7 @@ HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. @@ -1349,7 +1349,7 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# (see: https://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1357,7 +1357,7 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# Folders (see: https://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1366,7 +1366,7 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# Filters (see: https://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1374,7 +1374,7 @@ QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# Filters (see: https://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1382,7 +1382,7 @@ QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# https://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = @@ -1487,7 +1487,7 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side Javascript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1514,7 +1514,7 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. +# MathJax from https://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1576,7 +1576,7 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). +# Xapian (see: https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1589,7 +1589,7 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). See the section "External Indexing and +# Xapian (see: https://xapian.org/). See the section "External Indexing and # Searching" for details. # This tag requires that the tag SEARCHENGINE is set to YES. @@ -1773,7 +1773,7 @@ LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See -# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. # The default value is: plain. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2138,7 +2138,7 @@ CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the +# https://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. @@ -2160,7 +2160,7 @@ HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 46b9bdd52d..755902c709 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -99,14 +99,6 @@ [b]Warning:[/b] Deserialized object can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threats (remote code execution). </description> </method> - <method name="cartesian2polar"> - <return type="Vector2" /> - <argument index="0" name="x" type="float" /> - <argument index="1" name="y" type="float" /> - <description> - Converts a 2D point expressed in the cartesian coordinate system (X and Y axis) to the polar coordinate system (a distance from the origin and an angle). - </description> - </method> <method name="ceil"> <return type="float" /> <argument index="0" name="x" type="float" /> @@ -521,14 +513,6 @@ [b]Warning:[/b] Due to the way it is implemented, this function returns [code]0[/code] rather than [code]1[/code] for non-positive values of [code]value[/code] (in reality, 1 is the smallest integer power of 2). </description> </method> - <method name="polar2cartesian"> - <return type="Vector2" /> - <argument index="0" name="r" type="float" /> - <argument index="1" name="th" type="float" /> - <description> - Converts a 2D point expressed in the polar coordinate system (a distance from the origin [code]r[/code] and an angle [code]th[/code]) to the cartesian coordinate system (X and Y axis). - </description> - </method> <method name="posmod"> <return type="int" /> <argument index="0" name="x" type="int" /> @@ -711,6 +695,19 @@ <description> </description> </method> + <method name="rid_allocate_id"> + <return type="int" /> + <description> + Allocate a unique ID which can be used by the implementation to construct a RID. This is used mainly from native extensions to implement servers. + </description> + </method> + <method name="rid_from_int64"> + <return type="RID" /> + <argument index="0" name="base" type="int" /> + <description> + Create a RID from an int64. This is used mainly from native extensions to build servers. + </description> + </method> <method name="round"> <return type="float" /> <argument index="0" name="x" type="float" /> @@ -1072,8 +1069,8 @@ <member name="TranslationServer" type="TranslationServer" setter="" getter=""> The [TranslationServer] singleton. </member> - <member name="VisualScriptEditor" type="VisualScriptEditor" setter="" getter=""> - The [VisualScriptEditor] singleton. + <member name="VisualScriptEditor" type="VisualScriptCustomNodes" setter="" getter=""> + The [VisualScriptCustomNodes] singleton. </member> <member name="XRServer" type="XRServer" setter="" getter=""> The [XRServer] singleton. @@ -2351,9 +2348,11 @@ </constant> <constant name="PROPERTY_HINT_INT_IS_OBJECTID" value="38" enum="PropertyHint"> </constant> + <constant name="PROPERTY_HINT_INT_IS_POINTER" value="40" enum="PropertyHint"> + </constant> <constant name="PROPERTY_HINT_ARRAY_TYPE" value="39" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_MAX" value="40" enum="PropertyHint"> + <constant name="PROPERTY_HINT_MAX" value="41" enum="PropertyHint"> </constant> <constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags"> </constant> @@ -2426,6 +2425,8 @@ </constant> <constant name="PROPERTY_USAGE_EDITOR_BASIC_SETTING" value="268435456" enum="PropertyUsageFlags"> </constant> + <constant name="PROPERTY_USAGE_ARRAY" value="1073741824" enum="PropertyUsageFlags"> + </constant> <constant name="PROPERTY_USAGE_DEFAULT" value="7" enum="PropertyUsageFlags"> Default usage (storage, editor and network). </constant> @@ -2459,10 +2460,29 @@ <constant name="METHOD_FLAG_STATIC" value="256" enum="MethodFlags"> </constant> <constant name="METHOD_FLAG_OBJECT_CORE" value="512" enum="MethodFlags"> + Used internally. Allows to not dump core virtuals such as [code]_notification[/code] to the JSON API. </constant> <constant name="METHOD_FLAGS_DEFAULT" value="1" enum="MethodFlags"> Default method flags. </constant> + <constant name="RPC_MODE_DISABLED" value="0" enum="RPCMode"> + Used with [method Node.rpc_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods. + </constant> + <constant name="RPC_MODE_ANY" value="1" enum="RPCMode"> + Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc(any)[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not. + </constant> + <constant name="RPC_MODE_AUTH" value="2" enum="RPCMode"> + Used with [method Node.rpc_config] to set a method to be callable remotely only by the current multiplayer authority (which is the server by default). Analogous to the [code]@rpc(auth)[/code] annotation. See [method Node.set_multiplayer_authority]. + </constant> + <constant name="TRANSFER_MODE_UNRELIABLE" value="0" enum="TransferMode"> + Packets are not acknowledged, no resend attempts are made for lost packets. Packets may arrive in any order. Potentially faster than [constant TRANSFER_MODE_ORDERED]. Use for non-critical data, and always consider whether the order matters. + </constant> + <constant name="TRANSFER_MODE_ORDERED" value="1" enum="TransferMode"> + Packets are not acknowledged, no resend attempts are made for lost packets. Packets are received in the order they were sent in. Potentially faster than [constant TRANSFER_MODE_RELIABLE]. Use for non-critical data or data that would be outdated if received late due to resend attempt(s) anyway, for example movement and positional data. + </constant> + <constant name="TRANSFER_MODE_RELIABLE" value="2" enum="TransferMode"> + Packets must be received and resend attempts should be made until the packets are acknowledged. Packets must be received in the order they were sent in. Most reliable transfer mode, but potentially the slowest due to the overhead. Use for critical data that must be transmitted and arrive in order, for example an ability being triggered or a chat message. Consider carefully if the information really is critical, and use sparingly. + </constant> <constant name="TYPE_NIL" value="0" enum="Variant.Type"> Variable is [code]null[/code]. </constant> diff --git a/doc/classes/AnimatableBody2D.xml b/doc/classes/AnimatableBody2D.xml new file mode 100644 index 0000000000..731c702549 --- /dev/null +++ b/doc/classes/AnimatableBody2D.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AnimatableBody2D" inherits="StaticBody2D" version="4.0"> + <brief_description> + Physics body for 2D physics which moves only by script or animation. Useful for moving platforms and doors. + </brief_description> + <description> + Animatable body for 2D physics. + An animatable body can't be moved by external forces or contacts, but can be moved by script or animation to affect other bodies in its path. It is ideal for implementing moving objects in the environment, such as moving platforms or doors. + When the body is moved manually, either from code or from an [AnimationPlayer] (with [member AnimationPlayer.playback_process_mode] set to [code]physics[/code]), the physics will automatically compute an estimate of their linear and angular velocity. This makes them very useful for moving platforms or other AnimationPlayer-controlled objects (like a door, a bridge that opens, etc). + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <members> + <member name="sync_to_physics" type="bool" setter="set_sync_to_physics" getter="is_sync_to_physics_enabled" default="false"> + If [code]true[/code], the body's movement will be synchronized to the physics frame. This is useful when animating movement via [AnimationPlayer], for example on moving platforms. Do [b]not[/b] use together with [method PhysicsBody2D.move_and_collide]. + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/AnimatableBody3D.xml b/doc/classes/AnimatableBody3D.xml new file mode 100644 index 0000000000..8192f26057 --- /dev/null +++ b/doc/classes/AnimatableBody3D.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AnimatableBody3D" inherits="StaticBody3D" version="4.0"> + <brief_description> + Physics body for 3D physics which moves only by script or animation. Useful for moving platforms and doors. + </brief_description> + <description> + Animatable body for 3D physics. + An animatable body can't be moved by external forces or contacts, but can be moved by script or animation to affect other bodies in its path. It is ideal for implementing moving objects in the environment, such as moving platforms or doors. + When the body is moved manually, either from code or from an [AnimationPlayer] (with [member AnimationPlayer.playback_process_mode] set to [code]physics[/code]), the physics will automatically compute an estimate of their linear and angular velocity. This makes them very useful for moving platforms or other AnimationPlayer-controlled objects (like a door, a bridge that opens, etc). + </description> + <tutorials> + <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> + </tutorials> + <methods> + </methods> + <members> + <member name="sync_to_physics" type="bool" setter="set_sync_to_physics" getter="is_sync_to_physics_enabled" default="false"> + If [code]true[/code], the body's movement will be synchronized to the physics frame. This is useful when animating movement via [AnimationPlayer], for example on moving platforms. Do [b]not[/b] use together with [method PhysicsBody3D.move_and_collide]. + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/Area3D.xml b/doc/classes/Area3D.xml index 2180196bb5..e91cfd79a1 100644 --- a/doc/classes/Area3D.xml +++ b/doc/classes/Area3D.xml @@ -92,6 +92,15 @@ <member name="space_override" type="int" setter="set_space_override_mode" getter="get_space_override_mode" enum="Area3D.SpaceOverride" default="0"> Override mode for gravity and damping calculations within this area. See [enum SpaceOverride] for possible values. </member> + <member name="wind_attenuation_factor" type="float" setter="set_wind_attenuation_factor" getter="get_wind_attenuation_factor" default="0.0"> + The exponential rate at which wind force decreases with distance from its origin. + </member> + <member name="wind_force_magnitude" type="float" setter="set_wind_force_magnitude" getter="get_wind_force_magnitude" default="0.0"> + The magnitude of area-specific wind force. + </member> + <member name="wind_source_path" type="NodePath" setter="set_wind_source_path" getter="get_wind_source_path" default="NodePath("")"> + The [Node3D] which is used to specify the the direction and origin of an area-specific wind force. The direction is opposite to the z-axis of the [Node3D]'s local transform, and its origin is the origin of the [Node3D]'s local transform. + </member> </members> <signals> <signal name="area_entered"> diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 7767a1028d..02c6b18d55 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -302,7 +302,7 @@ </description> </method> <method name="insert"> - <return type="void" /> + <return type="int" /> <argument index="0" name="position" type="int" /> <argument index="1" name="value" type="Variant" /> <description> @@ -393,6 +393,14 @@ <description> </description> </method> + <method name="pop_at"> + <return type="Variant" /> + <argument index="0" name="position" type="int" /> + <description> + Removes and returns the element of the array at index [code]position[/code]. If negative, [code]position[/code] is considered relative to the end of the array. Leaves the array untouched and returns [code]null[/code] if the array is empty or if it's accessed out of bounds. An error message is printed when the array is accessed out of bounds, but not when the array is empty. + [b]Note:[/b] On large arrays, this method can be slower than [method pop_back] as it will reindex the array's elements that are located after the removed element. The larger the array and the lower the index of the removed element, the slower [method pop_at] will be. + </description> + </method> <method name="pop_back"> <return type="Variant" /> <description> @@ -487,7 +495,7 @@ <argument index="2" name="step" type="int" default="1" /> <argument index="3" name="deep" type="bool" default="false" /> <description> - Duplicates the subset described in the function and returns it in an array, deeply copying the array if [code]deep[/code] is [code]true[/code]. Lower and upper index are inclusive, with the [code]step[/code] describing the change between indices while slicing. + Duplicates the subset described in the function and returns it in an array, deeply copying the array if [code]deep[/code] is [code]true[/code]. Lower and upper index are inclusive, with the [code]step[/code] describing the change between indices while slicing. If [code]end[/code] is an invalid value, the end of the array is used. </description> </method> <method name="sort"> diff --git a/doc/classes/ArrayMesh.xml b/doc/classes/ArrayMesh.xml index 637b9a9f16..670a25ab83 100644 --- a/doc/classes/ArrayMesh.xml +++ b/doc/classes/ArrayMesh.xml @@ -115,6 +115,7 @@ <argument index="0" name="index" type="int" /> <argument index="1" name="name" type="StringName" /> <description> + Sets the name of the blend shape at this index. </description> </method> <method name="surface_find_by_name" qualifiers="const"> diff --git a/doc/classes/AudioStream.xml b/doc/classes/AudioStream.xml index 8a58b178d8..32e51603ee 100644 --- a/doc/classes/AudioStream.xml +++ b/doc/classes/AudioStream.xml @@ -13,12 +13,38 @@ <link title="Audio Spectrum Demo">https://godotengine.org/asset-library/asset/528</link> </tutorials> <methods> + <method name="_get_length" qualifiers="virtual const"> + <return type="float" /> + <description> + </description> + </method> + <method name="_get_stream_name" qualifiers="virtual const"> + <return type="String" /> + <description> + </description> + </method> + <method name="_instance_playback" qualifiers="virtual const"> + <return type="AudioStreamPlayback" /> + <description> + </description> + </method> + <method name="_is_monophonic" qualifiers="virtual const"> + <return type="bool" /> + <description> + </description> + </method> <method name="get_length" qualifiers="const"> <return type="float" /> <description> Returns the length of the audio stream in seconds. </description> </method> + <method name="is_monophonic" qualifiers="const"> + <return type="bool" /> + <description> + Returns true if this audio stream only supports monophonic playback, or false if the audio stream supports polyphony. + </description> + </method> </methods> <constants> </constants> diff --git a/doc/classes/AudioStreamPlayback.xml b/doc/classes/AudioStreamPlayback.xml index cb01aa75e8..25f3e076b4 100644 --- a/doc/classes/AudioStreamPlayback.xml +++ b/doc/classes/AudioStreamPlayback.xml @@ -10,6 +10,46 @@ <link title="Audio Generator Demo">https://godotengine.org/asset-library/asset/526</link> </tutorials> <methods> + <method name="_get_loop_count" qualifiers="virtual const"> + <return type="int" /> + <description> + </description> + </method> + <method name="_get_playback_position" qualifiers="virtual const"> + <return type="float" /> + <description> + </description> + </method> + <method name="_is_playing" qualifiers="virtual const"> + <return type="bool" /> + <description> + </description> + </method> + <method name="_mix" qualifiers="virtual"> + <return type="int" /> + <argument index="0" name="buffer" type="AudioFrame*" /> + <argument index="1" name="rate_scale" type="float" /> + <argument index="2" name="frames" type="int" /> + <description> + </description> + </method> + <method name="_seek" qualifiers="virtual"> + <return type="void" /> + <argument index="0" name="position" type="float" /> + <description> + </description> + </method> + <method name="_start" qualifiers="virtual"> + <return type="void" /> + <argument index="0" name="from_pos" type="float" /> + <description> + </description> + </method> + <method name="_stop" qualifiers="virtual"> + <return type="void" /> + <description> + </description> + </method> </methods> <constants> </constants> diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml index a6c437f875..b692ae858e 100644 --- a/doc/classes/AudioStreamPlayer.xml +++ b/doc/classes/AudioStreamPlayer.xml @@ -56,6 +56,9 @@ <member name="bus" type="StringName" setter="set_bus" getter="get_bus" default="&"Master""> Bus on which this audio is playing. </member> + <member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1"> + The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds. + </member> <member name="mix_target" type="int" setter="set_mix_target" getter="get_mix_target" enum="AudioStreamPlayer.MixTarget" default="0"> If the audio configuration has more than two speakers, this sets the target channels. See [enum MixTarget] constants. </member> diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml index c40c223091..e36a428499 100644 --- a/doc/classes/AudioStreamPlayer2D.xml +++ b/doc/classes/AudioStreamPlayer2D.xml @@ -61,6 +61,9 @@ <member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="2000.0"> Maximum distance from which audio is still hearable. </member> + <member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1"> + The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds. + </member> <member name="pitch_scale" type="float" setter="set_pitch_scale" getter="get_pitch_scale" default="1.0"> The pitch and the tempo of the audio, as a multiplier of the audio sample's sample rate. </member> diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml index 584f03399c..fa2fa5a8e3 100644 --- a/doc/classes/AudioStreamPlayer3D.xml +++ b/doc/classes/AudioStreamPlayer3D.xml @@ -83,6 +83,9 @@ <member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="0.0"> Sets the distance from which the [member out_of_range_mode] takes effect. Has no effect if set to 0. </member> + <member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1"> + The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds. + </member> <member name="out_of_range_mode" type="int" setter="set_out_of_range_mode" getter="get_out_of_range_mode" enum="AudioStreamPlayer3D.OutOfRangeMode" default="0"> Decides if audio should pause when source is outside of [member max_distance] range. </member> diff --git a/doc/classes/BaseMaterial3D.xml b/doc/classes/BaseMaterial3D.xml index bbf7c5eb6d..a5516636aa 100644 --- a/doc/classes/BaseMaterial3D.xml +++ b/doc/classes/BaseMaterial3D.xml @@ -683,7 +683,7 @@ </constant> <constant name="BILLBOARD_PARTICLES" value="3" enum="BillboardMode"> Used for particle systems when assigned to [GPUParticles3D] and [CPUParticles3D] nodes. Enables [code]particles_anim_*[/code] properties. - The [member ParticlesMaterial.anim_speed] or [member CPUParticles3D.anim_speed] should also be set to a positive value for the animation to play. + The [member ParticlesMaterial.anim_speed_min] or [member CPUParticles3D.anim_speed_min] should also be set to a value bigger than zero for the animation to play. </constant> <constant name="TEXTURE_CHANNEL_RED" value="0" enum="TextureChannel"> Used to read from the red channel of a texture. diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index ab6897ca1d..9226140c1a 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -18,25 +18,23 @@ Sets this node's properties to match a given [GPUParticles2D] node with an assigned [ParticlesMaterial]. </description> </method> - <method name="get_param" qualifiers="const"> - <return type="float" /> + <method name="get_param_curve" qualifiers="const"> + <return type="Curve" /> <argument index="0" name="param" type="int" enum="CPUParticles2D.Parameter" /> <description> - Returns the base value of the parameter specified by [enum Parameter]. + Returns the [Curve] of the parameter specified by [enum Parameter]. </description> </method> - <method name="get_param_curve" qualifiers="const"> - <return type="Curve" /> + <method name="get_param_max" qualifiers="const"> + <return type="float" /> <argument index="0" name="param" type="int" enum="CPUParticles2D.Parameter" /> <description> - Returns the [Curve] of the parameter specified by [enum Parameter]. </description> </method> - <method name="get_param_randomness" qualifiers="const"> + <method name="get_param_min" qualifiers="const"> <return type="float" /> <argument index="0" name="param" type="int" enum="CPUParticles2D.Parameter" /> <description> - Returns the randomness factor of the parameter specified by [enum Parameter]. </description> </method> <method name="get_particle_flag" qualifiers="const"> @@ -52,28 +50,26 @@ Restarts the particle emitter. </description> </method> - <method name="set_param"> + <method name="set_param_curve"> <return type="void" /> <argument index="0" name="param" type="int" enum="CPUParticles2D.Parameter" /> - <argument index="1" name="value" type="float" /> + <argument index="1" name="curve" type="Curve" /> <description> - Sets the base value of the parameter specified by [enum Parameter]. + Sets the [Curve] of the parameter specified by [enum Parameter]. </description> </method> - <method name="set_param_curve"> + <method name="set_param_max"> <return type="void" /> <argument index="0" name="param" type="int" enum="CPUParticles2D.Parameter" /> - <argument index="1" name="curve" type="Curve" /> + <argument index="1" name="value" type="float" /> <description> - Sets the [Curve] of the parameter specified by [enum Parameter]. </description> </method> - <method name="set_param_randomness"> + <method name="set_param_min"> <return type="void" /> <argument index="0" name="param" type="int" enum="CPUParticles2D.Parameter" /> - <argument index="1" name="randomness" type="float" /> + <argument index="1" name="value" type="float" /> <description> - Sets the randomness factor of the parameter specified by [enum Parameter]. </description> </method> <method name="set_particle_flag"> @@ -89,41 +85,33 @@ <member name="amount" type="int" setter="set_amount" getter="get_amount" default="8"> Number of particles emitted in one emission cycle. </member> - <member name="angle" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial rotation applied to each particle, in degrees. - </member> <member name="angle_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's rotation will be animated along this [Curve]. </member> - <member name="angle_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Rotation randomness ratio. + <member name="angle_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> </member> - <member name="angular_velocity" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial angular velocity applied to each particle. Sets the speed of rotation of the particle. + <member name="angle_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> <member name="angular_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's angular velocity will vary along this [Curve]. </member> - <member name="angular_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Angular velocity randomness ratio. + <member name="angular_velocity_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> </member> - <member name="anim_offset" type="float" setter="set_param" getter="get_param" default="0.0"> - Particle animation offset. + <member name="angular_velocity_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> <member name="anim_offset_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's animation offset will vary along this [Curve]. </member> - <member name="anim_offset_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Animation offset randomness ratio. + <member name="anim_offset_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> </member> - <member name="anim_speed" type="float" setter="set_param" getter="get_param" default="0.0"> - Particle animation speed. + <member name="anim_offset_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> <member name="anim_speed_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's animation speed will vary along this [Curve]. </member> - <member name="anim_speed_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Animation speed randomness ratio. + <member name="anim_speed_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + </member> + <member name="anim_speed_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> <member name="color" type="Color" setter="set_color" getter="get_color" default="Color(1, 1, 1, 1)"> Each particle's initial color. If [member texture] is defined, it will be multiplied by this color. @@ -131,14 +119,12 @@ <member name="color_ramp" type="Gradient" setter="set_color_ramp" getter="get_color_ramp"> Each particle's color will vary along this [Gradient] (multiplied with [member color]). </member> - <member name="damping" type="float" setter="set_param" getter="get_param" default="0.0"> - The rate at which particles lose velocity. - </member> <member name="damping_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Damping will vary along this [Curve]. </member> - <member name="damping_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Damping randomness ratio. + <member name="damping_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + </member> + <member name="damping_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> <member name="direction" type="Vector2" setter="set_direction" getter="get_direction" default="Vector2(1, 0)"> Unit vector specifying the particles' emission direction. @@ -179,20 +165,16 @@ <member name="gravity" type="Vector2" setter="set_gravity" getter="get_gravity" default="Vector2(0, 980)"> Gravity applied to every particle. </member> - <member name="hue_variation" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial hue variation applied to each particle. - </member> <member name="hue_variation_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's hue will vary along this [Curve]. </member> - <member name="hue_variation_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Hue variation randomness ratio. + <member name="hue_variation_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + </member> + <member name="hue_variation_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> - <member name="initial_velocity" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial velocity magnitude for each particle. Direction comes from [member spread] and the node's orientation. + <member name="initial_velocity_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> </member> - <member name="initial_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Initial velocity randomness ratio. + <member name="initial_velocity_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> <member name="lifetime" type="float" setter="set_lifetime" getter="get_lifetime" default="1.0"> Amount of time each particle will exist. @@ -200,14 +182,12 @@ <member name="lifetime_randomness" type="float" setter="set_lifetime_randomness" getter="get_lifetime_randomness" default="0.0"> Particle lifetime randomness ratio. </member> - <member name="linear_accel" type="float" setter="set_param" getter="get_param" default="0.0"> - Linear acceleration applied to each particle in the direction of motion. - </member> <member name="linear_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's linear acceleration will vary along this [Curve]. </member> - <member name="linear_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Linear acceleration randomness ratio. + <member name="linear_accel_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + </member> + <member name="linear_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> <member name="local_coords" type="bool" setter="set_use_local_coordinates" getter="get_use_local_coordinates" default="true"> If [code]true[/code], particles use the parent node's coordinate space. If [code]false[/code], they use global coordinates. @@ -215,14 +195,12 @@ <member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot" default="false"> If [code]true[/code], only one emission cycle occurs. If set [code]true[/code] during a cycle, emission will stop at the cycle's end. </member> - <member name="orbit_velocity" type="float" setter="set_param" getter="get_param" default="0.0"> - Orbital velocity applied to each particle. Makes the particles circle around origin. Specified in number of full rotations around origin per second. - </member> <member name="orbit_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's orbital velocity will vary along this [Curve]. </member> - <member name="orbit_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Orbital velocity randomness ratio. + <member name="orbit_velocity_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + </member> + <member name="orbit_velocity_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> <member name="particle_flag_align_y" type="bool" setter="set_particle_flag" getter="get_particle_flag" default="false"> Align Y axis of particle with the direction of its velocity. @@ -230,41 +208,41 @@ <member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time" default="0.0"> Particle system starts as if it had already run for this many seconds. </member> - <member name="radial_accel" type="float" setter="set_param" getter="get_param" default="0.0"> - Radial acceleration applied to each particle. Makes particle accelerate away from origin. - </member> <member name="radial_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's radial acceleration will vary along this [Curve]. </member> - <member name="radial_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Radial acceleration randomness ratio. + <member name="radial_accel_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + </member> + <member name="radial_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> <member name="randomness" type="float" setter="set_randomness_ratio" getter="get_randomness_ratio" default="0.0"> Emission lifetime randomness ratio. </member> - <member name="scale_amount" type="float" setter="set_param" getter="get_param" default="1.0"> - Initial scale applied to each particle. - </member> <member name="scale_amount_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's scale will vary along this [Curve]. </member> - <member name="scale_amount_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Scale randomness ratio. + <member name="scale_amount_max" type="float" setter="set_param_max" getter="get_param_max" default="1.0"> + </member> + <member name="scale_amount_min" type="float" setter="set_param_min" getter="get_param_min" default="1.0"> + </member> + <member name="scale_curve_x" type="Curve" setter="set_scale_curve_x" getter="get_scale_curve_x"> + </member> + <member name="scale_curve_y" type="Curve" setter="set_scale_curve_y" getter="get_scale_curve_y"> </member> <member name="speed_scale" type="float" setter="set_speed_scale" getter="get_speed_scale" default="1.0"> Particle system's running speed scaling ratio. A value of [code]0[/code] can be used to pause the particles. </member> + <member name="split_scale" type="bool" setter="set_split_scale" getter="get_split_scale" default="false"> + </member> <member name="spread" type="float" setter="set_spread" getter="get_spread" default="45.0"> Each particle's initial direction range from [code]+spread[/code] to [code]-spread[/code] degrees. </member> - <member name="tangential_accel" type="float" setter="set_param" getter="get_param" default="0.0"> - Tangential acceleration applied to each particle. Tangential acceleration is perpendicular to the particle's velocity giving the particles a swirling motion. - </member> <member name="tangential_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's tangential acceleration will vary along this [Curve]. </member> - <member name="tangential_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Tangential acceleration randomness ratio. + <member name="tangential_accel_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + </member> + <member name="tangential_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> <member name="texture" type="Texture2D" setter="set_texture" getter="get_texture"> Particle texture. If [code]null[/code], particles will be squares. @@ -278,40 +256,40 @@ Particles are drawn in order of remaining lifetime. </constant> <constant name="PARAM_INITIAL_LINEAR_VELOCITY" value="0" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set initial velocity properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set initial velocity properties. </constant> <constant name="PARAM_ANGULAR_VELOCITY" value="1" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set angular velocity properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set angular velocity properties. </constant> <constant name="PARAM_ORBIT_VELOCITY" value="2" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set orbital velocity properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set orbital velocity properties. </constant> <constant name="PARAM_LINEAR_ACCEL" value="3" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set linear acceleration properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set linear acceleration properties. </constant> <constant name="PARAM_RADIAL_ACCEL" value="4" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set radial acceleration properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set radial acceleration properties. </constant> <constant name="PARAM_TANGENTIAL_ACCEL" value="5" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set tangential acceleration properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set tangential acceleration properties. </constant> <constant name="PARAM_DAMPING" value="6" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set damping properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set damping properties. </constant> <constant name="PARAM_ANGLE" value="7" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set angle properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set angle properties. </constant> <constant name="PARAM_SCALE" value="8" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set scale properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set scale properties. </constant> <constant name="PARAM_HUE_VARIATION" value="9" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set hue variation properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set hue variation properties. </constant> <constant name="PARAM_ANIM_SPEED" value="10" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set animation speed properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set animation speed properties. </constant> <constant name="PARAM_ANIM_OFFSET" value="11" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set animation offset properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set animation offset properties. </constant> <constant name="PARAM_MAX" value="12" enum="Parameter"> Represents the size of the [enum Parameter] enum. diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index 8aa3573996..fe8c354427 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -17,25 +17,23 @@ Sets this node's properties to match a given [GPUParticles3D] node with an assigned [ParticlesMaterial]. </description> </method> - <method name="get_param" qualifiers="const"> - <return type="float" /> + <method name="get_param_curve" qualifiers="const"> + <return type="Curve" /> <argument index="0" name="param" type="int" enum="CPUParticles3D.Parameter" /> <description> - Returns the base value of the parameter specified by [enum Parameter]. + Returns the [Curve] of the parameter specified by [enum Parameter]. </description> </method> - <method name="get_param_curve" qualifiers="const"> - <return type="Curve" /> + <method name="get_param_max" qualifiers="const"> + <return type="float" /> <argument index="0" name="param" type="int" enum="CPUParticles3D.Parameter" /> <description> - Returns the [Curve] of the parameter specified by [enum Parameter]. </description> </method> - <method name="get_param_randomness" qualifiers="const"> + <method name="get_param_min" qualifiers="const"> <return type="float" /> <argument index="0" name="param" type="int" enum="CPUParticles3D.Parameter" /> <description> - Returns the randomness factor of the parameter specified by [enum Parameter]. </description> </method> <method name="get_particle_flag" qualifiers="const"> @@ -51,28 +49,28 @@ Restarts the particle emitter. </description> </method> - <method name="set_param"> + <method name="set_param_curve"> <return type="void" /> <argument index="0" name="param" type="int" enum="CPUParticles3D.Parameter" /> - <argument index="1" name="value" type="float" /> + <argument index="1" name="curve" type="Curve" /> <description> - Sets the base value of the parameter specified by [enum Parameter]. + Sets the [Curve] of the parameter specified by [enum Parameter]. </description> </method> - <method name="set_param_curve"> + <method name="set_param_max"> <return type="void" /> <argument index="0" name="param" type="int" enum="CPUParticles3D.Parameter" /> - <argument index="1" name="curve" type="Curve" /> + <argument index="1" name="value" type="float" /> <description> - Sets the [Curve] of the parameter specified by [enum Parameter]. + Sets the maximum value for the given parameter </description> </method> - <method name="set_param_randomness"> + <method name="set_param_min"> <return type="void" /> <argument index="0" name="param" type="int" enum="CPUParticles3D.Parameter" /> - <argument index="1" name="randomness" type="float" /> + <argument index="1" name="value" type="float" /> <description> - Sets the randomness factor of the parameter specified by [enum Parameter]. + Sets the minimum value for the given parameter </description> </method> <method name="set_particle_flag"> @@ -88,41 +86,41 @@ <member name="amount" type="int" setter="set_amount" getter="get_amount" default="8"> Number of particles emitted in one emission cycle. </member> - <member name="angle" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial rotation applied to each particle, in degrees. - </member> <member name="angle_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's rotation will be animated along this [Curve]. </member> - <member name="angle_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Rotation randomness ratio. + <member name="angle_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum angle. </member> - <member name="angular_velocity" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial angular velocity applied to each particle. Sets the speed of rotation of the particle. + <member name="angle_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum angle. </member> <member name="angular_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's angular velocity will vary along this [Curve]. </member> - <member name="angular_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Angular velocity randomness ratio. + <member name="angular_velocity_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum angular velocity. </member> - <member name="anim_offset" type="float" setter="set_param" getter="get_param" default="0.0"> - Particle animation offset. + <member name="angular_velocity_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum angular velocity. </member> <member name="anim_offset_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's animation offset will vary along this [Curve]. </member> - <member name="anim_offset_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Animation offset randomness ratio. + <member name="anim_offset_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum animation offset. </member> - <member name="anim_speed" type="float" setter="set_param" getter="get_param" default="0.0"> - Particle animation speed. + <member name="anim_offset_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum animation offset. </member> <member name="anim_speed_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's animation speed will vary along this [Curve]. </member> - <member name="anim_speed_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Animation speed randomness ratio. + <member name="anim_speed_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum particle animation speed. + </member> + <member name="anim_speed_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum particle animation speed. </member> <member name="color" type="Color" setter="set_color" getter="get_color" default="Color(1, 1, 1, 1)"> Each particle's initial color. To have particle display color in a [BaseMaterial3D] make sure to set [member BaseMaterial3D.vertex_color_use_as_albedo] to [code]true[/code]. @@ -130,14 +128,14 @@ <member name="color_ramp" type="Gradient" setter="set_color_ramp" getter="get_color_ramp"> Each particle's color will vary along this [GradientTexture] over its lifetime (multiplied with [member color]). </member> - <member name="damping" type="float" setter="set_param" getter="get_param" default="0.0"> - The rate at which particles lose velocity. - </member> <member name="damping_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Damping will vary along this [Curve]. </member> - <member name="damping_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Damping randomness ratio. + <member name="damping_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum damping. + </member> + <member name="damping_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum damping </member> <member name="direction" type="Vector3" setter="set_direction" getter="get_direction" default="Vector3(1, 0, 0)"> Unit vector specifying the particles' emission direction. @@ -193,20 +191,20 @@ <member name="gravity" type="Vector3" setter="set_gravity" getter="get_gravity" default="Vector3(0, -9.8, 0)"> Gravity applied to every particle. </member> - <member name="hue_variation" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial hue variation applied to each particle. - </member> <member name="hue_variation_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's hue will vary along this [Curve]. </member> - <member name="hue_variation_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Hue variation randomness ratio. + <member name="hue_variation_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum hue variation. </member> - <member name="initial_velocity" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial velocity magnitude for each particle. Direction comes from [member spread] and the node's orientation. + <member name="hue_variation_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum hue variation. </member> - <member name="initial_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Initial velocity randomness ratio. + <member name="initial_velocity_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum value of the initial velocity. + </member> + <member name="initial_velocity_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum value of the initial velocity. </member> <member name="lifetime" type="float" setter="set_lifetime" getter="get_lifetime" default="1.0"> Amount of time each particle will exist. @@ -214,14 +212,14 @@ <member name="lifetime_randomness" type="float" setter="set_lifetime_randomness" getter="get_lifetime_randomness" default="0.0"> Particle lifetime randomness ratio. </member> - <member name="linear_accel" type="float" setter="set_param" getter="get_param" default="0.0"> - Linear acceleration applied to each particle in the direction of motion. - </member> <member name="linear_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's linear acceleration will vary along this [Curve]. </member> - <member name="linear_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Linear acceleration randomness ratio. + <member name="linear_accel_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum linear acceleration. + </member> + <member name="linear_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum linear acceleration. </member> <member name="local_coords" type="bool" setter="set_use_local_coordinates" getter="get_use_local_coordinates" default="true"> If [code]true[/code], particles use the parent node's coordinate space. If [code]false[/code], they use global coordinates. @@ -232,15 +230,14 @@ <member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot" default="false"> If [code]true[/code], only one emission cycle occurs. If set [code]true[/code] during a cycle, emission will stop at the cycle's end. </member> - <member name="orbit_velocity" type="float" setter="set_param" getter="get_param"> - Orbital velocity applied to each particle. Makes the particles circle around origin in the local XY plane. Specified in number of full rotations around origin per second. - This property is only available when [member particle_flag_disable_z] is [code]true[/code]. - </member> <member name="orbit_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's orbital velocity will vary along this [Curve]. </member> - <member name="orbit_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> - Orbital velocity randomness ratio. + <member name="orbit_velocity_max" type="float" setter="set_param_max" getter="get_param_max"> + Maximum orbit velocity. + </member> + <member name="orbit_velocity_min" type="float" setter="set_param_min" getter="get_param_min"> + Minimum orbit velocity. </member> <member name="particle_flag_align_y" type="bool" setter="set_particle_flag" getter="get_particle_flag" default="false"> Align Y axis of particle with the direction of its velocity. @@ -249,46 +246,58 @@ If [code]true[/code], particles will not move on the Z axis. </member> <member name="particle_flag_rotate_y" type="bool" setter="set_particle_flag" getter="get_particle_flag" default="false"> - If [code]true[/code], particles rotate around Y axis by [member angle]. + If [code]true[/code], particles rotate around Y axis by [member angle_min]. </member> <member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time" default="0.0"> Particle system starts as if it had already run for this many seconds. </member> - <member name="radial_accel" type="float" setter="set_param" getter="get_param" default="0.0"> - Radial acceleration applied to each particle. Makes particle accelerate away from origin. - </member> <member name="radial_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's radial acceleration will vary along this [Curve]. </member> - <member name="radial_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Radial acceleration randomness ratio. + <member name="radial_accel_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum radial acceleration. + </member> + <member name="radial_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum radial acceleration. </member> <member name="randomness" type="float" setter="set_randomness_ratio" getter="get_randomness_ratio" default="0.0"> Emission lifetime randomness ratio. </member> - <member name="scale_amount" type="float" setter="set_param" getter="get_param" default="1.0"> - Initial scale applied to each particle. - </member> <member name="scale_amount_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's scale will vary along this [Curve]. </member> - <member name="scale_amount_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Scale randomness ratio. + <member name="scale_amount_max" type="float" setter="set_param_max" getter="get_param_max" default="1.0"> + Maximum scale. + </member> + <member name="scale_amount_min" type="float" setter="set_param_min" getter="get_param_min" default="1.0"> + Minimum scale. + </member> + <member name="scale_curve_x" type="Curve" setter="set_scale_curve_x" getter="get_scale_curve_x"> + Curve for the scale over life, along the x axis. + </member> + <member name="scale_curve_y" type="Curve" setter="set_scale_curve_y" getter="get_scale_curve_y"> + Curve for the scale over life, along the y axis. + </member> + <member name="scale_curve_z" type="Curve" setter="set_scale_curve_z" getter="get_scale_curve_z"> + Curve for the scale over life, along the z axis. </member> <member name="speed_scale" type="float" setter="set_speed_scale" getter="get_speed_scale" default="1.0"> Particle system's running speed scaling ratio. A value of [code]0[/code] can be used to pause the particles. </member> + <member name="split_scale" type="bool" setter="set_split_scale" getter="get_split_scale" default="false"> + If set to true, three different scale curves can be specified, one per scale axis. + </member> <member name="spread" type="float" setter="set_spread" getter="get_spread" default="45.0"> Each particle's initial direction range from [code]+spread[/code] to [code]-spread[/code] degrees. Applied to X/Z plane and Y/Z planes. </member> - <member name="tangential_accel" type="float" setter="set_param" getter="get_param" default="0.0"> - Tangential acceleration applied to each particle. Tangential acceleration is perpendicular to the particle's velocity giving the particles a swirling motion. - </member> <member name="tangential_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> Each particle's tangential acceleration will vary along this [Curve]. </member> - <member name="tangential_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Tangential acceleration randomness ratio. + <member name="tangential_accel_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum tangent acceleration. + </member> + <member name="tangential_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum tangent acceleration. </member> </members> <constants> @@ -302,40 +311,40 @@ Particles are drawn in order of depth. </constant> <constant name="PARAM_INITIAL_LINEAR_VELOCITY" value="0" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set initial velocity properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set initial velocity properties. </constant> <constant name="PARAM_ANGULAR_VELOCITY" value="1" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set angular velocity properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set angular velocity properties. </constant> <constant name="PARAM_ORBIT_VELOCITY" value="2" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set orbital velocity properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set orbital velocity properties. </constant> <constant name="PARAM_LINEAR_ACCEL" value="3" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set linear acceleration properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set linear acceleration properties. </constant> <constant name="PARAM_RADIAL_ACCEL" value="4" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set radial acceleration properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set radial acceleration properties. </constant> <constant name="PARAM_TANGENTIAL_ACCEL" value="5" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set tangential acceleration properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set tangential acceleration properties. </constant> <constant name="PARAM_DAMPING" value="6" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set damping properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set damping properties. </constant> <constant name="PARAM_ANGLE" value="7" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set angle properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set angle properties. </constant> <constant name="PARAM_SCALE" value="8" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set scale properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set scale properties. </constant> <constant name="PARAM_HUE_VARIATION" value="9" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set hue variation properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set hue variation properties. </constant> <constant name="PARAM_ANIM_SPEED" value="10" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set animation speed properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set animation speed properties. </constant> <constant name="PARAM_ANIM_OFFSET" value="11" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_curve] to set animation offset properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set animation offset properties. </constant> <constant name="PARAM_MAX" value="12" enum="Parameter"> Represents the size of the [enum Parameter] enum. diff --git a/doc/classes/Camera2D.xml b/doc/classes/Camera2D.xml index d0ff66ae06..a3a891cdcb 100644 --- a/doc/classes/Camera2D.xml +++ b/doc/classes/Camera2D.xml @@ -5,6 +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. + Cameras register themselves in the nearest [Viewport] node (when ascending the tree). Only one camera can be active per viewport. If no viewport is available ascending the tree, the camera will register in the global 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> @@ -81,7 +82,7 @@ The Camera2D's anchor point. See [enum AnchorMode] constants. </member> <member name="current" type="bool" setter="set_current" getter="is_current" default="false"> - If [code]true[/code], the camera is the active camera for the current scene. Only one camera can be current, so setting a different camera [code]current[/code] will disable this one. + If [code]true[/code], the camera acts as the active camera for its [Viewport] ancestor. Only one camera can be current in a given viewport, so setting a different camera in the same viewport [code]current[/code] will disable whatever camera was already active in that viewport. </member> <member name="custom_viewport" type="Node" setter="set_custom_viewport" getter="get_custom_viewport"> The custom [Viewport] node attached to the [Camera2D]. If [code]null[/code] or not a [Viewport], uses the default viewport instead. diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index 2c92ce0185..4641bc52a4 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -106,6 +106,20 @@ Draws a [Mesh] in 2D, using the provided texture. See [MeshInstance2D] for related documentation. </description> </method> + <method name="draw_msdf_texture_rect_region"> + <return type="void" /> + <argument index="0" name="texture" type="Texture2D" /> + <argument index="1" name="rect" type="Rect2" /> + <argument index="2" name="src_rect" type="Rect2" /> + <argument index="3" name="modulate" type="Color" default="Color(1, 1, 1, 1)" /> + <argument index="4" name="outline" type="float" default="0.0" /> + <argument index="5" name="pixel_range" type="float" default="4.0" /> + <description> + Draws a textured rectangle region of the multi-channel signed distance field texture at a given position, optionally modulated by a color. + If [code]outline[/code] is positive, each alpha channel value of pixel in region is set to maximum value of true distance in the [code]outline[/code] radius. + Value of the [code]pixel_range[/code] should the same that was used during distance field texture generation. + </description> + </method> <method name="draw_multiline"> <return type="void" /> <argument index="0" name="points" type="PackedVector2Array" /> diff --git a/doc/classes/CanvasItemMaterial.xml b/doc/classes/CanvasItemMaterial.xml index c2d44c1d17..780899bff7 100644 --- a/doc/classes/CanvasItemMaterial.xml +++ b/doc/classes/CanvasItemMaterial.xml @@ -30,7 +30,7 @@ [b]Note:[/b] This property is only used and visible in the editor if [member particles_animation] is [code]true[/code]. </member> <member name="particles_animation" type="bool" setter="set_particles_animation" getter="get_particles_animation" default="false"> - If [code]true[/code], enable spritesheet-based animation features when assigned to [GPUParticles2D] and [CPUParticles2D] nodes. The [member ParticlesMaterial.anim_speed] or [member CPUParticles2D.anim_speed] should also be set to a positive value for the animation to play. + If [code]true[/code], enable spritesheet-based animation features when assigned to [GPUParticles2D] and [CPUParticles2D] nodes. The [member ParticlesMaterial.anim_speed_max] or [member CPUParticles2D.anim_speed_max] should also be set to a positive value for the animation to play. This property (and other [code]particles_anim_*[/code] properties that depend on it) has no effect on other types of nodes. </member> </members> diff --git a/doc/classes/CharacterBody2D.xml b/doc/classes/CharacterBody2D.xml index e5f60541b9..f98c22a1e9 100644 --- a/doc/classes/CharacterBody2D.xml +++ b/doc/classes/CharacterBody2D.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CharacterBody2D" inherits="PhysicsBody2D" version="4.0"> <brief_description> - Character body 2D node. + Specialized 2D physics body node for characters moved by script. </brief_description> <description> - Character bodies are special types of bodies that are meant to be user-controlled. They are not affected by physics at all; to other types of bodies, such as a rigid body, these are the same as a static body. However, they have two main uses: - [b]Kinematic characters:[/b] Character bodies have an API for moving objects with walls and slopes detection ([method move_and_slide] method), in addition to collision detection (also done with [method PhysicsBody3D.move_and_collide]). This makes them really useful to implement characters that move in specific ways and collide with the world, but don't require advanced physics. - [b]Kinematic motion:[/b] Character bodies can also be used for kinematic motion (same functionality as [member StaticBody3D.kinematic_motion] when enabled), which allows them to be moved by code and push other bodies on their path. + Character bodies are special types of bodies that are meant to be user-controlled. They are not affected by physics at all; to other types of bodies, such as a rigid body, these are the same as a [AnimatableBody2D]. However, they have two main uses: + [b]Kinematic characters:[/b] Character bodies have an API for moving objects with walls and slopes detection ([method move_and_slide] method), in addition to collision detection (also done with [method PhysicsBody2D.move_and_collide]). This makes them really useful to implement characters that move in specific ways and collide with the world, but don't require advanced physics. + [b]Kinematic motion:[/b] Character bodies can also be used for kinematic motion (same functionality as [AnimatableBody2D]), which allows them to be moved by code and push other bodies on their path. </description> <tutorials> <link title="Kinematic character (2D)">https://docs.godotengine.org/en/latest/tutorials/physics/kinematic_character_2d.html</link> @@ -152,8 +152,11 @@ <member name="motion_mode" type="int" setter="set_motion_mode" getter="get_motion_mode" enum="CharacterBody2D.MotionMode" default="0"> Sets the motion mode which defines the behaviour of [method move_and_slide]. See [enum MotionMode] constants for available modes. </member> - <member name="moving_platform_ignore_layers" type="int" setter="set_moving_platform_ignore_layers" getter="get_moving_platform_ignore_layers" default="0"> - Collision layers that will be excluded for detecting bodies that will act as moving platforms to be followed by the [CharacterBody2D]. By default, all touching bodies are detected and propagate their velocity. You can add excluded layers to ignore bodies that are contained in these layers. + <member name="moving_platform_floor_layers" type="int" setter="set_moving_platform_floor_layers" getter="get_moving_platform_floor_layers" default="4294967295"> + Collision layers that will be included for detecting floor bodies that will act as moving platforms to be followed by the [CharacterBody2D]. By default, all floor bodies are detected and propagate their velocity. + </member> + <member name="moving_platform_wall_layers" type="int" setter="set_moving_platform_wall_layers" getter="get_moving_platform_wall_layers" default="0"> + Collision layers that will be included for detecting wall bodies that will act as moving platforms to be followed by the [CharacterBody2D]. By default, all wall bodies are ignored. </member> <member name="slide_on_ceiling" type="bool" setter="set_slide_on_ceiling_enabled" getter="is_slide_on_ceiling_enabled" default="true"> If [code]true[/code], during a jump against the ceiling, the body will slide, if [code]false[/code] it will be stopped and will fall vertically. diff --git a/doc/classes/CharacterBody3D.xml b/doc/classes/CharacterBody3D.xml index 85135d5509..81ffbe01c1 100644 --- a/doc/classes/CharacterBody3D.xml +++ b/doc/classes/CharacterBody3D.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CharacterBody3D" inherits="PhysicsBody3D" version="4.0"> <brief_description> - Character body 3D node. + Specialized 3D physics body node for characters moved by script. </brief_description> <description> - Character bodies are special types of bodies that are meant to be user-controlled. They are not affected by physics at all; to other types of bodies, such as a rigid body, these are the same as a static body. However, they have two main uses: + Character bodies are special types of bodies that are meant to be user-controlled. They are not affected by physics at all; to other types of bodies, such as a rigid body, these are the same as a [AnimatableBody3D]. However, they have two main uses: [b]Kinematic characters:[/b] Character bodies have an API for moving objects with walls and slopes detection ([method move_and_slide] method), in addition to collision detection (also done with [method PhysicsBody3D.move_and_collide]). This makes them really useful to implement characters that move in specific ways and collide with the world, but don't require advanced physics. - [b]Kinematic motion:[/b] Character bodies can also be used for kinematic motion (same functionality as [member StaticBody3D.kinematic_motion] when enabled), which allows them to be moved by code and push other bodies on their path. + [b]Kinematic motion:[/b] Character bodies can also be used for kinematic motion (same functionality as [AnimatableBody3D]), which allows them to be moved by code and push other bodies on their path. </description> <tutorials> <link title="Kinematic character (2D)">https://docs.godotengine.org/en/latest/tutorials/physics/kinematic_character_2d.html</link> diff --git a/doc/classes/CollisionObject2D.xml b/doc/classes/CollisionObject2D.xml index 7129c72e7c..ba1ee3909d 100644 --- a/doc/classes/CollisionObject2D.xml +++ b/doc/classes/CollisionObject2D.xml @@ -11,7 +11,7 @@ <methods> <method name="_input_event" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="viewport" type="Object" /> + <argument index="0" name="viewport" type="Viewport" /> <argument index="1" name="event" type="InputEvent" /> <argument index="2" name="shape_idx" type="int" /> <description> diff --git a/doc/classes/CollisionObject3D.xml b/doc/classes/CollisionObject3D.xml index f9151a2c2f..19bcdcbb27 100644 --- a/doc/classes/CollisionObject3D.xml +++ b/doc/classes/CollisionObject3D.xml @@ -11,7 +11,7 @@ <methods> <method name="_input_event" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="camera" type="Object" /> + <argument index="0" name="camera" type="Camera3D" /> <argument index="1" name="event" type="InputEvent" /> <argument index="2" name="position" type="Vector3" /> <argument index="3" name="normal" type="Vector3" /> diff --git a/doc/classes/ColorPicker.xml b/doc/classes/ColorPicker.xml index 99e121de75..571ffd592a 100644 --- a/doc/classes/ColorPicker.xml +++ b/doc/classes/ColorPicker.xml @@ -116,7 +116,7 @@ </theme_item> <theme_item name="picker_cursor" data_type="icon" type="Texture2D"> </theme_item> - <theme_item name="preset_bg" data_type="icon" type="Texture2D"> + <theme_item name="sample_bg" data_type="icon" type="Texture2D"> </theme_item> <theme_item name="screen_picker" data_type="icon" type="Texture2D"> The icon for the screen color picker button. diff --git a/doc/classes/ConfigFile.xml b/doc/classes/ConfigFile.xml index f970be23a6..249e2a8f80 100644 --- a/doc/classes/ConfigFile.xml +++ b/doc/classes/ConfigFile.xml @@ -86,6 +86,7 @@ [/codeblocks] Keep in mind that section and property names can't contain spaces. Anything after a space will be ignored on save and on load. ConfigFiles can also contain manually written comment lines starting with a semicolon ([code];[/code]). Those lines will be ignored when parsing the file. Note that comments will be lost when saving the ConfigFile. This can still be useful for dedicated server configuration files, which are typically never overwritten without explicit user action. + [b]Note:[/b] The file extension given to a ConfigFile does not have any impact on its formatting or behavior. By convention, the [code].cfg[/code] extension is used here, but any other extension such as [code].ini[/code] is also valid. Since neither [code].cfg[/code] nor [code].ini[/code] are standardized, Godot's ConfigFile formatting may differ from files written by other programs. </description> <tutorials> </tutorials> @@ -149,6 +150,8 @@ </method> <method name="load"> <return type="int" enum="Error" /> + <returns_error number="0"/> + <returns_error number="12"/> <argument index="0" name="path" type="String" /> <description> Loads the config file specified as a parameter. The file's contents are parsed and loaded in the [ConfigFile] object which the method was called on. diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 5392189f6a..6602764cd4 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -921,7 +921,8 @@ Anchors the top edge of the node to the origin, the center or the end of its parent control. It changes how the top offset updates when the node moves or changes size. You can use one of the [enum Anchor] constants for convenience. </member> <member name="auto_translate" type="bool" setter="set_auto_translate" getter="is_auto_translating" default="true"> - Toggles if any text should automatically change to its translated version depending on the current locale. + Toggles if any text should automatically change to its translated version depending on the current locale. Note that this will not affect any internal nodes (e.g. the popup of a [MenuButton]). + Also decides if the node's strings should be parsed for POT generation. </member> <member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" enum="Control.FocusMode" default="0"> The focus access mode for the control (None, Click or All). Only one Control can be focused at the same time, and it will receive keyboard signals. diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index e564e8045c..b8436be76a 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -56,11 +56,11 @@ Called by the engine when the 3D editor's viewport is updated. Use the [code]overlay[/code] [Control] for drawing. You can update the viewport manually by calling [method update_overlays]. [codeblocks] [gdscript] - func _forward_spatial_3d_over_viewport(overlay): + func _forward_3d_draw_over_viewport(overlay): # Draw a circle at cursor position. overlay.draw_circle(overlay.get_local_mouse_position(), 64) - func _forward_spatial_gui_input(camera, event): + func _forward_3d_gui_input(camera, event): if event is InputEventMouseMotion: # Redraw viewport when cursor is moved. update_overlays() @@ -68,13 +68,13 @@ return false [/gdscript] [csharp] - public override void ForwardSpatialDrawOverViewport(Godot.Control overlay) + public override void _Forward3dDrawOverViewport(Godot.Control overlay) { // Draw a circle at cursor position. overlay.DrawCircle(overlay.GetLocalMousePosition(), 64, Colors.White); } - public override bool ForwardSpatialGuiInput(Godot.Camera3D camera, InputEvent @event) + public override bool _Forward3dGuiInput(Godot.Camera3D camera, InputEvent @event) { if (@event is InputEventMouseMotion) { @@ -104,12 +104,12 @@ [codeblocks] [gdscript] # Prevents the InputEvent to reach other Editor classes. - func _forward_spatial_gui_input(camera, event): + func _forward_3d_gui_input(camera, event): return true [/gdscript] [csharp] // Prevents the InputEvent to reach other Editor classes. - public override bool ForwardSpatialGuiInput(Camera3D camera, InputEvent @event) + public override bool _Forward3dGuiInput(Camera3D camera, InputEvent @event) { return true; } @@ -119,12 +119,12 @@ [codeblocks] [gdscript] # Consumes InputEventMouseMotion and forwards other InputEvent types. - func _forward_spatial_gui_input(camera, event): + func _forward_3d_gui_input(camera, event): return event is InputEventMouseMotion [/gdscript] [csharp] // Consumes InputEventMouseMotion and forwards other InputEvent types. - public override bool ForwardSpatialGuiInput(Camera3D camera, InputEvent @event) + public override bool _Forward3dGuiInput(Camera3D camera, InputEvent @event) { return @event is InputEventMouseMotion; } diff --git a/doc/classes/EditorSceneImporterMesh.xml b/doc/classes/EditorSceneImporterMesh.xml index 3a9eea87bb..c0c53ff255 100644 --- a/doc/classes/EditorSceneImporterMesh.xml +++ b/doc/classes/EditorSceneImporterMesh.xml @@ -1,8 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="EditorSceneImporterMesh" inherits="Resource" version="4.0"> <brief_description> + A [Resource] that contains vertex array-based geometry during the import process. </brief_description> <description> + EditorSceneImporterMesh is a type of [Resource] analogous to [ArrayMesh]. It contains vertex array-based geometry, divided in [i]surfaces[/i]. Each surface contains a completely separate array and a material used to draw it. Design wise, a mesh with multiple surfaces is preferred to a single surface, because objects created in 3D editing software commonly contain multiple materials. + + Unlike its runtime counterpart, [EditorSceneImporterMesh] contains mesh data before various import steps, such as lod and shadow mesh generation, have taken place. Modify surface data by calling [method clear], followed by [method add_surface] for each surface. </description> <tutorials> </tutorials> @@ -11,6 +15,7 @@ <return type="void" /> <argument index="0" name="name" type="String" /> <description> + Adds name for a blend shape that will be added with [method add_surface]. Must be called before surface is added. </description> </method> <method name="add_surface"> @@ -22,45 +27,58 @@ }" /> <argument index="4" name="material" type="Material" default="null" /> <argument index="5" name="name" type="String" default="""" /> + <argument index="6" name="flags" type="int" default="0" /> <description> + Creates a new surface, analogous to [method ArrayMesh.add_surface_from_arrays]. + 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> <method name="clear"> <return type="void" /> <description> + Removes all surfaces and blend shapes from this [EditorSceneImporterMesh]. </description> </method> <method name="get_blend_shape_count" qualifiers="const"> <return type="int" /> <description> + Returns the number of blend shapes that the mesh holds. </description> </method> <method name="get_blend_shape_mode" qualifiers="const"> <return type="int" enum="Mesh.BlendShapeMode" /> <description> + Returns the blend shape mode for this Mesh. </description> </method> <method name="get_blend_shape_name" qualifiers="const"> <return type="String" /> <argument index="0" name="blend_shape_idx" type="int" /> <description> + Returns the name of the blend shape at this index. </description> </method> <method name="get_lightmap_size_hint" qualifiers="const"> <return type="Vector2i" /> <description> + Returns the size hint of this mesh for lightmap-unwrapping in UV-space. </description> </method> <method name="get_mesh"> <return type="ArrayMesh" /> - <argument index="0" name="arg0" type="Mesh" /> + <argument index="0" name="base_mesh" type="ArrayMesh" default="null" /> <description> + Returns the mesh data represented by this [EditorSceneImporterMesh] as a usable [ArrayMesh]. + This method caches the returned mesh, and subsequent calls will return the cached data until [method clear] is called. + If not yet cached and [code]base_mesh[/code] is provided, [code]base_mesh[/code] will be used and mutated. </description> </method> <method name="get_surface_arrays" qualifiers="const"> <return type="Array" /> <argument index="0" name="surface_idx" type="int" /> <description> + Returns the arrays for the vertices, normals, uvs, etc. that make up the requested surface. See [method add_surface]. </description> </method> <method name="get_surface_blend_shape_arrays" qualifiers="const"> @@ -68,17 +86,27 @@ <argument index="0" name="surface_idx" type="int" /> <argument index="1" name="blend_shape_idx" type="int" /> <description> + Returns a single set of blend shape arrays for the requested blend shape index for a surface. </description> </method> <method name="get_surface_count" qualifiers="const"> <return type="int" /> <description> + Returns the amount of surfaces that the mesh holds. + </description> + </method> + <method name="get_surface_format" qualifiers="const"> + <return type="int" /> + <argument index="0" name="surface_idx" type="int" /> + <description> + Returns the format of the surface that the mesh holds. </description> </method> <method name="get_surface_lod_count" qualifiers="const"> <return type="int" /> <argument index="0" name="surface_idx" type="int" /> <description> + Returns the amount of lods that the mesh holds on a given surface. </description> </method> <method name="get_surface_lod_indices" qualifiers="const"> @@ -86,6 +114,7 @@ <argument index="0" name="surface_idx" type="int" /> <argument index="1" name="lod_idx" type="int" /> <description> + Returns the index buffer of a lod for a surface. </description> </method> <method name="get_surface_lod_size" qualifiers="const"> @@ -93,36 +122,58 @@ <argument index="0" name="surface_idx" type="int" /> <argument index="1" name="lod_idx" type="int" /> <description> + Returns the screen ratio which activates a lod for a surface. </description> </method> <method name="get_surface_material" qualifiers="const"> <return type="Material" /> <argument index="0" name="surface_idx" type="int" /> <description> + Returns a [Material] in a given surface. Surface is rendered using this material. </description> </method> <method name="get_surface_name" qualifiers="const"> <return type="String" /> <argument index="0" name="surface_idx" type="int" /> <description> + Gets the name assigned to this surface. </description> </method> <method name="get_surface_primitive_type"> <return type="int" enum="Mesh.PrimitiveType" /> <argument index="0" name="surface_idx" type="int" /> <description> + Returns the primitive type of the requested surface (see [method add_surface]). </description> </method> <method name="set_blend_shape_mode"> <return type="void" /> <argument index="0" name="mode" type="int" enum="Mesh.BlendShapeMode" /> <description> + Sets the blend shape mode to one of [enum Mesh.BlendShapeMode]. </description> </method> <method name="set_lightmap_size_hint"> <return type="void" /> <argument index="0" name="size" type="Vector2i" /> <description> + Sets the size hint of this mesh for lightmap-unwrapping in UV-space. + </description> + </method> + <method name="set_surface_material"> + <return type="void" /> + <argument index="0" name="surface_idx" type="int" /> + <argument index="1" name="material" type="Material" /> + <description> + Sets a [Material] for a given surface. Surface will be rendered using this material. + </description> + </method> + <method name="set_surface_name"> + <return type="void" /> + <argument index="0" name="surface_idx" type="int" /> + <argument index="1" name="name" type="String" /> + <description> + Sets a name for a given surface. </description> </method> </methods> diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index 886a18900e..36590093bd 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -37,7 +37,7 @@ <method name="get_frames_drawn"> <return type="int" /> <description> - Returns the total number of frames drawn. If the render loop is disabled with [code]--disable-render-loop[/code] via command line, this returns [code]0[/code]. See also [method get_process_frames]. + Returns the total number of frames drawn. On headless platforms, or if the render loop is disabled with [code]--disable-render-loop[/code] via command line, [method get_frames_drawn] always returns [code]0[/code]. See [method get_process_frames]. </description> </method> <method name="get_frames_per_second" qualifiers="const"> @@ -67,7 +67,13 @@ <method name="get_physics_frames" qualifiers="const"> <return type="int" /> <description> - Returns the total number of frames passed since engine initialization which is advanced on each [b]physics frame[/b]. + Returns the total number of frames passed since engine initialization which is advanced on each [b]physics frame[/b]. See also [method get_process_frames]. + [method get_physics_frames] can be used to run expensive logic less often without relying on a [Timer]: + [codeblock] + func _physics_process(_delta): + if Engine.get_physics_frames() % 2 == 0: + pass # Run expensive logic only once every 2 physics frames here. + [/codeblock] </description> </method> <method name="get_physics_interpolation_fraction" qualifiers="const"> @@ -79,16 +85,27 @@ <method name="get_process_frames" qualifiers="const"> <return type="int" /> <description> - Returns the total number of frames passed since engine initialization which is advanced on each [b]process frame[/b], regardless of whether the render loop is enabled. See also [method get_frames_drawn]. + Returns the total number of frames passed since engine initialization which is advanced on each [b]process frame[/b], regardless of whether the render loop is enabled. See also [method get_frames_drawn] and [method get_physics_frames]. + [method get_process_frames] can be used to run expensive logic less often without relying on a [Timer]: + [codeblock] + func _process(_delta): + if Engine.get_process_frames() % 2 == 0: + pass # Run expensive logic only once every 2 process (render) frames here. + [/codeblock] </description> </method> <method name="get_singleton" qualifiers="const"> <return type="Object" /> - <argument index="0" name="name" type="String" /> + <argument index="0" name="name" type="StringName" /> <description> Returns a global singleton with given [code]name[/code]. Often used for plugins, e.g. GodotPayments. </description> </method> + <method name="get_singleton_list" qualifiers="const"> + <return type="PackedStringArray" /> + <description> + </description> + </method> <method name="get_version_info" qualifiers="const"> <return type="Dictionary" /> <description> @@ -125,7 +142,7 @@ </method> <method name="has_singleton" qualifiers="const"> <return type="bool" /> - <argument index="0" name="name" type="String" /> + <argument index="0" name="name" type="StringName" /> <description> Returns [code]true[/code] if a singleton with given [code]name[/code] exists in global scope. </description> @@ -136,6 +153,19 @@ Returns [code]true[/code] if the game is inside the fixed process and physics phase of the game loop. </description> </method> + <method name="register_singleton"> + <return type="void" /> + <argument index="0" name="name" type="StringName" /> + <argument index="1" name="instance" type="Object" /> + <description> + </description> + </method> + <method name="unregister_singleton"> + <return type="void" /> + <argument index="0" name="name" type="StringName" /> + <description> + </description> + </method> </methods> <members> <member name="editor_hint" type="bool" setter="set_editor_hint" getter="is_editor_hint" default="true"> diff --git a/doc/classes/File.xml b/doc/classes/File.xml index 6622619fb3..8ecdc8b220 100644 --- a/doc/classes/File.xml +++ b/doc/classes/File.xml @@ -438,7 +438,7 @@ Opens the file for read and write operations. The file is created if it does not exist, and truncated if it does. The cursor is positioned at the beginning of the file. </constant> <constant name="COMPRESSION_FASTLZ" value="0" enum="CompressionMode"> - Uses the [url=http://fastlz.org/]FastLZ[/url] compression method. + Uses the [url=https://fastlz.org/]FastLZ[/url] compression method. </constant> <constant name="COMPRESSION_DEFLATE" value="1" enum="CompressionMode"> Uses the [url=https://en.wikipedia.org/wiki/DEFLATE]DEFLATE[/url] compression method. diff --git a/doc/classes/Font.xml b/doc/classes/Font.xml index 06dcaca846..aa70856e32 100644 --- a/doc/classes/Font.xml +++ b/doc/classes/Font.xml @@ -70,6 +70,12 @@ Add font data source to the set. </description> </method> + <method name="clear_data"> + <return type="void" /> + <description> + Removes all font data sourcers for the set. + </description> + </method> <method name="draw_char" qualifiers="const"> <return type="float" /> <argument index="0" name="canvas_item" type="RID" /> @@ -151,6 +157,13 @@ Returns the number of font data sources. </description> </method> + <method name="get_data_rid" qualifiers="const"> + <return type="RID" /> + <argument index="0" name="idx" type="int" /> + <description> + Returns TextServer RID of the font data resources. + </description> + </method> <method name="get_descent" qualifiers="const"> <return type="float" /> <argument index="0" name="size" type="int" default="-1" /> @@ -180,15 +193,18 @@ </method> <method name="get_spacing" qualifiers="const"> <return type="int" /> - <argument index="0" name="type" type="int" /> + <argument index="0" name="spacing" type="int" enum="TextServer.SpacingType" /> <description> - Returns the spacing for the given [code]type[/code] (see [enum SpacingType]). + Returns the spacing for the given [code]type[/code] (see [enum TextServer.SpacingType]). </description> </method> <method name="get_string_size" qualifiers="const"> <return type="Vector2" /> <argument index="0" name="text" type="String" /> <argument index="1" name="size" type="int" default="-1" /> + <argument index="2" name="align" type="int" enum="HAlign" default="0" /> + <argument index="3" name="width" type="float" default="-1" /> + <argument index="4" name="flags" type="int" default="3" /> <description> Returns the size of a bounding box of a string, taking kerning and advance into account. [b]Note:[/b] Real height of the string is context-dependent and can be significantly different from the value returned by [method get_height]. @@ -242,10 +258,10 @@ </method> <method name="set_spacing"> <return type="void" /> - <argument index="0" name="type" type="int" /> + <argument index="0" name="spacing" type="int" enum="TextServer.SpacingType" /> <argument index="1" name="value" type="int" /> <description> - Sets the spacing for [code]type[/code] (see [enum SpacingType]) to [code]value[/code] in pixels (not relative to the font size). + Sets the spacing for [code]type[/code] (see [enum TextServer.SpacingType]) to [code]value[/code] in pixels (not relative to the font size). </description> </method> <method name="update_changes"> @@ -256,19 +272,19 @@ </method> </methods> <members> - <member name="extra_spacing_bottom" type="int" setter="set_spacing" getter="get_spacing" default="0"> + <member name="base_size" type="int" setter="set_base_size" getter="get_base_size" default="16"> + Default font size. + </member> + <member name="spacing_bottom" type="int" setter="set_spacing" getter="get_spacing" default="0"> Extra spacing at the bottom of the line in pixels. </member> - <member name="extra_spacing_top" type="int" setter="set_spacing" getter="get_spacing" default="0"> + <member name="spacing_top" type="int" setter="set_spacing" getter="get_spacing" default="0"> Extra spacing at the top of the line in pixels. </member> + <member name="variation_coordinates" type="Dictionary" setter="set_variation_coordinates" getter="get_variation_coordinates" default="{}"> + Default font [url=https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg]variation coordinates[/url]. + </member> </members> <constants> - <constant name="SPACING_TOP" value="0" enum="SpacingType"> - Spacing at the top of the line. - </constant> - <constant name="SPACING_BOTTOM" value="1" enum="SpacingType"> - Spacing at the bottom of the line. - </constant> </constants> </class> diff --git a/doc/classes/FontData.xml b/doc/classes/FontData.xml index 7a845a698f..72af7ca485 100644 --- a/doc/classes/FontData.xml +++ b/doc/classes/FontData.xml @@ -1,122 +1,179 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="FontData" inherits="Resource" version="4.0"> <brief_description> - Font data source, file or memory buffer. + Font source data and prerendered glyph cache, imported from dynamic or bitmap font. + Supported font formats: + - Dynamic font importer: TrueType (.ttf), OpenType (.otf), WOFF (.woff), Type 1 (.pfb, .pfm). + - Bitmap font importer: AngelCode BMFont (.fnt, .font), text and binary (version 3) format variants. + - Monospace image font importer: All supported image formats. </brief_description> <description> - Built-in text servers support font data sources of the following formats: - - Bitmap fonts in the [url=https://www.angelcode.com/products/bmfont/]BMFont[/url] format. Handles [code].fnt, *.font[/code] fonts containing texture atlases. Non-scalable. Supports distance fields. Complex text shaping support is limited. - - Dynamic fonts using the [url=https://www.freetype.org/]FreeType[/url] and [url=https://github.com/silnrsi/graphite/]Graphite[/url] library for rasterization. Handles [code]*.ttf, *.otf[/code] fonts. Scalable. Doesn't support distance fields. Supports complex text shaping and OpenType features. </description> <tutorials> </tutorials> <methods> - <method name="bitmap_add_char"> + <method name="clear_cache"> <return type="void" /> - <argument index="0" name="char" type="int" /> - <argument index="1" name="texture_idx" type="int" /> - <argument index="2" name="rect" type="Rect2" /> - <argument index="3" name="align" type="Vector2" /> - <argument index="4" name="advance" type="float" /> <description> - Adds a character to the font, where [code]character[/code] is the Unicode value, [code]texture[/code] is the texture index, [code]rect[/code] is the region in the texture (in pixels!), [code]align[/code] is the (optional) alignment for the character and [code]advance[/code] is the (optional) advance. + Removes all font cache entries. </description> </method> - <method name="bitmap_add_kerning_pair"> + <method name="clear_glyphs"> <return type="void" /> - <argument index="0" name="A" type="int" /> - <argument index="1" name="B" type="int" /> - <argument index="2" name="kerning" type="int" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> <description> - Adds a kerning pair to the bitmap font as a difference. Kerning pairs are special cases where a typeface advance is determined by the next character. + Removes all rendered glyphs information from the cache entry. Note: This function will not remove textures associated with the glyphs, use [method remove_texture] to remove them manually. </description> </method> - <method name="bitmap_add_texture"> + <method name="clear_kerning_map"> <return type="void" /> - <argument index="0" name="texture" type="Texture" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> <description> - Adds a texture to the bitmap font. + Removes all kerning overrides. </description> </method> - <method name="draw_glyph" qualifiers="const"> - <return type="Vector2" /> - <argument index="0" name="canvas" type="RID" /> - <argument index="1" name="size" type="int" /> - <argument index="2" name="pos" type="Vector2" /> - <argument index="3" name="index" type="int" /> - <argument index="4" name="color" type="Color" default="Color(1, 1, 1, 1)" /> + <method name="clear_size_cache"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> <description> - Draws single glyph into a canvas item at the position, using [code]font[/code] at the size [code]size[/code]. - Returns advance of the glyph for horizontal and vertical layouts. - Note: Glyph index is bound to the font data, use only glyphs indices returned by [method TextServer.shaped_text_get_glyphs] or [method get_glyph_index] for this font data. + Removes all font sizes from the cache entry </description> </method> - <method name="draw_glyph_outline" qualifiers="const"> - <return type="Vector2" /> - <argument index="0" name="canvas" type="RID" /> - <argument index="1" name="size" type="int" /> - <argument index="2" name="outline_size" type="int" /> - <argument index="3" name="pos" type="Vector2" /> - <argument index="4" name="index" type="int" /> - <argument index="5" name="color" type="Color" default="Color(1, 1, 1, 1)" /> + <method name="clear_textures"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <description> + Removes all textures from font cache entry. Note: This function will not remove glyphs associated with the texture, use [method remove_glyph] to remove them manually. + </description> + </method> + <method name="find_cache" qualifiers="const"> + <return type="RID" /> + <argument index="0" name="variation_coordinates" type="Dictionary" /> <description> - Draws single glyph outline of size [code]outline_size[/code] into a canvas item at the position, using [code]font[/code] at the size [code]size[/code]. If outline drawing is not supported, nothing is drawn. - Returns advance of the glyph for horizontal and vertical layouts (regardless of outline drawing support). - Note: Glyph index is bound to the font data, use only glyphs indices returned by [method TextServer.shaped_text_get_glyphs] or [method get_glyph_index] for this font data. + Returns existing or creates a new font cache entry for the specified variation coordinates. </description> </method> <method name="get_ascent" qualifiers="const"> <return type="float" /> - <argument index="0" name="size" type="int" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> <description> Returns the font ascent (number of pixels above the baseline). </description> </method> - <method name="get_base_size" qualifiers="const"> - <return type="float" /> + <method name="get_cache_count" qualifiers="const"> + <return type="int" /> <description> - Returns the base size of the font (the only size supported for non-scalable fonts, meaningless for scalable fonts). + Returns number of the font cache entries. + </description> + </method> + <method name="get_cache_rid" qualifiers="const"> + <return type="RID" /> + <argument index="0" name="cache_index" type="int" /> + <description> + Returns text server font cache entry resource id. + </description> + </method> + <method name="get_data" qualifiers="const"> + <return type="PackedByteArray" /> + <description> + Returns contents of the dynamic font source file. </description> </method> <method name="get_descent" qualifiers="const"> <return type="float" /> - <argument index="0" name="size" type="int" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> <description> - Returns the font descent (number of pixels below the baseline). + Returns font descent (number of pixels below the baseline). </description> </method> <method name="get_glyph_advance" qualifiers="const"> <return type="Vector2" /> - <argument index="0" name="index" type="int" /> + <argument index="0" name="cache_index" type="int" /> <argument index="1" name="size" type="int" /> + <argument index="2" name="glyph" type="int" /> <description> - Returns advance of the glyph for horizontal and vertical layouts. - Note: Glyph index is bound to the font data, use only glyphs indices returned by [method TextServer.shaped_text_get_glyphs] or [method get_glyph_index] for this font data. + Returns glyph advance (offset of the next glyph). Note: advance for glyphs outlines is the same as the base glyph advance and is not saved. </description> </method> <method name="get_glyph_index" qualifiers="const"> <return type="int" /> <argument index="0" name="char" type="int" /> - <argument index="1" name="variation_selector" type="int" default="0" /> + <argument index="1" name="variation_selector" type="int" /> + <argument index="2" name="arg2" type="int" /> <description> - Return the glyph index of a [code]char[/code], optionally modified by the [code]variation_selector[/code]. + Returns the glyph index of a [code]char[/code], optionally modified by the [code]variation_selector[/code]. </description> </method> - <method name="get_glyph_kerning" qualifiers="const"> + <method name="get_glyph_list" qualifiers="const"> + <return type="Array" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <description> + Returns list of rendered glyphs in the cache entry. + </description> + </method> + <method name="get_glyph_offset" qualifiers="const"> <return type="Vector2" /> - <argument index="0" name="index_a" type="int" /> - <argument index="1" name="index_b" type="int" /> - <argument index="2" name="size" type="int" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> <description> - Returns a kerning of the pair of glyphs for horizontal and vertical layouts. - Note: Glyph index is bound to the font data, use only glyphs indices returned by [method TextServer.shaped_text_get_glyphs] or [method get_glyph_index] for this font data. + Returns glyph offset from the baseline. </description> </method> - <method name="get_height" qualifiers="const"> - <return type="float" /> - <argument index="0" name="size" type="int" /> + <method name="get_glyph_size" qualifiers="const"> + <return type="Vector2" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> <description> - Returns the total font height (ascent plus descent) in pixels. + Returns glyph size. + </description> + </method> + <method name="get_glyph_texture_idx" qualifiers="const"> + <return type="int" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <description> + Returns index of the cache texture containing the glyph. + </description> + </method> + <method name="get_glyph_uv_rect" qualifiers="const"> + <return type="Rect2" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <description> + Returns rectangle in the cache texture containing the glyph. + </description> + </method> + <method name="get_hinting" qualifiers="const"> + <return type="int" enum="TextServer.Hinting" /> + <description> + Returns the font hinting mode. Used by dynamic fonts only. + </description> + </method> + <method name="get_kerning" qualifiers="const"> + <return type="Vector2" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="glyph_pair" type="Vector2i" /> + <description> + Returns kerning for the pair of glyphs. + </description> + </method> + <method name="get_kerning_list" qualifiers="const"> + <return type="Array" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <description> + Returns list of the kerning overrides. </description> </method> <method name="get_language_support_override" qualifiers="const"> @@ -132,6 +189,32 @@ Returns list of language support overrides. </description> </method> + <method name="get_msdf_pixel_range" qualifiers="const"> + <return type="int" /> + <description> + Returns the width of the range around the shape between the minimum and maximum representable signed distance. + </description> + </method> + <method name="get_msdf_size" qualifiers="const"> + <return type="int" /> + <description> + Returns source font size used to generate MSDF textures. + </description> + </method> + <method name="get_oversampling" qualifiers="const"> + <return type="float" /> + <description> + Returns font oversampling factor, if set to [code]0.0[/code] global oversampling factor is used instead. Used by dynamic fonts only. + </description> + </method> + <method name="get_scale" qualifiers="const"> + <return type="float" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <description> + Returns scaling factor of the color bitmap font. + </description> + </method> <method name="get_script_support_override" qualifiers="const"> <return type="bool" /> <argument index="0" name="script" type="String" /> @@ -145,11 +228,20 @@ Returns list of script support overrides. </description> </method> + <method name="get_size_cache_list" qualifiers="const"> + <return type="Array" /> + <argument index="0" name="cache_index" type="int" /> + <description> + Return list of the font sizes in the cache. Each size is [code]Vector2i[/code] with font size and outline size. + </description> + </method> <method name="get_spacing" qualifiers="const"> <return type="int" /> - <argument index="0" name="type" type="int" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="arg2" type="int" enum="TextServer.SpacingType" /> <description> - Returns the spacing for the given [code]type[/code] (see [enum SpacingType]). + Returns extra spacing added between glyphs in pixels. </description> </method> <method name="get_supported_chars" qualifiers="const"> @@ -158,32 +250,66 @@ Returns a string containing all the characters available in the font. </description> </method> - <method name="get_underline_position" qualifiers="const"> - <return type="float" /> - <argument index="0" name="size" type="int" /> + <method name="get_supported_feature_list" qualifiers="const"> + <return type="Dictionary" /> <description> - Returns underline offset (number of pixels below the baseline). + Returns list of OpenType features supported by font. </description> </method> - <method name="get_underline_thickness" qualifiers="const"> + <method name="get_supported_variation_list" qualifiers="const"> + <return type="Dictionary" /> + <description> + Returns list of supported [url=https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg]variation coordinates[/url], each coordinate is returned as [code]tag: Vector3i(min_value,max_value,default_value)[/code]. + Font variations allow for continuous change of glyph characteristics along some given design axis, such as weight, width or slant. + </description> + </method> + <method name="get_texture_count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <description> + Returns number of textures used by font cache entry. + </description> + </method> + <method name="get_texture_image" qualifiers="const"> + <return type="Image" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="texture_index" type="int" /> + <description> + Returns a copy of the font cache texture image. + </description> + </method> + <method name="get_texture_offsets" qualifiers="const"> + <return type="PackedInt32Array" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="texture_index" type="int" /> + <description> + Returns a copy of the array containing the first free pixel in the each column of texture. Should be the same size as texture width or empty. + </description> + </method> + <method name="get_underline_position" qualifiers="const"> <return type="float" /> - <argument index="0" name="size" type="int" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> <description> - Returns underline thickness in pixels. + Returns pixel offset of the underline below the baseline. </description> </method> - <method name="get_variation" qualifiers="const"> + <method name="get_underline_thickness" qualifiers="const"> <return type="float" /> - <argument index="0" name="tag" type="String" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> <description> - Returns variation coordinate [code]tag[/code]. + Returns thickness of the underline in pixels. </description> </method> - <method name="get_variation_list" qualifiers="const"> + <method name="get_variation_coordinates" qualifiers="const"> <return type="Dictionary" /> + <argument index="0" name="cache_index" type="int" /> <description> - Returns list of supported [url=https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg]variation coordinates[/url], each coordinate is returned as [code]tag: Vector3i(min_value,max_value,default_value)[/code]. - Font variations allow for continuous change of glyph characteristics along some given design axis, such as weight, width or slant. + Returns variation coordinates for the specified font cache entry. See [method get_supported_variation_list] for more info. </description> </method> <method name="has_char" qualifiers="const"> @@ -193,10 +319,16 @@ Return [code]true[/code] if a Unicode [code]char[/code] is available in the font. </description> </method> - <method name="has_outline" qualifiers="const"> + <method name="is_antialiased" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if font 8-bit anitialiased glyph rendering is supported and enabled. + </description> + </method> + <method name="is_force_autohinter" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code], if font supports drawing glyph outlines. + Returns [code]true[/code] if auto-hinting is supported and preffered over font built-in hinting. Used by dynamic fonts only. </description> </method> <method name="is_language_supported" qualifiers="const"> @@ -206,6 +338,12 @@ Returns [code]true[/code], if font supports given language ([url=https://en.wikipedia.org/wiki/ISO_639-1]ISO 639[/url] code). </description> </method> + <method name="is_multichannel_signed_distance_field" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if glyphs of all sizes are rendered using single multichannel signed distance field generated from the dynamic font vector data. + </description> + </method> <method name="is_script_supported" qualifiers="const"> <return type="bool" /> <argument index="0" name="script" type="String" /> @@ -213,32 +351,29 @@ Returns [code]true[/code], if font supports given script ([url=https://en.wikipedia.org/wiki/ISO_15924]ISO 15924[/url] code). </description> </method> - <method name="load_memory"> + <method name="remove_cache"> <return type="void" /> - <argument index="0" name="data" type="PackedByteArray" /> - <argument index="1" name="type" type="String" /> - <argument index="2" name="base_size" type="int" default="16" /> + <argument index="0" name="cache_index" type="int" /> <description> - Creates new font from the data in memory. - Note: For non-scalable fonts [code]base_size[/code] is ignored, use [method get_base_size] to check actual font size. + Removes specified font cache entry. </description> </method> - <method name="load_resource"> + <method name="remove_glyph"> <return type="void" /> - <argument index="0" name="filename" type="String" /> - <argument index="1" name="base_size" type="int" default="16" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> <description> - Creates new font from the file. - Note: For non-scalable fonts [code]base_size[/code] is ignored, use [method get_base_size] to check actual font size. + Removes specified rendered glyph information from the cache entry. Note: This function will not remove textures associated with the glyphs, use [method remove_texture] to remove them manually. </description> </method> - <method name="new_bitmap"> + <method name="remove_kerning"> <return type="void" /> - <argument index="0" name="height" type="float" /> - <argument index="1" name="ascent" type="float" /> - <argument index="2" name="base_size" type="int" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="glyph_pair" type="Vector2i" /> <description> - Creates new, empty bitmap font. + Removes kerning override for the pair of glyphs. </description> </method> <method name="remove_language_support_override"> @@ -255,6 +390,148 @@ Removes script support override. </description> </method> + <method name="remove_size_cache"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <description> + Removes specified font size from the cache entry. + </description> + </method> + <method name="remove_texture"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="texture_index" type="int" /> + <description> + Removes specified texture from font cache entry. Note: This function will not remove glyphs associated with the texture, remove them manually, using [method remove_glyph]. + </description> + </method> + <method name="render_glyph"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="index" type="int" /> + <description> + Renders specified glyph the the font cache texture. + </description> + </method> + <method name="render_range"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="start" type="int" /> + <argument index="3" name="end" type="int" /> + <description> + Renders the range of characters to the font cache texture. + </description> + </method> + <method name="set_antialiased"> + <return type="void" /> + <argument index="0" name="antialiased" type="bool" /> + <description> + If set to [code]true[/code], 8-bit antialiased glyph rendering is used, otherwise 1-bit rendering is used. Used by dynamic fonts only. + </description> + </method> + <method name="set_ascent"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="ascent" type="float" /> + <description> + Sets the font ascent (number of pixels above the baseline). + </description> + </method> + <method name="set_data"> + <return type="void" /> + <argument index="0" name="data" type="PackedByteArray" /> + <description> + Sets font source data, e.g contents of the dynamic font source file. + </description> + </method> + <method name="set_descent"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="descent" type="float" /> + <description> + Sets the font descent (number of pixels below the baseline). + </description> + </method> + <method name="set_force_autohinter"> + <return type="void" /> + <argument index="0" name="force_autohinter" type="bool" /> + <description> + If set to [code]true[/code] auto-hinting is preffered over font built-in hinting. + </description> + </method> + <method name="set_glyph_advance"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="glyph" type="int" /> + <argument index="3" name="advance" type="Vector2" /> + <description> + Sets glyph advance (offset of the next glyph). Note: advance for glyphs outlines is the same as the base glyph advance and is not saved. + </description> + </method> + <method name="set_glyph_offset"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <argument index="3" name="offset" type="Vector2" /> + <description> + Sets glyph offset from the baseline. + </description> + </method> + <method name="set_glyph_size"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <argument index="3" name="gl_size" type="Vector2" /> + <description> + Sets glyph size. + </description> + </method> + <method name="set_glyph_texture_idx"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <argument index="3" name="texture_idx" type="int" /> + <description> + Sets index of the cache texture containing the glyph. + </description> + </method> + <method name="set_glyph_uv_rect"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <argument index="3" name="uv_rect" type="Rect2" /> + <description> + Sets rectangle in the cache texture containing the glyph. + </description> + </method> + <method name="set_hinting"> + <return type="void" /> + <argument index="0" name="hinting" type="int" enum="TextServer.Hinting" /> + <description> + Sets font hinting mode. Used by dynamic fonts only. + </description> + </method> + <method name="set_kerning"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="glyph_pair" type="Vector2i" /> + <argument index="3" name="kerning" type="Vector2" /> + <description> + Sets kerning for the pair of glyphs. + </description> + </method> <method name="set_language_support_override"> <return type="void" /> <argument index="0" name="language" type="String" /> @@ -263,6 +540,43 @@ Adds override for [method is_language_supported]. </description> </method> + <method name="set_msdf_pixel_range"> + <return type="void" /> + <argument index="0" name="msdf_pixel_range" type="int" /> + <description> + Sets the width of the range around the shape between the minimum and maximum representable signed distance. + </description> + </method> + <method name="set_msdf_size"> + <return type="void" /> + <argument index="0" name="msdf_size" type="int" /> + <description> + Sets source font size used to generate MSDF textures. + </description> + </method> + <method name="set_multichannel_signed_distance_field"> + <return type="void" /> + <argument index="0" name="msdf" type="bool" /> + <description> + If set to [code]true[/code], glyphs of all sizes are rendered using single multichannel signed distance field generated from the dynamic font vector data. + </description> + </method> + <method name="set_oversampling"> + <return type="void" /> + <argument index="0" name="oversampling" type="float" /> + <description> + Sets font oversampling factor, if set to [code]0.0[/code] global oversampling factor is used instead. Used by dynamic fonts only. + </description> + </method> + <method name="set_scale"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="scale" type="float" /> + <description> + Sets scaling factor of the color bitmap font. + </description> + </method> <method name="set_script_support_override"> <return type="void" /> <argument index="0" name="script" type="String" /> @@ -273,52 +587,61 @@ </method> <method name="set_spacing"> <return type="void" /> - <argument index="0" name="type" type="int" /> - <argument index="1" name="value" type="int" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="spacing" type="int" enum="TextServer.SpacingType" /> + <argument index="3" name="arg3" type="int" /> + <description> + Sets extra spacing added between glyphs in pixels. + </description> + </method> + <method name="set_texture_image"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="texture_index" type="int" /> + <argument index="3" name="image" type="Image" /> + <description> + Sets font cache texture image. + </description> + </method> + <method name="set_texture_offsets"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="texture_index" type="int" /> + <argument index="3" name="offset" type="PackedInt32Array" /> + <description> + Sets array containing the first free pixel in the each column of texture. Should be the same size as texture width or empty (for the fonts without dynamic glyph generation support). + </description> + </method> + <method name="set_underline_position"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="underline_position" type="float" /> + <description> + Sets pixel offset of the underline below the baseline. + </description> + </method> + <method name="set_underline_thickness"> + <return type="void" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="underline_thickness" type="float" /> <description> - Sets the spacing for [code]type[/code] (see [enum SpacingType]) to [code]value[/code] in pixels (not relative to the font size). + Sets thickness of the underline in pixels. </description> </method> - <method name="set_variation"> + <method name="set_variation_coordinates"> <return type="void" /> - <argument index="0" name="tag" type="String" /> - <argument index="1" name="value" type="float" /> + <argument index="0" name="cache_index" type="int" /> + <argument index="1" name="variation_coordinates" type="Dictionary" /> <description> - Sets variation coordinate [code]tag[/code]. + Sets variation coordinates for the specified font cache entry. See [method get_supported_variation_list] for more info. </description> </method> </methods> - <members> - <member name="antialiased" type="bool" setter="set_antialiased" getter="get_antialiased" default="false"> - If [code]true[/code], the font is rendered with anti-aliasing. - </member> - <member name="data_path" type="String" setter="set_data_path" getter="get_data_path" default=""""> - The path to the font data file. If font data was loaded from memory location is set to [code]"(Memory)"[/code]. - </member> - <member name="distance_field_hint" type="bool" setter="set_distance_field_hint" getter="get_distance_field_hint" default="false"> - If [code]true[/code], distance field hint is enabled. - </member> - <member name="extra_spacing_glyph" type="int" setter="set_spacing" getter="get_spacing" default="0"> - Extra spacing for each glyph in pixels. - This can be a negative number to make the distance between glyphs smaller. - </member> - <member name="extra_spacing_space" type="int" setter="set_spacing" getter="get_spacing" default="0"> - Extra spacing for the space character in pixels. - This can be a negative number to make the distance between words smaller. - </member> - <member name="force_autohinter" type="bool" setter="set_force_autohinter" getter="get_force_autohinter" default="false"> - If [code]true[/code], default autohinter is used for font hinting. - </member> - <member name="hinting" type="int" setter="set_hinting" getter="get_hinting" enum="TextServer.Hinting" default="0"> - The font hinting mode used by FreeType. See [enum TextServer.Hinting] for options. - </member> - </members> <constants> - <constant name="SPACING_GLYPH" value="0" enum="SpacingType"> - Spacing for each glyph. - </constant> - <constant name="SPACING_SPACE" value="1" enum="SpacingType"> - Spacing for the space character. - </constant> </constants> </class> diff --git a/doc/classes/GradientTexture.xml b/doc/classes/GradientTexture.xml index 242a78b2e4..44da042dd4 100644 --- a/doc/classes/GradientTexture.xml +++ b/doc/classes/GradientTexture.xml @@ -14,6 +14,9 @@ <member name="gradient" type="Gradient" setter="set_gradient" getter="get_gradient"> The [Gradient] that will be used to fill the texture. </member> + <member name="use_hdr" type="bool" setter="set_use_hdr" getter="is_using_hdr" default="false"> + If [code]true[/code], the generated texture will support high dynamic range ([constant Image.FORMAT_RGBAF] format). This allows for glow effects to work if [member Environment.glow_enabled] is [code]true[/code]. If [code]false[/code], the generated texture will use low dynamic range; overbright colors will be clamped ([constant Image.FORMAT_RGBA8] format). + </member> <member name="width" type="int" setter="set_width" getter="get_width" default="2048"> The number of color samples that will be obtained from the [Gradient]. </member> diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index a3759a51dd..c870026d58 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -10,7 +10,7 @@ <tutorials> </tutorials> <methods> - <method name="_get_connection_line" qualifiers="virtual"> + <method name="_get_connection_line" qualifiers="virtual const"> <return type="PackedVector2Array" /> <argument index="0" name="from" type="Vector2" /> <argument index="1" name="to" type="Vector2" /> diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml index 22398cc3ce..29aaf3c756 100644 --- a/doc/classes/HTTPClient.xml +++ b/doc/classes/HTTPClient.xml @@ -8,6 +8,7 @@ [b]Note:[/b] This client only needs to connect to a host once (see [method connect_to_host]) to send multiple requests. Because of this, methods that take URLs usually take just the part after the host instead of the full URL, as the client is already connected to a host. See [method request] for a full example and to get started. A [HTTPClient] should be reused between multiple requests or to connect to different hosts instead of creating one client per request. Supports SSL and SSL server certificate verification. HTTP status codes in the 2xx range indicate success, 3xx redirection (i.e. "try again, but over here"), 4xx something was wrong with the request, and 5xx something went wrong on the server's side. For more information on HTTP, see https://developer.mozilla.org/en-US/docs/Web/HTTP (or read RFC 2616 to get it straight from the source: https://tools.ietf.org/html/rfc2616). + [b]Note:[/b] It's recommended to use transport encryption (SSL/TLS) and to avoid sending sensitive information (such as login credentials) in HTTP GET URL parameters. Consider using HTTP POST requests or HTTP headers for such information instead. [b]Note:[/b] When performing HTTP requests from a project exported to HTML5, keep in mind the remote server may not allow requests from foreign origins due to [url=https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS]CORS[/url]. If you host the server in question, you should modify its backend to allow requests from foreign origins by adding the [code]Access-Control-Allow-Origin: *[/code] HTTP header. [b]Note:[/b] SSL/TLS support is currently limited to TLS 1.0, TLS 1.1, and TLS 1.2. Attempting to connect to a TLS 1.3-only server will return an error. [b]Warning:[/b] SSL/TLS certificate revocation and certificate pinning are currently not supported. Revoked certificates are accepted as long as they are otherwise valid. If this is a concern, you may want to use automatically managed certificates with a short validity period. @@ -138,7 +139,7 @@ <argument index="3" name="body" type="String" default="""" /> <description> Sends a request to the connected host. - The URL parameter is usually just the part after the host, so for [code]http://somehost.com/index.php[/code], it is [code]/index.php[/code]. When sending requests to an HTTP proxy server, it should be an absolute URL. For [constant HTTPClient.METHOD_OPTIONS] requests, [code]*[/code] is also allowed. For [constant HTTPClient.METHOD_CONNECT] requests, it should be the authority component ([code]host:port[/code]). + The URL parameter is usually just the part after the host, so for [code]https://somehost.com/index.php[/code], it is [code]/index.php[/code]. When sending requests to an HTTP proxy server, it should be an absolute URL. For [constant HTTPClient.METHOD_OPTIONS] requests, [code]*[/code] is also allowed. For [constant HTTPClient.METHOD_CONNECT] requests, it should be the authority component ([code]host:port[/code]). Headers are HTTP request headers. For available HTTP methods, see [enum Method]. To create a POST request with query strings to push to the server, do: [codeblocks] @@ -166,7 +167,7 @@ <argument index="3" name="body" type="PackedByteArray" /> <description> Sends a raw request to the connected host. - The URL parameter is usually just the part after the host, so for [code]http://somehost.com/index.php[/code], it is [code]/index.php[/code]. When sending requests to an HTTP proxy server, it should be an absolute URL. For [constant HTTPClient.METHOD_OPTIONS] requests, [code]*[/code] is also allowed. For [constant HTTPClient.METHOD_CONNECT] requests, it should be the authority component ([code]host:port[/code]). + The URL parameter is usually just the part after the host, so for [code]https://somehost.com/index.php[/code], it is [code]/index.php[/code]. When sending requests to an HTTP proxy server, it should be an absolute URL. For [constant HTTPClient.METHOD_OPTIONS] requests, [code]*[/code] is also allowed. For [constant HTTPClient.METHOD_CONNECT] requests, it should be the authority component ([code]host:port[/code]). Headers are HTTP request headers. For available HTTP methods, see [enum Method]. Sends the body data raw, as a byte array and does not encode it in any way. </description> diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml index f45ddd0abb..00927b98c5 100644 --- a/doc/classes/HTTPRequest.xml +++ b/doc/classes/HTTPRequest.xml @@ -192,7 +192,8 @@ <description> Creates request on the underlying [HTTPClient]. If there is no configuration errors, it tries to connect using [method HTTPClient.connect_to_host] and passes parameters onto [method HTTPClient.request]. Returns [constant OK] if request is successfully created. (Does not imply that the server has responded), [constant ERR_UNCONFIGURED] if not in the tree, [constant ERR_BUSY] if still processing previous request, [constant ERR_INVALID_PARAMETER] if given string is not a valid URL format, or [constant ERR_CANT_CONNECT] if not using thread and the [HTTPClient] cannot connect to host. - [b]Note:[/b] The [code]request_data[/code] parameter is ignored if [code]method[/code] is [constant HTTPClient.METHOD_GET]. This is because GET methods can't contain request data. As a workaround, you can pass request data as a query string in the URL. See [method String.uri_encode] for an example. + [b]Note:[/b] When [code]method[/code] is [constant HTTPClient.METHOD_GET], the payload sent via [code]request_data[/code] might be ignored by the server or even cause the server to reject the request (check [url=https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.1]RFC 7231 section 4.3.1[/url] for more details). As a workaround, you can send data as a query string in the URL (see [method String.uri_encode] for an example). + [b]Note:[/b] It's recommended to use transport encryption (SSL/TLS) and to avoid sending sensitive information (such as login credentials) in HTTP GET URL parameters. Consider using HTTP POST requests or HTTP headers for such information instead. </description> </method> <method name="request_raw"> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 34c5fb582e..5d79e22c49 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -180,6 +180,7 @@ <argument index="0" name="renormalize" type="bool" default="false" /> <description> 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]. + [b]Note:[/b] Mipmap generation is done on the CPU, is single-threaded and is [i]always[/i] done on the main thread. This means generating mipmaps will result in noticeable stuttering during gameplay, even if [method generate_mipmaps] is called from a [Thread]. </description> </method> <method name="get_data" qualifiers="const"> diff --git a/doc/classes/InputEventShortcut.xml b/doc/classes/InputEventShortcut.xml new file mode 100644 index 0000000000..35cca02cf7 --- /dev/null +++ b/doc/classes/InputEventShortcut.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="InputEventShortcut" inherits="InputEvent" version="4.0"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <members> + <member name="shortcut" type="Shortcut" setter="set_shortcut" getter="get_shortcut"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/ItemList.xml b/doc/classes/ItemList.xml index 06e98f7e57..e3e4a9fa7d 100644 --- a/doc/classes/ItemList.xml +++ b/doc/classes/ItemList.xml @@ -403,6 +403,9 @@ <member name="select_mode" type="int" setter="set_select_mode" getter="get_select_mode" enum="ItemList.SelectMode" default="0"> Allows single or multiple item selection. See the [enum SelectMode] constants. </member> + <member name="text_overrun_behavior" type="int" setter="set_text_overrun_behavior" getter="get_text_overrun_behavior" enum="TextParagraph.OverrunBehavior" default="0"> + Sets the clipping behavior when the text exceeds an item's bounding rectangle. See [enum TextParagraph.OverrunBehavior] for a description of all modes. + </member> </members> <signals> <signal name="item_activated"> diff --git a/doc/classes/JNISingleton.xml b/doc/classes/JNISingleton.xml index 84ab1a49c1..fbf18ddc03 100644 --- a/doc/classes/JNISingleton.xml +++ b/doc/classes/JNISingleton.xml @@ -1,10 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="JNISingleton" inherits="Object" version="4.0"> <brief_description> + Singleton that connects the engine with Android plugins to interface with native Android code. </brief_description> <description> + The JNISingleton is implemented only in the Android export. It's used to call methods and connect signals from an Android plugin written in Java or Kotlin. Methods and signals can be called and connected to the JNISingleton as if it is a Node. See [url=https://en.wikipedia.org/wiki/Java_Native_Interface]Java Native Interface - Wikipedia[/url] for more information. </description> <tutorials> + <link title="Creating Android plugins">https://docs.godotengine.org/en/latest/tutorials/platform/android/android_plugin.html#doc-android-plugin</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Light3D.xml b/doc/classes/Light3D.xml index cd2f4eca18..52359b0ede 100644 --- a/doc/classes/Light3D.xml +++ b/doc/classes/Light3D.xml @@ -47,7 +47,8 @@ The light's strength multiplier (this is not a physical unit). For [OmniLight3D] and [SpotLight3D], changing this value will only change the light color's intensity, not the light's radius. </member> <member name="light_indirect_energy" type="float" setter="set_param" getter="get_param" default="1.0"> - Secondary multiplier used with indirect light (light bounces). Used with [VoxelGI]. + Secondary multiplier used with indirect light (light bounces). Used with [VoxelGI] and SDFGI (see [member Environment.sdfgi_enabled]). + [b]Note:[/b] This property is ignored if [member light_energy] is equal to [code]0.0[/code], as the light won't be present at all in the GI shader. </member> <member name="light_negative" type="bool" setter="set_negative" getter="is_negative" default="false"> If [code]true[/code], the light's effect is reversed, darkening areas and casting bright shadows. diff --git a/doc/classes/Line2D.xml b/doc/classes/Line2D.xml index 093ba51755..4d9abbbb19 100644 --- a/doc/classes/Line2D.xml +++ b/doc/classes/Line2D.xml @@ -58,6 +58,7 @@ <members> <member name="antialiased" type="bool" setter="set_antialiased" getter="get_antialiased" default="false"> If [code]true[/code], the line's border will be anti-aliased. + [b]Note:[/b] Line2D is not accelerated by batching when being anti-aliased. </member> <member name="begin_cap_mode" type="int" setter="set_begin_cap_mode" getter="get_begin_cap_mode" enum="Line2D.LineCapMode" default="0"> Controls the style of the line's first point. Use [enum LineCapMode] constants. diff --git a/doc/classes/Listener2D.xml b/doc/classes/Listener2D.xml new file mode 100644 index 0000000000..27ee63d201 --- /dev/null +++ b/doc/classes/Listener2D.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="Listener2D" inherits="Node2D" version="4.0"> + <brief_description> + Overrides the location sounds are heard from. + </brief_description> + <description> + Once added to the scene tree and enabled using [method make_current], this node will override the location sounds are heard from. Only one [Listener2D] can be current. Using [method make_current] will disable the previous [Listener2D]. + If there is no active [Listener2D] in the current [Viewport], center of the screen will be used as a hearing point for the audio. [Listener2D] needs to be inside [SceneTree] to function. + </description> + <tutorials> + </tutorials> + <methods> + <method name="clear_current"> + <return type="void" /> + <description> + Disables the [Listener2D]. If it's not set as current, this method will have no effect. + </description> + </method> + <method name="is_current" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if this [Listener2D] is currently active. + </description> + </method> + <method name="make_current"> + <return type="void" /> + <description> + Makes the [Listener2D] active, setting it as the hearing point for the sounds. If there is already another active [Listener2D], it will be disabled. + This method will have no effect if the [Listener2D] is not added to [SceneTree]. + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/Listener3D.xml b/doc/classes/Listener3D.xml index 9cc803f241..5e1b2ce7fc 100644 --- a/doc/classes/Listener3D.xml +++ b/doc/classes/Listener3D.xml @@ -5,7 +5,6 @@ </brief_description> <description> Once added to the scene tree and enabled using [method make_current], this node will override the location sounds are heard from. This can be used to listen from a location different from the [Camera3D]. - [b]Note:[/b] There is no 2D equivalent for this node yet. </description> <tutorials> </tutorials> diff --git a/doc/classes/Mesh.xml b/doc/classes/Mesh.xml index bfa55c2d35..c774528a39 100644 --- a/doc/classes/Mesh.xml +++ b/doc/classes/Mesh.xml @@ -210,6 +210,8 @@ </constant> <constant name="ARRAY_FORMAT_CUSTOM_BASE" value="13" enum="ArrayFormat"> </constant> + <constant name="ARRAY_FORMAT_CUSTOM_BITS" value="3" enum="ArrayFormat"> + </constant> <constant name="ARRAY_FORMAT_CUSTOM0_SHIFT" value="13" enum="ArrayFormat"> </constant> <constant name="ARRAY_FORMAT_CUSTOM1_SHIFT" value="16" enum="ArrayFormat"> diff --git a/doc/classes/MeshLibrary.xml b/doc/classes/MeshLibrary.xml index 9e0292f946..1d07647ea7 100644 --- a/doc/classes/MeshLibrary.xml +++ b/doc/classes/MeshLibrary.xml @@ -45,6 +45,13 @@ Returns the item's mesh. </description> </method> + <method name="get_item_mesh_transform" qualifiers="const"> + <return type="Transform3D" /> + <argument index="0" name="id" type="int" /> + <description> + Returns the transform applied to the item's mesh. + </description> + </method> <method name="get_item_name" qualifiers="const"> <return type="String" /> <argument index="0" name="id" type="int" /> @@ -102,6 +109,14 @@ Sets the item's mesh. </description> </method> + <method name="set_item_mesh_transform"> + <return type="void" /> + <argument index="0" name="id" type="int" /> + <argument index="1" name="mesh_transform" type="Transform3D" /> + <description> + Sets the transform to apply to the item's mesh. + </description> + </method> <method name="set_item_name"> <return type="void" /> <argument index="0" name="id" type="int" /> diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml index 610b00efe9..647233f679 100644 --- a/doc/classes/MultiplayerAPI.xml +++ b/doc/classes/MultiplayerAPI.xml @@ -4,8 +4,8 @@ High-level multiplayer API. </brief_description> <description> - This class implements most of the logic behind the high-level multiplayer API. See also [MultiplayerPeer]. - By default, [SceneTree] has a reference to this class that is used to provide multiplayer capabilities (i.e. RPC/RSET) across the whole scene. + This class implements the high-level multiplayer API. See also [MultiplayerPeer]. + By default, [SceneTree] has a reference to this class that is used to provide multiplayer capabilities (i.e. RPCs) across the whole scene. It is possible to override the MultiplayerAPI instance used by specific Nodes by setting the [member Node.custom_multiplayer] property, effectively allowing to run both client and server in the same scene. [b]Note:[/b] The high-level multiplayer API protocol is an implementation detail and isn't meant to be used by non-Godot servers. It may change without notice. </description> @@ -18,49 +18,49 @@ Clears the current MultiplayerAPI network state (you shouldn't call this unless you know what you are doing). </description> </method> - <method name="get_network_connected_peers" qualifiers="const"> + <method name="get_peers" qualifiers="const"> <return type="PackedInt32Array" /> <description> - Returns the peer IDs of all connected peers of this MultiplayerAPI's [member network_peer]. + Returns the peer IDs of all connected peers of this MultiplayerAPI's [member multiplayer_peer]. </description> </method> - <method name="get_network_unique_id" qualifiers="const"> + <method name="get_remote_sender_id" qualifiers="const"> <return type="int" /> <description> - Returns the unique peer ID of this MultiplayerAPI's [member network_peer]. + Returns the sender's peer ID for the RPC currently being executed. + [b]Note:[/b] If not inside an RPC this method will return 0. </description> </method> - <method name="get_rpc_sender_id" qualifiers="const"> + <method name="get_unique_id" qualifiers="const"> <return type="int" /> <description> - Returns the sender's peer ID for the RPC currently being executed. - [b]Note:[/b] If not inside an RPC this method will return 0. + Returns the unique peer ID of this MultiplayerAPI's [member multiplayer_peer]. </description> </method> - <method name="has_network_peer" qualifiers="const"> + <method name="has_multiplayer_peer" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if there is a [member network_peer] set. + Returns [code]true[/code] if there is a [member multiplayer_peer] set. </description> </method> - <method name="is_network_server" qualifiers="const"> + <method name="is_server" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this MultiplayerAPI's [member network_peer] is in server mode (listening for connections). + Returns [code]true[/code] if this MultiplayerAPI's [member multiplayer_peer] is valid and in server mode (listening for connections). </description> </method> <method name="poll"> <return type="void" /> <description> Method used for polling the MultiplayerAPI. You only need to worry about this if you are using [member Node.custom_multiplayer] override or you set [member SceneTree.multiplayer_poll] to [code]false[/code]. By default, [SceneTree] will poll its MultiplayerAPI for you. - [b]Note:[/b] This method results in RPCs and RSETs being called, so they will be executed in the same context of this function (e.g. [code]_process[/code], [code]physics[/code], [Thread]). + [b]Note:[/b] This method results in RPCs being called, so they will be executed in the same context of this function (e.g. [code]_process[/code], [code]physics[/code], [Thread]). </description> </method> <method name="send_bytes"> <return type="int" enum="Error" /> <argument index="0" name="bytes" type="PackedByteArray" /> <argument index="1" name="id" type="int" default="0" /> - <argument index="2" name="mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" /> + <argument index="2" name="mode" type="int" enum="TransferMode" default="2" /> <argument index="3" name="channel" type="int" default="0" /> <description> Sends the given raw [code]bytes[/code] to a specific peer identified by [code]id[/code] (see [method MultiplayerPeer.set_target_peer]). Default ID is [code]0[/code], i.e. broadcast to all peers. @@ -69,14 +69,14 @@ </methods> <members> <member name="allow_object_decoding" type="bool" setter="set_allow_object_decoding" getter="is_object_decoding_allowed" default="false"> - If [code]true[/code], the MultiplayerAPI will allow encoding and decoding of object during RPCs/RSETs. + If [code]true[/code], the MultiplayerAPI will allow encoding and decoding of object during RPCs. [b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threats such as remote code execution. </member> - <member name="network_peer" type="MultiplayerPeer" setter="set_network_peer" getter="get_network_peer"> - The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the MultiplayerAPI will become a network server (check with [method is_network_server]) and will set root node's network mode to master, or it will become a regular peer with root node set to puppet. All child nodes are set to inherit the network mode by default. Handling of networking-related events (connection, disconnection, new clients) is done by connecting to MultiplayerAPI's signals. + <member name="multiplayer_peer" type="MultiplayerPeer" setter="set_multiplayer_peer" getter="get_multiplayer_peer"> + The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the MultiplayerAPI will become a network server (check with [method is_server]) and will set root node's network mode to authority, or it will become a regular client peer. All child nodes are set to inherit the network mode by default. Handling of networking-related events (connection, disconnection, new clients) is done by connecting to MultiplayerAPI's signals. </member> - <member name="refuse_new_network_connections" type="bool" setter="set_refuse_new_network_connections" getter="is_refusing_new_network_connections" default="false"> - If [code]true[/code], the MultiplayerAPI's [member network_peer] refuses new incoming connections. + <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" default="false"> + If [code]true[/code], the MultiplayerAPI's [member multiplayer_peer] refuses new incoming connections. </member> <member name="replicator" type="MultiplayerReplicator" setter="" getter="get_replicator"> </member> @@ -88,51 +88,39 @@ <signals> <signal name="connected_to_server"> <description> - Emitted when this MultiplayerAPI's [member network_peer] successfully connected to a server. Only emitted on clients. + Emitted when this MultiplayerAPI's [member multiplayer_peer] successfully connected to a server. Only emitted on clients. </description> </signal> <signal name="connection_failed"> <description> - Emitted when this MultiplayerAPI's [member network_peer] fails to establish a connection to a server. Only emitted on clients. + Emitted when this MultiplayerAPI's [member multiplayer_peer] fails to establish a connection to a server. Only emitted on clients. </description> </signal> - <signal name="network_peer_connected"> + <signal name="peer_connected"> <argument index="0" name="id" type="int" /> <description> - Emitted when this MultiplayerAPI's [member network_peer] connects with a new peer. ID is the peer ID of the new peer. Clients get notified when other clients connect to the same server. Upon connecting to a server, a client also receives this signal for the server (with ID being 1). + Emitted when this MultiplayerAPI's [member multiplayer_peer] connects with a new peer. ID is the peer ID of the new peer. Clients get notified when other clients connect to the same server. Upon connecting to a server, a client also receives this signal for the server (with ID being 1). </description> </signal> - <signal name="network_peer_disconnected"> + <signal name="peer_disconnected"> <argument index="0" name="id" type="int" /> <description> - Emitted when this MultiplayerAPI's [member network_peer] disconnects from a peer. Clients get notified when other clients disconnect from the same server. + Emitted when this MultiplayerAPI's [member multiplayer_peer] disconnects from a peer. Clients get notified when other clients disconnect from the same server. </description> </signal> - <signal name="network_peer_packet"> + <signal name="peer_packet"> <argument index="0" name="id" type="int" /> <argument index="1" name="packet" type="PackedByteArray" /> <description> - Emitted when this MultiplayerAPI's [member network_peer] receive a [code]packet[/code] with custom data (see [method send_bytes]). ID is the peer ID of the peer that sent the packet. + Emitted when this MultiplayerAPI's [member multiplayer_peer] receives a [code]packet[/code] with custom data (see [method send_bytes]). ID is the peer ID of the peer that sent the packet. </description> </signal> <signal name="server_disconnected"> <description> - Emitted when this MultiplayerAPI's [member network_peer] disconnects from server. Only emitted on clients. + Emitted when this MultiplayerAPI's [member multiplayer_peer] disconnects from server. Only emitted on clients. </description> </signal> </signals> <constants> - <constant name="RPC_MODE_DISABLED" value="0" enum="RPCMode"> - Used with [method Node.rpc_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods. - </constant> - <constant name="RPC_MODE_REMOTE" value="1" enum="RPCMode"> - Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on the remote end, not locally. Analogous to the [code]remote[/code] keyword. Calls and property changes are accepted from all remote peers, no matter if they are node's master or puppets. - </constant> - <constant name="RPC_MODE_MASTER" value="2" enum="RPCMode"> - Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on the network master for this node. Analogous to the [code]master[/code] keyword. Only accepts calls or property changes from the node's network puppets, see [method Node.set_network_master]. - </constant> - <constant name="RPC_MODE_PUPPET" value="3" enum="RPCMode"> - Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on puppets for this node. Analogous to the [code]puppet[/code] keyword. Only accepts calls or property changes from the node's network master, see [method Node.set_network_master]. - </constant> </constants> </class> diff --git a/doc/classes/MultiplayerPeer.xml b/doc/classes/MultiplayerPeer.xml index adaa359168..411317cdc8 100644 --- a/doc/classes/MultiplayerPeer.xml +++ b/doc/classes/MultiplayerPeer.xml @@ -4,7 +4,7 @@ A high-level network interface to simplify multiplayer interactions. </brief_description> <description> - Manages the connection to network peers. Assigns unique IDs to each client connected to the server. See also [MultiplayerAPI]. + Manages the connection to multiplayer peers. Assigns unique IDs to each client connected to the server. See also [MultiplayerAPI]. [b]Note:[/b] The high-level multiplayer API protocol is an implementation detail and isn't meant to be used by non-Godot servers. It may change without notice. </description> <tutorials> @@ -57,9 +57,9 @@ </member> <member name="transfer_channel" type="int" setter="set_transfer_channel" getter="get_transfer_channel" default="0"> The channel to use to send packets. Many network APIs such as ENet and WebRTC allow the creation of multiple independent channels which behaves, in a way, like separate connections. This means that reliable data will only block delivery of other packets on that channel, and ordering will only be in respect to the channel the packet is being sent on. Using different channels to send [b]different and independent[/b] state updates is a common way to optimize network usage and decrease latency in fast-paced games. - [b]Note:[/b] The default channel ([code]0[/code]) actually works as 3 separate channels (one for each [enum TransferMode]) so that [constant TRANSFER_MODE_RELIABLE] and [constant TRANSFER_MODE_UNRELIABLE_ORDERED] does not interact with each other by default. Refer to the specific network API documentation (e.g. ENet or WebRTC) to learn how to set up channels correctly. + [b]Note:[/b] The default channel ([code]0[/code]) actually works as 3 separate channels (one for each [enum TransferMode]) so that [constant TRANSFER_MODE_RELIABLE] and [constant TRANSFER_MODE_ORDERED] does not interact with each other by default. Refer to the specific network API documentation (e.g. ENet or WebRTC) to learn how to set up channels correctly. </member> - <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" enum="MultiplayerPeer.TransferMode" default="0"> + <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" enum="TransferMode" default="0"> The manner in which to send packets to the [code]target_peer[/code]. See [enum TransferMode]. </member> </members> @@ -93,15 +93,6 @@ </signal> </signals> <constants> - <constant name="TRANSFER_MODE_UNRELIABLE" value="0" enum="TransferMode"> - Packets are not acknowledged, no resend attempts are made for lost packets. Packets may arrive in any order. Potentially faster than [constant TRANSFER_MODE_UNRELIABLE_ORDERED]. Use for non-critical data, and always consider whether the order matters. - </constant> - <constant name="TRANSFER_MODE_UNRELIABLE_ORDERED" value="1" enum="TransferMode"> - Packets are not acknowledged, no resend attempts are made for lost packets. Packets are received in the order they were sent in. Potentially faster than [constant TRANSFER_MODE_RELIABLE]. Use for non-critical data or data that would be outdated if received late due to resend attempt(s) anyway, for example movement and positional data. - </constant> - <constant name="TRANSFER_MODE_RELIABLE" value="2" enum="TransferMode"> - Packets must be received and resend attempts should be made until the packets are acknowledged. Packets must be received in the order they were sent in. Most reliable transfer mode, but potentially the slowest due to the overhead. Use for critical data that must be transmitted and arrive in order, for example an ability being triggered or a chat message. Consider carefully if the information really is critical, and use sparingly. - </constant> <constant name="CONNECTION_DISCONNECTED" value="0" enum="ConnectionStatus"> The ongoing connection disconnected. </constant> diff --git a/doc/classes/MultiplayerReplicator.xml b/doc/classes/MultiplayerReplicator.xml index 15029e181f..e0c309ef39 100644 --- a/doc/classes/MultiplayerReplicator.xml +++ b/doc/classes/MultiplayerReplicator.xml @@ -12,6 +12,7 @@ <argument index="0" name="scene_id" type="int" /> <argument index="1" name="object" type="Object" /> <argument index="2" name="data" type="PackedByteArray" /> + <argument index="3" name="initial" type="bool" default="true" /> <description> Decode the given [code]data[/code] representing a spawnable state into [code]object[/code] using the configuration associated with the provided [code]scene_id[/code]. This function is called automatically when a client receives a server spawn for a scene with [constant REPLICATION_MODE_SERVER]. See [method spawn_config]. Tip: You may find this function useful in servers when parsing spawn requests from clients, or when implementing your own logic with [constant REPLICATION_MODE_CUSTOM]. @@ -23,12 +24,14 @@ <argument index="1" name="object" type="Object" /> <argument index="2" name="peer_id" type="int" default="0" /> <description> + Request a despawn for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code]. This will either trigger the default behaviour, or invoke the custom spawn/despawn callables specified in [method spawn_config]. See [method send_despawn] for the default behavior. </description> </method> <method name="encode_state"> <return type="PackedByteArray" /> <argument index="0" name="scene_id" type="int" /> <argument index="1" name="object" type="Object" /> + <argument index="2" name="initial" type="bool" default="true" /> <description> Encode the given [code]object[/code] using the configuration associated with the provided [code]scene_id[/code]. This function is called automatically when the server spawns scenes with [constant REPLICATION_MODE_SERVER]. See [method spawn_config]. Tip: You may find this function useful when requesting spawns from clients to server, or when implementing your own logic with [constant REPLICATION_MODE_CUSTOM]. @@ -41,7 +44,7 @@ <argument index="2" name="data" type="Variant" default="null" /> <argument index="3" name="path" type="NodePath" default="NodePath("")" /> <description> - Sends a despawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_network_server]), the receiving peer(s) will automatically queue for deletion the node at [code]path[/code] and emit the signal [signal despawned]. In all other cases no deletion happens, and the signal [signal despawn_requested] is emitted instead. + Sends a despawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_server]), the receiving peer(s) will automatically queue for deletion the node at [code]path[/code] and emit the signal [signal despawned]. In all other cases no deletion happens, and the signal [signal despawn_requested] is emitted instead. </description> </method> <method name="send_spawn"> @@ -51,7 +54,18 @@ <argument index="2" name="data" type="Variant" default="null" /> <argument index="3" name="path" type="NodePath" default="NodePath("")" /> <description> - Sends a spawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_network_server]), the receiving peer(s) will automatically instantiate that scene, add it to the [SceneTree] at the given [code]path[/code] and emit the signal [signal spawned]. In all other cases no instantiation happens, and the signal [signal spawn_requested] is emitted instead. + Sends a spawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_server]), the receiving peer(s) will automatically instantiate that scene, add it to the [SceneTree] at the given [code]path[/code] and emit the signal [signal spawned]. In all other cases no instantiation happens, and the signal [signal spawn_requested] is emitted instead. + </description> + </method> + <method name="send_sync"> + <return type="int" enum="Error" /> + <argument index="0" name="peer_id" type="int" /> + <argument index="1" name="scene_id" type="int" /> + <argument index="2" name="data" type="PackedByteArray" /> + <argument index="3" name="transfer_mode" type="int" enum="TransferMode" default="2" /> + <argument index="4" name="channel" type="int" default="0" /> + <description> + Sends a sync request for the instances of the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). This function can only be called manually when overriding the send and receive sync functions (see [method sync_config]). </description> </method> <method name="spawn"> @@ -60,6 +74,7 @@ <argument index="1" name="object" type="Object" /> <argument index="2" name="peer_id" type="int" default="0" /> <description> + Request a spawn for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code]. This will either trigger the default behaviour, or invoke the custom spawn/despawn callables specified in [method spawn_config]. See [method send_spawn] for the default behavior. </description> </method> <method name="spawn_config"> @@ -70,10 +85,47 @@ <argument index="3" name="custom_send" type="Callable" /> <argument index="4" name="custom_receive" type="Callable" /> <description> - Configures the MultiplayerAPI to track instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication. When [code]mode[/code] is [constant REPLICATION_MODE_SERVER], the specified [code]properties[/code] will also be replicated to clients during the initial spawn. + Configures the MultiplayerReplicator to track instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication. When [code]mode[/code] is [constant REPLICATION_MODE_SERVER], the specified [code]properties[/code] will also be replicated to clients during the initial spawn. You can optionally specify a [code]custom_send[/code] and a [code]custom_receive[/code] to override the default behaviour and customize the spawn/despawn proecess. Tip: You can use a custom property in the scene main script to return a customly optimized state representation. </description> </method> + <method name="sync_all"> + <return type="int" enum="Error" /> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="peer_id" type="int" default="0" /> + <description> + Manually request a sync for all the instances of the scene identified by [code]scene_id[/code]. This function will trigger the default sync behaviour, or call your send custom send callable if specified in [method sync_config]. + Note: The default implementation only allow syncing from server to clients. + </description> + </method> + <method name="sync_config"> + <return type="int" enum="Error" /> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="interval" type="int" /> + <argument index="2" name="properties" type="StringName[]" default="[]" /> + <argument index="3" name="custom_send" type="Callable" /> + <argument index="4" name="custom_receive" type="Callable" /> + <description> + Configures the MultiplayerReplicator to sync instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication at the desired [code]interval[/code] (in milliseconds). The specified [code]properties[/code] will be part of the state sync. You can optionally specify a [code]custom_send[/code] and a [code]custom_receive[/code] to override the default behaviour and customize the syncronization proecess. + Tip: You can use a custom property in the scene main script to return a customly optimized state representation (having a single property that returns a PackedByteArray is higly recommended when dealing with many instances). + </description> + </method> + <method name="track"> + <return type="void" /> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="object" type="Object" /> + <description> + Track the given [code]object[/code] as an instance of the scene identified by [code]scene_id[/code]. This object will be passed to your custom sync callables (see [method sync_config]). Tracking and untracking is automatic in [constant REPLICATION_MODE_SERVER]. + </description> + </method> + <method name="untrack"> + <return type="void" /> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="object" type="Object" /> + <description> + Untrack the given [code]object[/code]. This object will no longer be passed to your custom sync callables (see [method sync_config]). Tracking and untracking is automatic in [constant REPLICATION_MODE_SERVER]. + </description> + </method> </methods> <signals> <signal name="despawn_requested"> diff --git a/doc/classes/NativeExtensionManager.xml b/doc/classes/NativeExtensionManager.xml index c14ce94aff..42246619f6 100644 --- a/doc/classes/NativeExtensionManager.xml +++ b/doc/classes/NativeExtensionManager.xml @@ -18,6 +18,12 @@ <description> </description> </method> + <method name="is_extension_loaded" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="path" type="String" /> + <description> + </description> + </method> <method name="load_extension"> <return type="int" enum="NativeExtensionManager.LoadStatus" /> <argument index="0" name="path" type="String" /> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 97e6c71ef9..608d76cd9f 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -43,14 +43,6 @@ Call [method update_configuration_warnings] when the warnings need to be updated for this node. </description> </method> - <method name="_get_configuration_warnings" qualifiers="virtual"> - <return type="String[]" /> - <description> - The elements in the array returned from this method are displayed as warnings in the Scene Dock if the script that overrides it is a [code]tool[/code] script. - Returning an empty array produces no warnings. - Call [method update_configuration_warnings] when the warnings need to be updated for this node. - </description> - </method> <method name="_input" qualifiers="virtual"> <return type="void" /> <argument index="0" name="event" type="InputEvent" /> @@ -104,9 +96,9 @@ </method> <method name="_unhandled_key_input" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="event" type="InputEventKey" /> + <argument index="0" name="event" type="InputEvent" /> <description> - Called when an [InputEventKey] hasn't been consumed by [method _input] or any GUI. The input event propagates up through the node tree until a node consumes it. + Called when an [InputEventKey] or [InputEventShortcut] hasn't been consumed by [method _input] or any GUI. The input event propagates up through the node tree until a node consumes it. It is only called if unhandled key input processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_process_unhandled_key_input]. To consume the input event and stop it propagating further to other nodes, [method Viewport.set_input_as_handled] can be called. For gameplay input, this and [method _unhandled_input] are usually a better fit than [method _input] as they allow the GUI to intercept the events first. @@ -117,9 +109,11 @@ <return type="void" /> <argument index="0" name="node" type="Node" /> <argument index="1" name="legible_unique_name" type="bool" default="false" /> + <argument index="2" name="internal" type="int" enum="Node.InternalMode" default="0" /> <description> Adds a child node. Nodes can have any number of children, but every child must have a unique name. Child nodes are automatically deleted when the parent node is deleted, so an entire scene can be removed by deleting its topmost node. If [code]legible_unique_name[/code] is [code]true[/code], the child node will have a human-readable name based on the name of the node being instantiated instead of its type. + If [code]internal[/code] is different than [constant INTERNAL_MODE_DISABLED], the child will be added as internal node. Such nodes are ignored by methods like [method get_children], unless their parameter [code]include_internal[/code] is [code]true[/code].The intended usage is to hide the internal nodes from the user, so the user won't accidentally delete or modify them. Used by some GUI nodes, e.g. [ColorPicker]. See [enum InternalMode] for available modes. [b]Note:[/b] If the child node already has a parent, the function will fail. Use [method remove_child] first to remove the node from its current parent. For example: [codeblocks] [gdscript] @@ -149,6 +143,7 @@ Adds a [code]sibling[/code] node to current's node parent, at the same level as that node, right below it. If [code]legible_unique_name[/code] is [code]true[/code], the child node will have a human-readable name based on the name of the node being instantiated instead of its type. Use [method add_child] instead of this method if you don't need the child node to be added below a specific node in the list of children. + [b]Note:[/b] If this node is internal, the new sibling will be internal too (see [code]internal[/code] parameter in [method add_child]). </description> </method> <method name="add_to_group"> @@ -158,6 +153,7 @@ <description> Adds the node to a group. Groups are helpers to name and organize a subset of nodes, for example "enemies" or "collectables". A node can be in any number of groups. Nodes can be assigned a group at any time, but will not be added until they are inside the scene tree (see [method is_inside_tree]). See notes in the description, and the group methods in [SceneTree]. The [code]persistent[/code] option is used when packing node to [PackedScene] and saving to file. Non-persistent groups aren't stored. + [b]Note:[/b] For performance reasons, the order of node groups is [i]not[/i] guaranteed. The order of node groups should not be relied upon as it can vary across project runs. </description> </method> <method name="can_process" qualifiers="const"> @@ -208,22 +204,28 @@ <method name="get_child" qualifiers="const"> <return type="Node" /> <argument index="0" name="idx" type="int" /> + <argument index="1" name="include_internal" type="bool" default="false" /> <description> Returns a child node by its index (see [method get_child_count]). This method is often used for iterating all children of a node. Negative indices access the children from the last one. + If [code]include_internal[/code] is [code]true[/code], internal children are skipped (see [code]internal[/code] parameter in [method add_child]). To access a child node via its name, use [method get_node]. </description> </method> <method name="get_child_count" qualifiers="const"> <return type="int" /> + <argument index="0" name="include_internal" type="bool" default="false" /> <description> Returns the number of child nodes. + If [code]include_internal[/code] is [code]false[/code], internal children aren't counted (see [code]internal[/code] parameter in [method add_child]). </description> </method> <method name="get_children" qualifiers="const"> <return type="Node[]" /> + <argument index="0" name="include_internal" type="bool" default="false" /> <description> Returns an array of references to node's children. + If [code]include_internal[/code] is [code]false[/code], the returned array won't include internal children (see [code]internal[/code] parameter in [method add_child]). </description> </method> <method name="get_editor_description" qualifiers="const"> @@ -235,18 +237,21 @@ <return type="Array" /> <description> Returns an array listing the groups that the node is a member of. + [b]Note:[/b] For performance reasons, the order of node groups is [i]not[/i] guaranteed. The order of node groups should not be relied upon as it can vary across project runs. </description> </method> <method name="get_index" qualifiers="const"> <return type="int" /> + <argument index="0" name="include_internal" type="bool" default="false" /> <description> Returns the node's order in the scene tree branch. For example, if called on the first child node the position is [code]0[/code]. + If [code]include_internal[/code] is [code]false[/code], the index won't take internal children into account, i.e. first non-internal child will have index of 0 (see [code]internal[/code] parameter in [method add_child]). </description> </method> - <method name="get_network_master" qualifiers="const"> + <method name="get_multiplayer_authority" qualifiers="const"> <return type="int" /> <description> - Returns the peer ID of the network master for this node. See [method set_network_master]. + Returns the peer ID of the multiplayer authority for this node. See [method set_multiplayer_authority]. </description> </method> <method name="get_node" qualifiers="const"> @@ -384,7 +389,14 @@ <method name="is_displayed_folded" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the node is folded (collapsed) in the Scene dock. + Returns [code]true[/code] if the node is folded (collapsed) in the Scene dock. This method is only intended for use with editor tooling. + </description> + </method> + <method name="is_editable_instance" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="node" type="Node" /> + <description> + Returns [code]true[/code] if [code]node[/code] has editable children enabled relative to this node. This method is only intended for use with editor tooling. </description> </method> <method name="is_greater_than" qualifiers="const"> @@ -407,10 +419,10 @@ Returns [code]true[/code] if this node is currently inside a [SceneTree]. </description> </method> - <method name="is_network_master" qualifiers="const"> + <method name="is_multiplayer_authority" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the local system is the master of this node. + Returns [code]true[/code] if the local system is the multiplayer authority of this node. </description> </method> <method name="is_physics_processing" qualifiers="const"> @@ -461,6 +473,7 @@ <argument index="1" name="to_position" type="int" /> <description> Moves a child node to a different position (order) among the other children. Since calls, signals, etc are performed by tree order, changing the order of children nodes may be useful. + [b]Note:[/b] Internal children can only be moved within their expected "internal range" (see [code]internal[/code] parameter in [method add_child]). </description> </method> <method name="print_stray_nodes"> @@ -567,17 +580,17 @@ <argument index="0" name="method" type="StringName" /> <description> Sends a remote procedure call request for the given [code]method[/code] to peers on the network (and locally), optionally sending all additional arguments as arguments to the method called by the RPC. The call request will only be received by nodes with the same [NodePath], including the exact same node name. Behaviour depends on the RPC configuration for the given method, see [method rpc_config]. Methods are not exposed to RPCs by default. Returns an empty [Variant]. - [b]Note:[/b] You can only safely use RPCs on clients after you received the [code]connected_to_server[/code] signal from the [MultiplayerAPI]. You also need to keep track of the connection state, either by the [MultiplayerAPI] signals like [code]server_disconnected[/code] or by checking [code]get_multiplayer().network_peer.get_connection_status() == CONNECTION_CONNECTED[/code]. + [b]Note:[/b] You can only safely use RPCs on clients after you received the [code]connected_to_server[/code] signal from the [MultiplayerAPI]. You also need to keep track of the connection state, either by the [MultiplayerAPI] signals like [code]server_disconnected[/code] or by checking [code]get_multiplayer().peer.get_connection_status() == CONNECTION_CONNECTED[/code]. </description> </method> <method name="rpc_config"> <return type="int" /> <argument index="0" name="method" type="StringName" /> - <argument index="1" name="rpc_mode" type="int" enum="MultiplayerAPI.RPCMode" /> - <argument index="2" name="transfer_mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" /> + <argument index="1" name="rpc_mode" type="int" enum="RPCMode" /> + <argument index="2" name="transfer_mode" type="int" enum="TransferMode" default="2" /> <argument index="3" name="channel" type="int" default="0" /> <description> - Changes the RPC mode for the given [code]method[/code] to the given [code]rpc_mode[/code], optionally specifying the [code]transfer_mode[/code] and [code]channel[/code] (on supported peers). See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding keywords ([code]remote[/code], [code]master[/code], [code]puppet[/code], [code]remotesync[/code], [code]mastersync[/code], [code]puppetsync[/code]). By default, methods are not exposed to networking (and RPCs). + Changes the RPC mode for the given [code]method[/code] to the given [code]rpc_mode[/code], optionally specifying the [code]transfer_mode[/code] and [code]channel[/code] (on supported peers). See [enum RPCMode] and [enum TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc(any)[/code], [code]@rpc(auth)[/code]). By default, methods are not exposed to networking (and RPCs). </description> </method> <method name="rpc_id" qualifiers="vararg"> @@ -592,7 +605,15 @@ <return type="void" /> <argument index="0" name="fold" type="bool" /> <description> - Sets the folded state of the node in the Scene dock. + Sets the folded state of the node in the Scene dock. This method is only intended for use with editor tooling. + </description> + </method> + <method name="set_editable_instance"> + <return type="void" /> + <argument index="0" name="node" type="Node" /> + <argument index="1" name="is_editable" type="bool" /> + <description> + Sets the editable children state of [code]node[/code] relative to this node. This method is only intended for use with editor tooling. </description> </method> <method name="set_editor_description"> @@ -601,12 +622,12 @@ <description> </description> </method> - <method name="set_network_master"> + <method name="set_multiplayer_authority"> <return type="void" /> <argument index="0" name="id" type="int" /> <argument index="1" name="recursive" type="bool" default="true" /> <description> - Sets the node's network master to the peer with the given peer ID. The network master is the peer that has authority over the node on the network. Useful in conjunction with the [code]master[/code] and [code]puppet[/code] keywords. Inherited from the parent node by default, which ultimately defaults to peer ID 1 (the server). If [code]recursive[/code], the given peer is recursively set as the master for all children of this node. + Sets the node's multiplayer authority to the peer with the given peer ID. The multiplayer authority is the peer that has authority over the node on the network. Useful in conjunction with [method rpc_config] and the [MultiplayerAPI]. Inherited from the parent node by default, which ultimately defaults to peer ID 1 (the server). If [code]recursive[/code], the given peer is recursively set as the authority for all children of this node. </description> </method> <method name="set_physics_process"> @@ -881,5 +902,14 @@ Duplicate using instancing. An instance stays linked to the original so when the original changes, the instance changes too. </constant> + <constant name="INTERNAL_MODE_DISABLED" value="0" enum="InternalMode"> + Node will not be internal. + </constant> + <constant name="INTERNAL_MODE_FRONT" value="1" enum="InternalMode"> + Node will be placed at the front of parent's node list, before any non-internal sibling. + </constant> + <constant name="INTERNAL_MODE_BACK" value="2" enum="InternalMode"> + Node will be placed at the back of parent's node list, after any non-internal sibling. + </constant> </constants> </class> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 9ad79dc17a..ed045f8390 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -13,15 +13,15 @@ [codeblocks] [gdscript] var n = Node2D.new() - print("position" in n) # Prints "True". - print("other_property" in n) # Prints "False". + print("position" in n) # Prints "true". + print("other_property" in n) # Prints "false". [/gdscript] [csharp] var node = new Node2D(); // C# has no direct equivalent to GDScript's `in` operator here, but we // can achieve the same behavior by performing `Get` with a null check. - GD.Print(node.Get("position") != null); // Prints "True". - GD.Print(node.Get("other_property") != null); // Prints "False". + GD.Print(node.Get("position") != null); // Prints "true". + GD.Print(node.Get("other_property") != null); // Prints "false". [/csharp] [/codeblocks] The [code]in[/code] operator will evaluate to [code]true[/code] as long as the key exists, even if the value is [code]null[/code]. @@ -92,12 +92,12 @@ Calls the [code]method[/code] on the object and returns the result. This method supports a variable number of arguments, so parameters are passed as a comma separated list. Example: [codeblocks] [gdscript] - var node = Node2D.new() - node.call("set", "position", Vector2(42, 0)) + var node = Node3D.new() + node.call("rotate", Vector3(1.0, 0.0, 0.0), 1.571) [/gdscript] [csharp] - var node = new Node2D(); - node.Call("set", "position", new Vector2(42, 0)); + var node = new Node3D(); + node.Call("rotate", new Vector3(1f, 0f, 0f), 1.571f); [/csharp] [/codeblocks] [b]Note:[/b] In C#, the method name must be specified as snake_case if it is defined by a built-in Godot node. This doesn't apply to user-defined methods where you should use the same convention as in the C# source (typically PascalCase). @@ -110,12 +110,12 @@ Calls the [code]method[/code] on the object during idle time. This method supports a variable number of arguments, so parameters are passed as a comma separated list. Example: [codeblocks] [gdscript] - var node = Node2D.new() - node.call_deferred("set", "position", Vector2(42, 0)) + var node = Node3D.new() + node.call_deferred("rotate", Vector3(1.0, 0.0, 0.0), 1.571) [/gdscript] [csharp] - var node = new Node2D(); - node.CallDeferred("set", "position", new Vector2(42, 0)); + var node = new Node3D(); + node.CallDeferred("rotate", new Vector3(1f, 0f, 0f), 1.571f); [/csharp] [/codeblocks] [b]Note:[/b] In C#, the method name must be specified as snake_case if it is defined by a built-in Godot node. This doesn't apply to user-defined methods where you should use the same convention as in the C# source (typically PascalCase). @@ -129,12 +129,12 @@ Calls the [code]method[/code] on the object and returns the result. Contrarily to [method call], this method does not support a variable number of arguments but expects all parameters to be via a single [Array]. [codeblocks] [gdscript] - var node = Node2D.new() - node.callv("set", ["position", Vector2(42, 0)]) + var node = Node3D.new() + node.callv("rotate", [Vector3(1.0, 0.0, 0.0), 1.571]) [/gdscript] [csharp] - var node = new Node2D(); - node.Callv("set", new Godot.Collections.Array { "position", new Vector2(42, 0) }); + var node = new Node3D(); + node.Callv("rotate", new Godot.Collections.Array { new Vector3(1f, 0f, 0f), 1.571f }); [/csharp] [/codeblocks] </description> @@ -331,7 +331,8 @@ <method name="get_class" qualifiers="const"> <return type="String" /> <description> - Returns the object's class as a [String]. + Returns the object's class as a [String]. See also [method is_class]. + [b]Note:[/b] [method get_class] does not take [code]class_name[/code] declarations into account. If the object has a [code]class_name[/code] defined, the base class name will be returned instead. </description> </method> <method name="get_incoming_connections" qualifiers="const"> @@ -441,7 +442,8 @@ <return type="bool" /> <argument index="0" name="class" type="String" /> <description> - Returns [code]true[/code] if the object inherits from the given [code]class[/code]. + Returns [code]true[/code] if the object inherits from the given [code]class[/code]. See also [method get_class]. + [b]Note:[/b] [method is_class] does not take [code]class_name[/code] declarations into account. If the object has a [code]class_name[/code] defined, [method is_class] will return [code]false[/code] for that name. </description> </method> <method name="is_connected" qualifiers="const"> diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml index af92590da3..72f134e9e9 100644 --- a/doc/classes/PackedByteArray.xml +++ b/doc/classes/PackedByteArray.xml @@ -270,7 +270,7 @@ Converts UTF-8 encoded array to [String]. Slower than [method get_string_from_ascii] but supports UTF-8 encoded data. Use this function if you are unsure about the source of the data. For user input this function should always be preferred. Returns empty string if source array is not valid UTF-8 string. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="int" /> <description> diff --git a/doc/classes/PackedColorArray.xml b/doc/classes/PackedColorArray.xml index a5a5703bfa..2dfaefca23 100644 --- a/doc/classes/PackedColorArray.xml +++ b/doc/classes/PackedColorArray.xml @@ -56,7 +56,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="Color" /> <description> diff --git a/doc/classes/PackedFloat32Array.xml b/doc/classes/PackedFloat32Array.xml index 9e7dd8f99e..5c05dd9fa7 100644 --- a/doc/classes/PackedFloat32Array.xml +++ b/doc/classes/PackedFloat32Array.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="float" /> <description> diff --git a/doc/classes/PackedFloat64Array.xml b/doc/classes/PackedFloat64Array.xml index ff4cf0edf8..921ca23859 100644 --- a/doc/classes/PackedFloat64Array.xml +++ b/doc/classes/PackedFloat64Array.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="float" /> <description> diff --git a/doc/classes/PackedInt32Array.xml b/doc/classes/PackedInt32Array.xml index 2e9716b665..dfc9cbf939 100644 --- a/doc/classes/PackedInt32Array.xml +++ b/doc/classes/PackedInt32Array.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="int" /> <description> diff --git a/doc/classes/PackedInt64Array.xml b/doc/classes/PackedInt64Array.xml index 1e7fff3bc6..dd38a4f5c2 100644 --- a/doc/classes/PackedInt64Array.xml +++ b/doc/classes/PackedInt64Array.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="int" /> <description> diff --git a/doc/classes/PackedStringArray.xml b/doc/classes/PackedStringArray.xml index 04113d4a2e..c2055531a5 100644 --- a/doc/classes/PackedStringArray.xml +++ b/doc/classes/PackedStringArray.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="String" /> <description> diff --git a/doc/classes/PackedVector2Array.xml b/doc/classes/PackedVector2Array.xml index 76d3aff20e..51b7c951da 100644 --- a/doc/classes/PackedVector2Array.xml +++ b/doc/classes/PackedVector2Array.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="Vector2" /> <description> diff --git a/doc/classes/PackedVector3Array.xml b/doc/classes/PackedVector3Array.xml index 1a35f71619..04a5e3d42e 100644 --- a/doc/classes/PackedVector3Array.xml +++ b/doc/classes/PackedVector3Array.xml @@ -56,7 +56,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="Vector3" /> <description> diff --git a/doc/classes/ParticlesMaterial.xml b/doc/classes/ParticlesMaterial.xml index 3520d3b966..3b583f5c89 100644 --- a/doc/classes/ParticlesMaterial.xml +++ b/doc/classes/ParticlesMaterial.xml @@ -11,18 +11,18 @@ <tutorials> </tutorials> <methods> - <method name="get_param" qualifiers="const"> + <method name="get_param_max" qualifiers="const"> <return type="float" /> <argument index="0" name="param" type="int" enum="ParticlesMaterial.Parameter" /> <description> - Returns the value of the specified parameter. + Return the maximum value range for the given prameter. </description> </method> - <method name="get_param_randomness" qualifiers="const"> + <method name="get_param_min" qualifiers="const"> <return type="float" /> <argument index="0" name="param" type="int" enum="ParticlesMaterial.Parameter" /> <description> - Returns the randomness ratio associated with the specified parameter. + Return the minimum value range for the given parameter. </description> </method> <method name="get_param_texture" qualifiers="const"> @@ -39,20 +39,20 @@ Returns [code]true[/code] if the specified particle flag is enabled. See [enum ParticleFlags] for options. </description> </method> - <method name="set_param"> + <method name="set_param_max"> <return type="void" /> <argument index="0" name="param" type="int" enum="ParticlesMaterial.Parameter" /> <argument index="1" name="value" type="float" /> <description> - Sets the specified [enum Parameter]. + Sets the maximum value range for the given parameter. </description> </method> - <method name="set_param_randomness"> + <method name="set_param_min"> <return type="void" /> <argument index="0" name="param" type="int" enum="ParticlesMaterial.Parameter" /> - <argument index="1" name="randomness" type="float" /> + <argument index="1" name="value" type="float" /> <description> - Sets the randomness ratio for the specified [enum Parameter]. + Sets the minimum value range for the given parameter. </description> </method> <method name="set_param_texture"> @@ -73,53 +73,56 @@ </method> </methods> <members> - <member name="angle" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial rotation applied to each particle, in degrees. - Only applied when [member particle_flag_disable_z] or [member particle_flag_rotate_y] are [code]true[/code] or the [BaseMaterial3D] being used to draw the particle is using [constant BaseMaterial3D.BILLBOARD_PARTICLES]. - </member> <member name="angle_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> Each particle's rotation will be animated along this [CurveTexture]. </member> - <member name="angle_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Rotation randomness ratio. + <member name="angle_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum angle. </member> - <member name="angular_velocity" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial angular velocity applied to each particle. Sets the speed of rotation of the particle. - Only applied when [member particle_flag_disable_z] or [member particle_flag_rotate_y] are [code]true[/code] or the [BaseMaterial3D] being used to draw the particle is using [constant BaseMaterial3D.BILLBOARD_PARTICLES]. + <member name="angle_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum angle. </member> <member name="angular_velocity_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> Each particle's angular velocity will vary along this [CurveTexture]. </member> - <member name="angular_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Angular velocity randomness ratio. + <member name="angular_velocity_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum angular velocity. </member> - <member name="anim_offset" type="float" setter="set_param" getter="get_param" default="0.0"> - Particle animation offset. + <member name="angular_velocity_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum angular velocity. </member> <member name="anim_offset_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> Each particle's animation offset will vary along this [CurveTexture]. </member> - <member name="anim_offset_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Animation offset randomness ratio. + <member name="anim_offset_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum animation offset. </member> - <member name="anim_speed" type="float" setter="set_param" getter="get_param" default="0.0"> - Particle animation speed. + <member name="anim_offset_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum animation offset. </member> <member name="anim_speed_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> Each particle's animation speed will vary along this [CurveTexture]. </member> - <member name="anim_speed_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Animation speed randomness ratio. + <member name="anim_speed_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum particle animation speed. + </member> + <member name="anim_speed_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum particle animation speed. </member> <member name="attractor_interaction_enabled" type="bool" setter="set_attractor_interaction_enabled" getter="is_attractor_interaction_enabled" default="true"> + True if the interaction with particle attractors is enabled. </member> <member name="collision_bounce" type="float" setter="set_collision_bounce" getter="get_collision_bounce" default="0.0"> + Collision bouncyness. </member> <member name="collision_enabled" type="bool" setter="set_collision_enabled" getter="is_collision_enabled" default="false"> + True if collisions are enabled for this particle system. </member> <member name="collision_friction" type="float" setter="set_collision_friction" getter="get_collision_friction" default="0.0"> + Collision friction. </member> <member name="collision_use_scale" type="bool" setter="set_collision_use_scale" getter="is_collision_using_scale" default="false"> + Should collision take scale into account. </member> <member name="color" type="Color" setter="set_color" getter="get_color" default="Color(1, 1, 1, 1)"> Each particle's initial color. If the [GPUParticles2D]'s [code]texture[/code] is defined, it will be multiplied by this color. To have particle display color in a [BaseMaterial3D] make sure to set [member BaseMaterial3D.vertex_color_use_as_albedo] to [code]true[/code]. @@ -127,14 +130,12 @@ <member name="color_ramp" type="Texture2D" setter="set_color_ramp" getter="get_color_ramp"> Each particle's color will vary along this [GradientTexture] over its lifetime (multiplied with [member color]). </member> - <member name="damping" type="float" setter="set_param" getter="get_param" default="0.0"> - The rate at which particles lose velocity. - </member> <member name="damping_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> Damping will vary along this [CurveTexture]. </member> - <member name="damping_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Damping randomness ratio. + <member name="damping_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + </member> + <member name="damping_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> </member> <member name="direction" type="Vector3" setter="set_direction" getter="get_direction" default="Vector3(1, 0, 0)"> Unit vector specifying the particles' emission direction. @@ -178,42 +179,41 @@ <member name="gravity" type="Vector3" setter="set_gravity" getter="get_gravity" default="Vector3(0, -9.8, 0)"> Gravity applied to every particle. </member> - <member name="hue_variation" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial hue variation applied to each particle. - </member> <member name="hue_variation_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> Each particle's hue will vary along this [CurveTexture]. </member> - <member name="hue_variation_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Hue variation randomness ratio. + <member name="hue_variation_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum hue variation. + </member> + <member name="hue_variation_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum hue variation. </member> - <member name="initial_velocity" type="float" setter="set_param" getter="get_param" default="0.0"> - Initial velocity magnitude for each particle. Direction comes from [member spread] and the node's orientation. + <member name="initial_velocity_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum initial velocity. </member> - <member name="initial_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Initial velocity randomness ratio. + <member name="initial_velocity_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum initial velocity. </member> <member name="lifetime_randomness" type="float" setter="set_lifetime_randomness" getter="get_lifetime_randomness" default="0.0"> Particle lifetime randomness ratio. </member> - <member name="linear_accel" type="float" setter="set_param" getter="get_param" default="0.0"> - Linear acceleration applied to each particle in the direction of motion. - </member> <member name="linear_accel_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> Each particle's linear acceleration will vary along this [CurveTexture]. </member> - <member name="linear_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Linear acceleration randomness ratio. + <member name="linear_accel_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum linear acceleration. </member> - <member name="orbit_velocity" type="float" setter="set_param" getter="get_param"> - Orbital velocity applied to each particle. Makes the particles circle around origin. Specified in number of full rotations around origin per second. - Only available when [member particle_flag_disable_z] is [code]true[/code]. + <member name="linear_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum linear acceleration. </member> <member name="orbit_velocity_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> Each particle's orbital velocity will vary along this [CurveTexture]. </member> - <member name="orbit_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> - Orbital velocity randomness ratio. + <member name="orbit_velocity_max" type="float" setter="set_param_max" getter="get_param_max"> + Maximum orbit velocity. + </member> + <member name="orbit_velocity_min" type="float" setter="set_param_min" getter="get_param_min"> + Minimum orbit velocity. </member> <member name="particle_flag_align_y" type="bool" setter="set_particle_flag" getter="get_particle_flag" default="false"> Align Y axis of particle with the direction of its velocity. @@ -222,25 +222,25 @@ If [code]true[/code], particles will not move on the z axis. </member> <member name="particle_flag_rotate_y" type="bool" setter="set_particle_flag" getter="get_particle_flag" default="false"> - If [code]true[/code], particles rotate around Y axis by [member angle]. - </member> - <member name="radial_accel" type="float" setter="set_param" getter="get_param" default="0.0"> - Radial acceleration applied to each particle. Makes particle accelerate away from origin. + If [code]true[/code], particles rotate around Y axis by [member angle_min]. </member> <member name="radial_accel_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> Each particle's radial acceleration will vary along this [CurveTexture]. </member> - <member name="radial_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Radial acceleration randomness ratio. + <member name="radial_accel_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum radial acceleration. </member> - <member name="scale" type="float" setter="set_param" getter="get_param" default="1.0"> - Initial scale applied to each particle. + <member name="radial_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum radial acceleration. </member> <member name="scale_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> - Each particle's scale will vary along this [CurveTexture]. + Each particle's scale will vary along this [CurveTexture]. If a [CurveXYZTexture] is supplied instead, the scale will be separated per-axis. </member> - <member name="scale_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Scale randomness ratio. + <member name="scale_max" type="float" setter="set_param_max" getter="get_param_max" default="1.0"> + Maximum scale. + </member> + <member name="scale_min" type="float" setter="set_param_min" getter="get_param_min" default="1.0"> + Minimum scale. </member> <member name="spread" type="float" setter="set_spread" getter="get_spread" default="45.0"> Each particle's initial direction range from [code]+spread[/code] to [code]-spread[/code] degrees. @@ -253,52 +253,52 @@ </member> <member name="sub_emitter_mode" type="int" setter="set_sub_emitter_mode" getter="get_sub_emitter_mode" enum="ParticlesMaterial.SubEmitterMode" default="0"> </member> - <member name="tangential_accel" type="float" setter="set_param" getter="get_param" default="0.0"> - Tangential acceleration applied to each particle. Tangential acceleration is perpendicular to the particle's velocity giving the particles a swirling motion. - </member> <member name="tangential_accel_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture"> Each particle's tangential acceleration will vary along this [CurveTexture]. </member> - <member name="tangential_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness" default="0.0"> - Tangential acceleration randomness ratio. + <member name="tangential_accel_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0"> + Maximum tangential acceleration. + </member> + <member name="tangential_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> + Minimum tangential acceleration. </member> </members> <constants> <constant name="PARAM_INITIAL_LINEAR_VELOCITY" value="0" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set initial velocity properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set initial velocity properties. </constant> <constant name="PARAM_ANGULAR_VELOCITY" value="1" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angular velocity properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set angular velocity properties. </constant> <constant name="PARAM_ORBIT_VELOCITY" value="2" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set orbital velocity properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set orbital velocity properties. </constant> <constant name="PARAM_LINEAR_ACCEL" value="3" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set linear acceleration properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set linear acceleration properties. </constant> <constant name="PARAM_RADIAL_ACCEL" value="4" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set radial acceleration properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set radial acceleration properties. </constant> <constant name="PARAM_TANGENTIAL_ACCEL" value="5" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set tangential acceleration properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set tangential acceleration properties. </constant> <constant name="PARAM_DAMPING" value="6" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set damping properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set damping properties. </constant> <constant name="PARAM_ANGLE" value="7" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angle properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set angle properties. </constant> <constant name="PARAM_SCALE" value="8" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set scale properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set scale properties. </constant> <constant name="PARAM_HUE_VARIATION" value="9" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set hue variation properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set hue variation properties. </constant> <constant name="PARAM_ANIM_SPEED" value="10" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation speed properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set animation speed properties. </constant> <constant name="PARAM_ANIM_OFFSET" value="11" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation offset properties. + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set animation offset properties. </constant> <constant name="PARAM_MAX" value="12" enum="Parameter"> Represents the size of the [enum Parameter] enum. diff --git a/doc/classes/PhysicsDirectBodyState2D.xml b/doc/classes/PhysicsDirectBodyState2D.xml index 4c6adfca32..01c8933b51 100644 --- a/doc/classes/PhysicsDirectBodyState2D.xml +++ b/doc/classes/PhysicsDirectBodyState2D.xml @@ -7,6 +7,7 @@ Provides direct access to a physics body in the [PhysicsServer2D], allowing safe changes to physics properties. This object is passed via the direct state callback of dynamic bodies, and is intended for changing the direct state of that body. See [method RigidBody2D._integrate_forces]. </description> <tutorials> + <link title="Physics introduction">https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html</link> <link title="Ray-casting">https://docs.godotengine.org/en/latest/tutorials/physics/ray-casting.html</link> </tutorials> <methods> @@ -155,6 +156,9 @@ <member name="angular_velocity" type="float" setter="set_angular_velocity" getter="get_angular_velocity"> The body's rotational velocity. </member> + <member name="center_of_mass" type="Vector2" setter="" getter="get_center_of_mass"> + The body's center of mass. + </member> <member name="inverse_inertia" type="float" setter="" getter="get_inverse_inertia"> The inverse of the inertia of the body. </member> diff --git a/doc/classes/PhysicsDirectBodyState3D.xml b/doc/classes/PhysicsDirectBodyState3D.xml index 271668e339..839a83cfc3 100644 --- a/doc/classes/PhysicsDirectBodyState3D.xml +++ b/doc/classes/PhysicsDirectBodyState3D.xml @@ -7,6 +7,8 @@ Provides direct access to a physics body in the [PhysicsServer3D], allowing safe changes to physics properties. This object is passed via the direct state callback of dynamic bodies, and is intended for changing the direct state of that body. See [method RigidBody3D._integrate_forces]. </description> <tutorials> + <link title="Physics introduction">https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html</link> + <link title="Ray-casting">https://docs.godotengine.org/en/latest/tutorials/physics/ray-casting.html</link> </tutorials> <methods> <method name="add_central_force"> @@ -157,6 +159,7 @@ The body's rotational velocity. </member> <member name="center_of_mass" type="Vector3" setter="" getter="get_center_of_mass"> + The body's center of mass. </member> <member name="inverse_inertia" type="Vector3" setter="" getter="get_inverse_inertia"> The inverse of the inertia of the body. diff --git a/doc/classes/PhysicsDirectSpaceState2D.xml b/doc/classes/PhysicsDirectSpaceState2D.xml index e84b3e0e49..536c7e4e04 100644 --- a/doc/classes/PhysicsDirectSpaceState2D.xml +++ b/doc/classes/PhysicsDirectSpaceState2D.xml @@ -7,7 +7,8 @@ Direct access object to a space in the [PhysicsServer2D]. It's used mainly to do queries against objects and areas residing in a given space. </description> <tutorials> - <link title="Ray-Casting">https://docs.godotengine.org/en/latest/tutorials/physics/ray-casting.html</link> + <link title="Physics introduction">https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html</link> + <link title="Ray-casting">https://docs.godotengine.org/en/latest/tutorials/physics/ray-casting.html</link> </tutorials> <methods> <method name="cast_motion"> @@ -47,7 +48,7 @@ <argument index="0" name="point" type="Vector2" /> <argument index="1" name="max_results" type="int" default="32" /> <argument index="2" name="exclude" type="Array" default="[]" /> - <argument index="3" name="collision_mask" type="int" default="2147483647" /> + <argument index="3" name="collision_mask" type="int" default="4294967295" /> <argument index="4" name="collide_with_bodies" type="bool" default="true" /> <argument index="5" name="collide_with_areas" type="bool" default="false" /> <description> @@ -57,7 +58,7 @@ [code]metadata[/code]: The intersecting shape's metadata. This metadata is different from [method Object.get_meta], and is set with [method PhysicsServer2D.shape_set_data]. [code]rid[/code]: The intersecting object's [RID]. [code]shape[/code]: The shape index of the colliding shape. - Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect, or booleans to determine if the ray should collide with [PhysicsBody2D]s or [Area2D]s, respectively. + Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect (all layers by default), or booleans to determine if the ray should collide with [PhysicsBody2D]s or [Area2D]s, respectively. [b]Note:[/b] [ConcavePolygonShape2D]s and [CollisionPolygon2D]s in [code]Segments[/code] build mode are not solid shapes. Therefore, they will not be detected. </description> </method> @@ -67,10 +68,18 @@ <argument index="1" name="canvas_instance_id" type="int" /> <argument index="2" name="max_results" type="int" default="32" /> <argument index="3" name="exclude" type="Array" default="[]" /> - <argument index="4" name="collision_mask" type="int" default="2147483647" /> + <argument index="4" name="collision_mask" type="int" default="4294967295" /> <argument index="5" name="collide_with_bodies" type="bool" default="true" /> <argument index="6" name="collide_with_areas" type="bool" default="false" /> <description> + Checks whether a point is inside any solid shape, in a specific canvas layer given by [code]canvas_instance_id[/code]. The shapes the point is inside of are returned in an array containing dictionaries with the following fields: + [code]collider[/code]: The colliding object. + [code]collider_id[/code]: The colliding object's ID. + [code]metadata[/code]: The intersecting shape's metadata. This metadata is different from [method Object.get_meta], and is set with [method PhysicsServer2D.shape_set_data]. + [code]rid[/code]: The intersecting object's [RID]. + [code]shape[/code]: The shape index of the colliding shape. + Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect (all layers by default), or booleans to determine if the ray should collide with [PhysicsBody2D]s or [Area2D]s, respectively. + [b]Note:[/b] [ConcavePolygonShape2D]s and [CollisionPolygon2D]s in [code]Segments[/code] build mode are not solid shapes. Therefore, they will not be detected. </description> </method> <method name="intersect_ray"> @@ -78,7 +87,7 @@ <argument index="0" name="from" type="Vector2" /> <argument index="1" name="to" type="Vector2" /> <argument index="2" name="exclude" type="Array" default="[]" /> - <argument index="3" name="collision_mask" type="int" default="2147483647" /> + <argument index="3" name="collision_mask" type="int" default="4294967295" /> <argument index="4" name="collide_with_bodies" type="bool" default="true" /> <argument index="5" name="collide_with_areas" type="bool" default="false" /> <description> @@ -91,7 +100,7 @@ [code]rid[/code]: The intersecting object's [RID]. [code]shape[/code]: The shape index of the colliding shape. If the ray did not intersect anything, then an empty dictionary is returned instead. - Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect, or booleans to determine if the ray should collide with [PhysicsBody2D]s or [Area2D]s, respectively. + Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect (all layers by default), or booleans to determine if the ray should collide with [PhysicsBody2D]s or [Area2D]s, respectively. </description> </method> <method name="intersect_shape"> diff --git a/doc/classes/PhysicsDirectSpaceState3D.xml b/doc/classes/PhysicsDirectSpaceState3D.xml index 13db50a2c7..4e6bd8456f 100644 --- a/doc/classes/PhysicsDirectSpaceState3D.xml +++ b/doc/classes/PhysicsDirectSpaceState3D.xml @@ -7,6 +7,7 @@ Direct access object to a space in the [PhysicsServer3D]. It's used mainly to do queries against objects and areas residing in a given space. </description> <tutorials> + <link title="Physics introduction">https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html</link> <link title="Ray-casting">https://docs.godotengine.org/en/latest/tutorials/physics/ray-casting.html</link> </tutorials> <methods> @@ -47,7 +48,7 @@ <argument index="0" name="from" type="Vector3" /> <argument index="1" name="to" type="Vector3" /> <argument index="2" name="exclude" type="Array" default="[]" /> - <argument index="3" name="collision_mask" type="int" default="2147483647" /> + <argument index="3" name="collision_mask" type="int" default="4294967295" /> <argument index="4" name="collide_with_bodies" type="bool" default="true" /> <argument index="5" name="collide_with_areas" type="bool" default="false" /> <description> @@ -59,7 +60,7 @@ [code]rid[/code]: The intersecting object's [RID]. [code]shape[/code]: The shape index of the colliding shape. If the ray did not intersect anything, then an empty dictionary is returned instead. - Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect, or booleans to determine if the ray should collide with [PhysicsBody3D]s or [Area3D]s, respectively. + Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect (all layers by default), or booleans to determine if the ray should collide with [PhysicsBody3D]s or [Area3D]s, respectively. </description> </method> <method name="intersect_shape"> diff --git a/doc/classes/PhysicsServer2D.xml b/doc/classes/PhysicsServer2D.xml index 1df2fd0158..b3b7fcd956 100644 --- a/doc/classes/PhysicsServer2D.xml +++ b/doc/classes/PhysicsServer2D.xml @@ -373,7 +373,7 @@ </description> </method> <method name="body_get_param" qualifiers="const"> - <return type="float" /> + <return type="Variant" /> <argument index="0" name="body" type="RID" /> <argument index="1" name="param" type="int" enum="PhysicsServer2D.BodyParameter" /> <description> @@ -449,6 +449,13 @@ Removes a shape from a body. The shape is not deleted, so it can be reused afterwards. </description> </method> + <method name="body_reset_mass_properties"> + <return type="void" /> + <argument index="0" name="body" type="RID" /> + <description> + Restores the default inertia and center of mass based on shapes to cancel any custom values previously set using [method body_set_param]. + </description> + </method> <method name="body_set_axis_velocity"> <return type="void" /> <argument index="0" name="body" type="RID" /> @@ -489,6 +496,9 @@ <argument index="2" name="userdata" type="Variant" default="null" /> <description> Sets the function used to calculate physics for an object, if that object allows it (see [method body_set_omit_force_integration]). + The force integration function takes 2 arguments: + [code]state:[/code] [PhysicsDirectBodyState2D] used to retrieve and modify the body's state. + [code]userdata:[/code] Optional user data, if it was passed when calling [code]body_set_force_integration_callback[/code]. </description> </method> <method name="body_set_max_contacts_reported"> @@ -519,7 +529,7 @@ <return type="void" /> <argument index="0" name="body" type="RID" /> <argument index="1" name="param" type="int" enum="PhysicsServer2D.BodyParameter" /> - <argument index="2" name="value" type="float" /> + <argument index="2" name="value" type="Variant" /> <description> Sets a body parameter. See [enum BodyParameter] for a list of available parameters. </description> @@ -595,7 +605,8 @@ <argument index="2" name="motion" type="Vector2" /> <argument index="3" name="margin" type="float" default="0.08" /> <argument index="4" name="result" type="PhysicsTestMotionResult2D" default="null" /> - <argument index="5" name="exclude" type="Array" default="[]" /> + <argument index="5" name="collide_separation_ray" type="bool" default="false" /> + <argument index="6" name="exclude" type="Array" default="[]" /> <description> Returns [code]true[/code] if a collision would result from moving in the given direction from a given point in space. Margin increases the size of the shapes involved in the collision detection. [PhysicsTestMotionResult2D] can be passed to return additional information in. </description> @@ -726,6 +737,11 @@ <description> </description> </method> + <method name="separation_ray_shape_create"> + <return type="RID" /> + <description> + </description> + </method> <method name="set_active"> <return type="void" /> <argument index="0" name="active" type="bool" /> @@ -840,25 +856,28 @@ <constant name="SHAPE_WORLD_MARGIN" value="0" enum="ShapeType"> This is the constant for creating world margin shapes. A world margin shape is an [i]infinite[/i] line with an origin point, and a normal. Thus, it can be used for front/behind checks. </constant> - <constant name="SHAPE_SEGMENT" value="1" enum="ShapeType"> + <constant name="SHAPE_SEPARATION_RAY" value="1" enum="ShapeType"> + This is the constant for creating separation ray shapes. A separation ray is defined by a length and separates itself from what is touching its far endpoint. Useful for character controllers. + </constant> + <constant name="SHAPE_SEGMENT" value="2" enum="ShapeType"> This is the constant for creating segment shapes. A segment shape is a [i]finite[/i] line from a point A to a point B. It can be checked for intersections. </constant> - <constant name="SHAPE_CIRCLE" value="2" enum="ShapeType"> + <constant name="SHAPE_CIRCLE" value="3" enum="ShapeType"> This is the constant for creating circle shapes. A circle shape only has a radius. It can be used for intersections and inside/outside checks. </constant> - <constant name="SHAPE_RECTANGLE" value="3" enum="ShapeType"> + <constant name="SHAPE_RECTANGLE" value="4" enum="ShapeType"> This is the constant for creating rectangle shapes. A rectangle shape is defined by a width and a height. It can be used for intersections and inside/outside checks. </constant> - <constant name="SHAPE_CAPSULE" value="4" enum="ShapeType"> + <constant name="SHAPE_CAPSULE" value="5" enum="ShapeType"> This is the constant for creating capsule shapes. A capsule shape is defined by a radius and a length. It can be used for intersections and inside/outside checks. </constant> - <constant name="SHAPE_CONVEX_POLYGON" value="5" enum="ShapeType"> + <constant name="SHAPE_CONVEX_POLYGON" value="6" enum="ShapeType"> This is the constant for creating convex polygon shapes. A polygon is defined by a list of points. It can be used for intersections and inside/outside checks. Unlike the [member CollisionPolygon2D.polygon] property, polygons modified with [method shape_set_data] do not verify that the points supplied form is a convex polygon. </constant> - <constant name="SHAPE_CONCAVE_POLYGON" value="6" enum="ShapeType"> + <constant name="SHAPE_CONCAVE_POLYGON" value="7" enum="ShapeType"> This is the constant for creating concave polygon shapes. A polygon is defined by a list of points. It can be used for intersections checks, but not for inside/outside checks. </constant> - <constant name="SHAPE_CUSTOM" value="7" enum="ShapeType"> + <constant name="SHAPE_CUSTOM" value="8" enum="ShapeType"> This constant is used internally by the engine. Any attempt to create this kind of shape results in an error. </constant> <constant name="AREA_PARAM_GRAVITY" value="0" enum="AreaParameter"> @@ -901,7 +920,7 @@ This area replaces any gravity/damp calculated so far, but keeps calculating the rest of the areas, down to the default one. </constant> <constant name="BODY_MODE_STATIC" value="0" enum="BodyMode"> - Constant for static bodies. In this mode, a body can be only moved by user code. + Constant for static bodies. In this mode, a body can be only moved by user code and doesn't collide with other bodies along its path when moved. </constant> <constant name="BODY_MODE_KINEMATIC" value="1" enum="BodyMode"> Constant for kinematic bodies. In this mode, a body can be only moved by user code and collides with other bodies along its path. @@ -924,16 +943,19 @@ <constant name="BODY_PARAM_INERTIA" value="3" enum="BodyParameter"> Constant to set/get a body's inertia. </constant> - <constant name="BODY_PARAM_GRAVITY_SCALE" value="4" enum="BodyParameter"> + <constant name="BODY_PARAM_CENTER_OF_MASS" value="4" enum="BodyParameter"> + Constant to set/get a body's center of mass. + </constant> + <constant name="BODY_PARAM_GRAVITY_SCALE" value="5" enum="BodyParameter"> Constant to set/get a body's gravity multiplier. </constant> - <constant name="BODY_PARAM_LINEAR_DAMP" value="5" enum="BodyParameter"> + <constant name="BODY_PARAM_LINEAR_DAMP" value="6" enum="BodyParameter"> Constant to set/get a body's linear dampening factor. </constant> - <constant name="BODY_PARAM_ANGULAR_DAMP" value="6" enum="BodyParameter"> + <constant name="BODY_PARAM_ANGULAR_DAMP" value="7" enum="BodyParameter"> Constant to set/get a body's angular dampening factor. </constant> - <constant name="BODY_PARAM_MAX" value="7" enum="BodyParameter"> + <constant name="BODY_PARAM_MAX" value="8" enum="BodyParameter"> Represents the size of the [enum BodyParameter] enum. </constant> <constant name="BODY_STATE_TRANSFORM" value="0" enum="BodyState"> diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index d46e38ac5f..2fbe84b8b1 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -347,7 +347,7 @@ </description> </method> <method name="body_get_param" qualifiers="const"> - <return type="float" /> + <return type="Variant" /> <argument index="0" name="body" type="RID" /> <argument index="1" name="param" type="int" enum="PhysicsServer3D.BodyParameter" /> <description> @@ -430,6 +430,13 @@ Removes a shape from a body. The shape is not deleted, so it can be reused afterwards. </description> </method> + <method name="body_reset_mass_properties"> + <return type="void" /> + <argument index="0" name="body" type="RID" /> + <description> + Restores the default inertia and center of mass based on shapes to cancel any custom values previously set using [method body_set_param]. + </description> + </method> <method name="body_set_axis_lock"> <return type="void" /> <argument index="0" name="body" type="RID" /> @@ -478,6 +485,9 @@ <argument index="2" name="userdata" type="Variant" default="null" /> <description> Sets the function used to calculate physics for an object, if that object allows it (see [method body_set_omit_force_integration]). + The force integration function takes 2 arguments: + [code]state:[/code] [PhysicsDirectBodyState3D] used to retrieve and modify the body's state. + [code]userdata:[/code] Optional user data, if it was passed when calling [code]body_set_force_integration_callback[/code]. </description> </method> <method name="body_set_max_contacts_reported"> @@ -508,7 +518,7 @@ <return type="void" /> <argument index="0" name="body" type="RID" /> <argument index="1" name="param" type="int" enum="PhysicsServer3D.BodyParameter" /> - <argument index="2" name="value" type="float" /> + <argument index="2" name="value" type="Variant" /> <description> Sets a body parameter. A list of available parameters is on the [enum BodyParameter] constants. </description> @@ -571,7 +581,8 @@ <argument index="2" name="motion" type="Vector3" /> <argument index="3" name="margin" type="float" default="0.001" /> <argument index="4" name="result" type="PhysicsTestMotionResult3D" default="null" /> - <argument index="5" name="exclude" type="Array" default="[]" /> + <argument index="5" name="collide_separation_ray" type="bool" default="false" /> + <argument index="6" name="exclude" type="Array" default="[]" /> <description> Returns [code]true[/code] if a collision would result from moving in the given direction from a given point in space. Margin increases the size of the shapes involved in the collision detection. [PhysicsTestMotionResult3D] can be passed to return additional information in. </description> @@ -849,6 +860,11 @@ <description> </description> </method> + <method name="separation_ray_shape_create"> + <return type="RID" /> + <description> + </description> + </method> <method name="set_active"> <return type="void" /> <argument index="0" name="active" type="bool" /> @@ -1171,31 +1187,34 @@ <constant name="SHAPE_PLANE" value="0" enum="ShapeType"> The [Shape3D] is a [WorldMarginShape3D]. </constant> - <constant name="SHAPE_SPHERE" value="1" enum="ShapeType"> + <constant name="SHAPE_SEPARATION_RAY" value="1" enum="ShapeType"> + The [Shape3D] is a [SeparationRayShape3D]. + </constant> + <constant name="SHAPE_SPHERE" value="2" enum="ShapeType"> The [Shape3D] is a [SphereShape3D]. </constant> - <constant name="SHAPE_BOX" value="2" enum="ShapeType"> + <constant name="SHAPE_BOX" value="3" enum="ShapeType"> The [Shape3D] is a [BoxShape3D]. </constant> - <constant name="SHAPE_CAPSULE" value="3" enum="ShapeType"> + <constant name="SHAPE_CAPSULE" value="4" enum="ShapeType"> The [Shape3D] is a [CapsuleShape3D]. </constant> - <constant name="SHAPE_CYLINDER" value="4" enum="ShapeType"> + <constant name="SHAPE_CYLINDER" value="5" enum="ShapeType"> The [Shape3D] is a [CylinderShape3D]. </constant> - <constant name="SHAPE_CONVEX_POLYGON" value="5" enum="ShapeType"> + <constant name="SHAPE_CONVEX_POLYGON" value="6" enum="ShapeType"> The [Shape3D] is a [ConvexPolygonShape3D]. </constant> - <constant name="SHAPE_CONCAVE_POLYGON" value="6" enum="ShapeType"> + <constant name="SHAPE_CONCAVE_POLYGON" value="7" enum="ShapeType"> The [Shape3D] is a [ConcavePolygonShape3D]. </constant> - <constant name="SHAPE_HEIGHTMAP" value="7" enum="ShapeType"> + <constant name="SHAPE_HEIGHTMAP" value="8" enum="ShapeType"> The [Shape3D] is a [HeightMapShape3D]. </constant> - <constant name="SHAPE_SOFT_BODY" value="8" enum="ShapeType"> + <constant name="SHAPE_SOFT_BODY" value="9" enum="ShapeType"> The [Shape3D] is a [SoftBody3D]. </constant> - <constant name="SHAPE_CUSTOM" value="9" enum="ShapeType"> + <constant name="SHAPE_CUSTOM" value="10" enum="ShapeType"> This constant is used internally by the engine. Any attempt to create this kind of shape results in an error. </constant> <constant name="AREA_PARAM_GRAVITY" value="0" enum="AreaParameter"> @@ -1222,6 +1241,18 @@ <constant name="AREA_PARAM_PRIORITY" value="7" enum="AreaParameter"> Constant to set/get the priority (order of processing) of an area. </constant> + <constant name="AREA_PARAM_WIND_FORCE_MAGNITUDE" value="8" enum="AreaParameter"> + Constant to set/get the magnitude of area-specific wind force. + </constant> + <constant name="AREA_PARAM_WIND_SOURCE" value="9" enum="AreaParameter"> + Constant to set/get the 3D vector that specifies the origin from which an area-specific wind blows. + </constant> + <constant name="AREA_PARAM_WIND_DIRECTION" value="10" enum="AreaParameter"> + Constant to set/get the 3D vector that specifies the direction in which an area-specific wind blows. + </constant> + <constant name="AREA_PARAM_WIND_ATTENUATION_FACTOR" value="11" enum="AreaParameter"> + Constant to set/get the exponential rate at which wind force decreases with distance from its origin. + </constant> <constant name="AREA_SPACE_OVERRIDE_DISABLED" value="0" enum="AreaSpaceOverrideMode"> This area does not affect gravity/damp. These are generally areas that exist only to detect collisions, and objects entering or exiting them. </constant> @@ -1238,7 +1269,7 @@ This area replaces any gravity/damp calculated so far, but keeps calculating the rest of the areas, down to the default one. </constant> <constant name="BODY_MODE_STATIC" value="0" enum="BodyMode"> - Constant for static bodies. In this mode, a body can be only moved by user code. + Constant for static bodies. In this mode, a body can be only moved by user code and doesn't collide with other bodies along its path when moved. </constant> <constant name="BODY_MODE_KINEMATIC" value="1" enum="BodyMode"> Constant for kinematic bodies. In this mode, a body can be only moved by user code and collides with other bodies along its path. @@ -1258,16 +1289,22 @@ <constant name="BODY_PARAM_MASS" value="2" enum="BodyParameter"> Constant to set/get a body's mass. </constant> - <constant name="BODY_PARAM_GRAVITY_SCALE" value="3" enum="BodyParameter"> + <constant name="BODY_PARAM_INERTIA" value="3" enum="BodyParameter"> + Constant to set/get a body's inertia. + </constant> + <constant name="BODY_PARAM_CENTER_OF_MASS" value="4" enum="BodyParameter"> + Constant to set/get a body's center of mass. + </constant> + <constant name="BODY_PARAM_GRAVITY_SCALE" value="5" enum="BodyParameter"> Constant to set/get a body's gravity multiplier. </constant> - <constant name="BODY_PARAM_LINEAR_DAMP" value="4" enum="BodyParameter"> + <constant name="BODY_PARAM_LINEAR_DAMP" value="6" enum="BodyParameter"> Constant to set/get a body's linear dampening factor. </constant> - <constant name="BODY_PARAM_ANGULAR_DAMP" value="5" enum="BodyParameter"> + <constant name="BODY_PARAM_ANGULAR_DAMP" value="7" enum="BodyParameter"> Constant to set/get a body's angular dampening factor. </constant> - <constant name="BODY_PARAM_MAX" value="6" enum="BodyParameter"> + <constant name="BODY_PARAM_MAX" value="8" enum="BodyParameter"> Represents the size of the [enum BodyParameter] enum. </constant> <constant name="BODY_STATE_TRANSFORM" value="0" enum="BodyState"> diff --git a/doc/classes/PhysicsShapeQueryParameters2D.xml b/doc/classes/PhysicsShapeQueryParameters2D.xml index 8b006c68e7..b54de15d15 100644 --- a/doc/classes/PhysicsShapeQueryParameters2D.xml +++ b/doc/classes/PhysicsShapeQueryParameters2D.xml @@ -17,7 +17,7 @@ <member name="collide_with_bodies" type="bool" setter="set_collide_with_bodies" getter="is_collide_with_bodies_enabled" default="true"> If [code]true[/code], the query will take [PhysicsBody2D]s into account. </member> - <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="2147483647"> + <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="4294967295"> The physics layers the query will detect (as a bitmask). By default, all collision layers are detected. See [url=https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information. </member> <member name="exclude" type="Array" setter="set_exclude" getter="get_exclude" default="[]"> diff --git a/doc/classes/PhysicsShapeQueryParameters3D.xml b/doc/classes/PhysicsShapeQueryParameters3D.xml index de9b623591..f74d1b5e48 100644 --- a/doc/classes/PhysicsShapeQueryParameters3D.xml +++ b/doc/classes/PhysicsShapeQueryParameters3D.xml @@ -17,7 +17,7 @@ <member name="collide_with_bodies" type="bool" setter="set_collide_with_bodies" getter="is_collide_with_bodies_enabled" default="true"> If [code]true[/code], the query will take [PhysicsBody3D]s into account. </member> - <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="2147483647"> + <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="4294967295"> The physics layers the query will detect (as a bitmask). By default, all collision layers are detected. See [url=https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information. </member> <member name="exclude" type="Array" setter="set_exclude" getter="get_exclude" default="[]"> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 0d1fa0e70f..21d974e233 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -625,19 +625,19 @@ </member> <member name="input/ui_text_backspace_all_to_left" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_backspace_all_to_left.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_backspace_all_to_left.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_backspace_word" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_backspace_word.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_backspace_word.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_document_end" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_document_end.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_document_end.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_document_start" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_document_start.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_document_start.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_down" type="Dictionary" setter="" getter=""> </member> @@ -645,11 +645,11 @@ </member> <member name="input/ui_text_caret_line_end" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_line_end.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_line_end.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_line_start" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_line_start.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_line_start.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_page_down" type="Dictionary" setter="" getter=""> </member> @@ -661,11 +661,11 @@ </member> <member name="input/ui_text_caret_word_left" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_word_left.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_word_left.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_word_right" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_word_right.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_word_right.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_completion_accept" type="Dictionary" setter="" getter=""> </member> @@ -679,11 +679,11 @@ </member> <member name="input/ui_text_delete_all_to_right" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_delete_all_to_right.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_delete_all_to_right.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_delete_word" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_delete_word.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_delete_word.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_indent" type="Dictionary" setter="" getter=""> </member> @@ -695,11 +695,11 @@ </member> <member name="input/ui_text_scroll_down" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_scroll_down.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_scroll_down.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_scroll_up" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_scroll_up.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_scroll_up.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_select_all" type="Dictionary" setter="" getter=""> </member> @@ -726,7 +726,7 @@ <member name="input_devices/pen_tablet/driver" type="String" setter="" getter=""> Specifies the tablet driver to use. If left empty, the default driver will be used. </member> - <member name="input_devices/pen_tablet/driver.Windows" type="String" setter="" getter=""> + <member name="input_devices/pen_tablet/driver.windows" type="String" setter="" getter=""> Override for [member input_devices/pen_tablet/driver] on Windows. </member> <member name="input_devices/pointing/emulate_mouse_from_touch" type="bool" setter="" getter="" default="true"> @@ -1473,6 +1473,9 @@ </member> <member name="rendering/2d/snap/snap_2d_vertices_to_pixel" type="bool" setter="" getter="" default="false"> </member> + <member name="rendering/3d/viewport/scale" type="int" setter="" getter="" default="0"> + Scale the 3D render buffer based on the viewport size. The smaller the faster 3D rendering is performed but at the cost of quality. + </member> <member name="rendering/anti_aliasing/quality/msaa" type="int" setter="" getter="" default="0"> Sets the number of MSAA samples to use (as a power of two). MSAA is used to reduce aliasing around the edges of polygons. A higher MSAA value results in smoother edges but can be significantly slower on some hardware. </member> diff --git a/doc/classes/RandomNumberGenerator.xml b/doc/classes/RandomNumberGenerator.xml index 2cf3cbb83d..fed6568d22 100644 --- a/doc/classes/RandomNumberGenerator.xml +++ b/doc/classes/RandomNumberGenerator.xml @@ -4,7 +4,7 @@ A class for generating pseudo-random numbers. </brief_description> <description> - RandomNumberGenerator is a class for generating pseudo-random numbers. It currently uses [url=http://www.pcg-random.org/]PCG32[/url]. + RandomNumberGenerator is a class for generating pseudo-random numbers. It currently uses [url=https://www.pcg-random.org/]PCG32[/url]. [b]Note:[/b] The underlying algorithm is an implementation detail. As a result, it should not be depended upon for reproducible random streams across Godot versions. To generate a random float number (within a given range) based on a time-dependant seed: [codeblock] diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml index 24f7f4274e..428fa2575c 100644 --- a/doc/classes/RenderingDevice.xml +++ b/doc/classes/RenderingDevice.xml @@ -359,6 +359,14 @@ <description> </description> </method> + <method name="get_driver_resource"> + <return type="int" /> + <argument index="0" name="resource" type="int" enum="RenderingDevice.DriverResource" /> + <argument index="1" name="rid" type="RID" /> + <argument index="2" name="index" type="int" /> + <description> + </description> + </method> <method name="get_frame_delay" qualifiers="const"> <return type="int" /> <description> @@ -646,6 +654,32 @@ </constant> <constant name="BARRIER_MASK_NO_BARRIER" value="8"> </constant> + <constant name="DRIVER_RESOURCE_VULKAN_DEVICE" value="0" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_PHYSICAL_DEVICE" value="1" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_INSTANCE" value="2" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_QUEUE" value="3" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_QUEUE_FAMILY_INDEX" value="4" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_IMAGE" value="5" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_IMAGE_VIEW" value="6" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_IMAGE_NATIVE_TEXTURE_FORMAT" value="7" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_SAMPLER" value="8" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_DESCRIPTOR_SET" value="9" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_BUFFER" value="10" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_COMPUTE_PIPELINE" value="11" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_RENDER_PIPELINE" value="12" enum="DriverResource"> + </constant> <constant name="DATA_FORMAT_R4G4_UNORM_PACK8" value="0" enum="DataFormat"> </constant> <constant name="DATA_FORMAT_R4G4B4A4_UNORM_PACK16" value="1" enum="DataFormat"> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index df8bfb7e34..c0d7cca840 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -1156,6 +1156,11 @@ <description> </description> </method> + <method name="get_rendering_device" qualifiers="const"> + <return type="RenderingDevice" /> + <description> + </description> + </method> <method name="get_rendering_info"> <return type="int" /> <argument index="0" name="info" type="int" enum="RenderingServer.RenderingInfo" /> @@ -3082,6 +3087,14 @@ If [code]true[/code], render the contents of the viewport directly to screen. This allows a low-level optimization where you can skip drawing a viewport to the root viewport. While this optimization can result in a significant increase in speed (especially on older devices), it comes at a cost of usability. When this is enabled, you cannot read from the viewport or from the [code]SCREEN_TEXTURE[/code]. You also lose the benefit of certain window settings, such as the various stretch modes. Another consequence to be aware of is that in 2D the rendering happens in window coordinates, so if you have a viewport that is double the size of the window, and you set this, then only the portion that fits within the window will be drawn, no automatic scaling is possible, even if your game scene is significantly larger than the window size. </description> </method> + <method name="viewport_set_scale_3d"> + <return type="void" /> + <argument index="0" name="viewport" type="RID" /> + <argument index="1" name="scale" type="int" enum="RenderingServer.ViewportScale3D" /> + <description> + Sets the scale at which we render 3D contents. + </description> + </method> <method name="viewport_set_scenario"> <return type="void" /> <argument index="0" name="viewport" type="RID" /> @@ -3896,6 +3909,16 @@ </constant> <constant name="VIEWPORT_DEBUG_DRAW_OCCLUDERS" value="23" enum="ViewportDebugDraw"> </constant> + <constant name="VIEWPORT_SCALE_3D_DISABLED" value="0" enum="ViewportScale3D"> + </constant> + <constant name="VIEWPORT_SCALE_3D_75_PERCENT" value="1" enum="ViewportScale3D"> + </constant> + <constant name="VIEWPORT_SCALE_3D_50_PERCENT" value="2" enum="ViewportScale3D"> + </constant> + <constant name="VIEWPORT_SCALE_3D_33_PERCENT" value="3" enum="ViewportScale3D"> + </constant> + <constant name="VIEWPORT_SCALE_3D_25_PERCENT" value="4" enum="ViewportScale3D"> + </constant> <constant name="SKY_MODE_AUTOMATIC" value="0" enum="SkyMode"> </constant> <constant name="SKY_MODE_QUALITY" value="1" enum="SkyMode"> diff --git a/doc/classes/RigidBody2D.xml b/doc/classes/RigidBody2D.xml index db16552db3..0702955495 100644 --- a/doc/classes/RigidBody2D.xml +++ b/doc/classes/RigidBody2D.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="RigidBody2D" inherits="PhysicsBody2D" version="4.0"> <brief_description> - A body that is controlled by the 2D physics engine. + Physics Body which is moved by 2D physics simulation. Useful for objects that have gravity and can be pushed by other objects. </brief_description> <description> This node implements simulated 2D physics. You do not control a RigidBody2D directly. Instead, you apply forces to it (gravity, impulses, etc.) and the physics simulation calculates the resulting movement based on its mass, friction, and other physical properties. @@ -99,6 +99,13 @@ <member name="can_sleep" type="bool" setter="set_can_sleep" getter="is_able_to_sleep" default="true"> If [code]true[/code], the body can enter sleep mode when there is no movement. See [member sleeping]. </member> + <member name="center_of_mass" type="Vector2" setter="set_center_of_mass" getter="get_center_of_mass" default="Vector2(0, 0)"> + The body's custom center of mass, relative to the body's origin position, when [member center_of_mass_mode] is set to [constant CENTER_OF_MASS_MODE_CUSTOM]. This is the balanced point of the body, where applied forces only cause linear acceleration. Applying forces outside of the center of mass causes angular acceleration. + When [member center_of_mass_mode] is set to [constant CENTER_OF_MASS_MODE_AUTO] (default value), the center of mass is automatically computed. + </member> + <member name="center_of_mass_mode" type="int" setter="set_center_of_mass_mode" getter="get_center_of_mass_mode" enum="RigidBody2D.CenterOfMassMode" default="0"> + Defines the way the body's center of mass is set. See [enum CenterOfMassMode] for possible values. + </member> <member name="contact_monitor" type="bool" setter="set_contact_monitor" getter="is_contact_monitor_enabled" default="false"> If [code]true[/code], the body will emit signals when it collides with another RigidBody2D. See also [member contacts_reported]. </member> @@ -116,8 +123,9 @@ <member name="gravity_scale" type="float" setter="set_gravity_scale" getter="get_gravity_scale" default="1.0"> Multiplies the gravity applied to the body. The body's gravity is calculated from the [b]Default Gravity[/b] value in [b]Project > Project Settings > Physics > 2d[/b] and/or any additional gravity vector applied by [Area2D]s. </member> - <member name="inertia" type="float" setter="set_inertia" getter="get_inertia"> - The body's moment of inertia. This is like mass, but for rotation: it determines how much torque it takes to rotate the body. The moment of inertia is usually computed automatically from the mass and the shapes, but this function allows you to set a custom value. Set 0 inertia to return to automatically computing it. + <member name="inertia" type="float" setter="set_inertia" getter="get_inertia" default="0.0"> + The body's moment of inertia. This is like mass, but for rotation: it determines how much torque it takes to rotate the body. The moment of inertia is usually computed automatically from the mass and the shapes, but this property allows you to set a custom value. + If set to [code]0[/code], inertia is automatically computed (default value). </member> <member name="linear_damp" type="float" setter="set_linear_damp" getter="get_linear_damp" default="-1.0"> Damps the body's [member linear_velocity]. If [code]-1[/code], the body will use the [b]Default Linear Damp[/b] in [b]Project > Project Settings > Physics > 2d[/b]. @@ -131,6 +139,7 @@ </member> <member name="mode" type="int" setter="set_mode" getter="get_mode" enum="RigidBody2D.Mode" default="0"> The body's mode. See [enum Mode] for possible values. + For a body that uses only Static or Kinematic mode, use [StaticBody2D] or [AnimatableBody2D] instead. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> The physics material override for the body. @@ -199,7 +208,13 @@ Locked dynamic body mode. Similar to [constant MODE_DYNAMIC], but the body can not rotate. </constant> <constant name="MODE_KINEMATIC" value="3" enum="Mode"> - Kinematic body mode. The body behaves like a [StaticBody2D] with [member StaticBody2D.kinematic_motion] enabled, and must be moved by user code. + Kinematic body mode. The body behaves like a [AnimatableBody2D], and must be moved by code. + </constant> + <constant name="CENTER_OF_MASS_MODE_AUTO" value="0" enum="CenterOfMassMode"> + In this mode, the body's center of mass is calculated automatically based on its shapes. + </constant> + <constant name="CENTER_OF_MASS_MODE_CUSTOM" value="1" enum="CenterOfMassMode"> + In this mode, the body's center of mass is set through [member center_of_mass]. Defaults to the body's origin position. </constant> <constant name="CCD_MODE_DISABLED" value="0" enum="CCDMode"> Continuous collision detection disabled. This is the fastest way to detect body collisions, but can miss small, fast-moving objects. diff --git a/doc/classes/RigidBody3D.xml b/doc/classes/RigidBody3D.xml index f4299335bf..1be35b0576 100644 --- a/doc/classes/RigidBody3D.xml +++ b/doc/classes/RigidBody3D.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="RigidBody3D" inherits="PhysicsBody3D" version="4.0"> <brief_description> - Physics Body whose position is determined through physics simulation in 3D space. + Physics Body which is moved by 3D physics simulation. Useful for objects that have gravity and can be pushed by other objects. </brief_description> <description> This is the node that implements full 3D physics. This means that you do not control a RigidBody3D directly. Instead, you can apply forces to it (gravity, impulses, etc.), and the physics simulation will calculate the resulting movement, collision, bouncing, rotating, etc. @@ -102,6 +102,13 @@ <member name="can_sleep" type="bool" setter="set_can_sleep" getter="is_able_to_sleep" default="true"> If [code]true[/code], the body can enter sleep mode when there is no movement. See [member sleeping]. </member> + <member name="center_of_mass" type="Vector3" setter="set_center_of_mass" getter="get_center_of_mass" default="Vector3(0, 0, 0)"> + The body's custom center of mass, relative to the body's origin position, when [member center_of_mass_mode] is set to [constant CENTER_OF_MASS_MODE_CUSTOM]. This is the balanced point of the body, where applied forces only cause linear acceleration. Applying forces outside of the center of mass causes angular acceleration. + When [member center_of_mass_mode] is set to [constant CENTER_OF_MASS_MODE_AUTO] (default value), the center of mass is automatically computed. + </member> + <member name="center_of_mass_mode" type="int" setter="set_center_of_mass_mode" getter="get_center_of_mass_mode" enum="RigidBody3D.CenterOfMassMode" default="0"> + Defines the way the body's center of mass is set. See [enum CenterOfMassMode] for possible values. + </member> <member name="contact_monitor" type="bool" setter="set_contact_monitor" getter="is_contact_monitor_enabled" default="false"> If [code]true[/code], the RigidBody3D will emit signals when it collides with another RigidBody3D. See also [member contacts_reported]. </member> @@ -119,6 +126,10 @@ <member name="gravity_scale" type="float" setter="set_gravity_scale" getter="get_gravity_scale" default="1.0"> This is multiplied by the global 3D gravity setting found in [b]Project > Project Settings > Physics > 3d[/b] to produce RigidBody3D's gravity. For example, a value of 1 will be normal gravity, 2 will apply double gravity, and 0.5 will apply half gravity to this object. </member> + <member name="inertia" type="Vector3" setter="set_inertia" getter="get_inertia" default="Vector3(0, 0, 0)"> + The body's moment of inertia. This is like mass, but for rotation: it determines how much torque it takes to rotate the body on each axis. The moment of inertia is usually computed automatically from the mass and the shapes, but this property allows you to set a custom value. + If set to [code]Vector3.ZERO[/code], inertia is automatically computed (default value). + </member> <member name="linear_damp" type="float" setter="set_linear_damp" getter="get_linear_damp" default="-1.0"> The body's linear damp. Cannot be less than -1.0. If this value is different from -1.0, any linear damp derived from the world or areas will be overridden. See [member ProjectSettings.physics/3d/default_linear_damp] for more details about damping. @@ -130,7 +141,8 @@ The body's mass. </member> <member name="mode" type="int" setter="set_mode" getter="get_mode" enum="RigidBody3D.Mode" default="0"> - The body mode. See [enum Mode] for possible values. + The body's mode. See [enum Mode] for possible values. + For a body that uses only Static or Kinematic mode, use [StaticBody3D] or [AnimatableBody3D] instead. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> The physics material override for the body. @@ -201,7 +213,13 @@ Locked dynamic body mode. Similar to [constant MODE_DYNAMIC], but the body can not rotate. </constant> <constant name="MODE_KINEMATIC" value="3" enum="Mode"> - Kinematic body mode. The body behaves like a [StaticBody3D] with [member StaticBody3D.kinematic_motion] enabled, and can only move by user code. + Kinematic body mode. The body behaves like a [AnimatableBody3D], and can only move by user code. + </constant> + <constant name="CENTER_OF_MASS_MODE_AUTO" value="0" enum="CenterOfMassMode"> + In this mode, the body's center of mass is calculated automatically based on its shapes. + </constant> + <constant name="CENTER_OF_MASS_MODE_CUSTOM" value="1" enum="CenterOfMassMode"> + In this mode, the body's center of mass is set through [member center_of_mass]. Defaults to the body's origin position. </constant> </constants> </class> diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index 59e3190213..e81eff35ac 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -215,7 +215,7 @@ </member> <member name="multiplayer_poll" type="bool" setter="set_multiplayer_poll_enabled" getter="is_multiplayer_poll_enabled" default="true"> If [code]true[/code] (default value), enables automatic polling of the [MultiplayerAPI] for this SceneTree during [signal process_frame]. - If [code]false[/code], you need to manually call [method MultiplayerAPI.poll] to process network packets and deliver RPCs/RSETs. This allows running RPCs/RSETs in a different loop (e.g. physics, thread, specific time step) and for manual [Mutex] protection when accessing the [MultiplayerAPI] from threads. + If [code]false[/code], you need to manually call [method MultiplayerAPI.poll] to process network packets and deliver RPCs. This allows running RPCs in a different loop (e.g. physics, thread, specific time step) and for manual [Mutex] protection when accessing the [MultiplayerAPI] from threads. </member> <member name="paused" type="bool" setter="set_pause" getter="is_paused" default="false"> If [code]true[/code], the [SceneTree] is paused. Doing so will have the following behavior: diff --git a/doc/classes/SeparationRayShape2D.xml b/doc/classes/SeparationRayShape2D.xml new file mode 100644 index 0000000000..fb90606577 --- /dev/null +++ b/doc/classes/SeparationRayShape2D.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="SeparationRayShape2D" inherits="Shape2D" version="4.0"> + <brief_description> + Separation ray shape for 2D collisions. + </brief_description> + <description> + Separation ray shape for 2D collisions. A ray is not really a collision body; instead, it tries to separate itself from whatever is touching its far endpoint. It's often useful for characters. + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <members> + <member name="length" type="float" setter="set_length" getter="get_length" default="20.0"> + The ray's length. + </member> + <member name="slide_on_slope" type="bool" setter="set_slide_on_slope" getter="get_slide_on_slope" default="false"> + If [code]false[/code] (default), the shape always separates and returns a normal along its own direction. + If [code]true[/code], the shape can return the correct normal and separate in any direction, allowing sliding motion on slopes. + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/SeparationRayShape3D.xml b/doc/classes/SeparationRayShape3D.xml new file mode 100644 index 0000000000..ea57e4eb59 --- /dev/null +++ b/doc/classes/SeparationRayShape3D.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="SeparationRayShape3D" inherits="Shape3D" version="4.0"> + <brief_description> + Separation ray shape for 3D collisions. + </brief_description> + <description> + Separation ray shape for 3D collisions, which can be set into a [PhysicsBody3D] or [Area3D]. A ray is not really a collision body; instead, it tries to separate itself from whatever is touching its far endpoint. It's often useful for characters. + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <members> + <member name="length" type="float" setter="set_length" getter="get_length" default="1.0"> + The ray's length. + </member> + <member name="slide_on_slope" type="bool" setter="set_slide_on_slope" getter="get_slide_on_slope" default="false"> + If [code]false[/code] (default), the shape always separates and returns a normal along its own direction. + If [code]true[/code], the shape can return the correct normal and separate in any direction, allowing sliding motion on slopes. + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/Skeleton2D.xml b/doc/classes/Skeleton2D.xml index 828d24338b..839193fb61 100644 --- a/doc/classes/Skeleton2D.xml +++ b/doc/classes/Skeleton2D.xml @@ -5,6 +5,7 @@ </brief_description> <description> Skeleton2D parents a hierarchy of [Bone2D] objects. It is a requirement of [Bone2D]. Skeleton2D holds a reference to the rest pose of its children and acts as a single point of access to its bones. + To setup different types of inverse kinematics for the given Skeleton2D, a [SkeletonModificationStack2D] should be created. They can be applied by creating the desired number of modifications, which can be done by increasing [member SkeletonModificationStack2D.modification_count]. </description> <tutorials> <link title="2D skeletons">https://docs.godotengine.org/en/latest/tutorials/animation/2d_skeletons.html</link> diff --git a/doc/classes/SkeletonModification3DJiggle.xml b/doc/classes/SkeletonModification3DJiggle.xml index e48e382cd4..6cc1c0b266 100644 --- a/doc/classes/SkeletonModification3DJiggle.xml +++ b/doc/classes/SkeletonModification3DJiggle.xml @@ -175,19 +175,19 @@ </methods> <members> <member name="damping" type="float" setter="set_damping" getter="get_damping" default="0.75"> - The default amount of dampening applied to the Jiggle joints, if they are not overriden. Higher values lead to more of the calculated velocity being applied. + The default amount of dampening applied to the Jiggle joints, if they are not overridden. Higher values lead to more of the calculated velocity being applied. </member> <member name="gravity" type="Vector3" setter="set_gravity" getter="get_gravity" default="Vector3(0, -6, 0)"> - The default amount of gravity applied to the Jiggle joints, if they are not overriden. + The default amount of gravity applied to the Jiggle joints, if they are not overridden. </member> <member name="jiggle_data_chain_length" type="int" setter="set_jiggle_data_chain_length" getter="get_jiggle_data_chain_length" default="0"> The amount of Jiggle joints in the Jiggle modification. </member> <member name="mass" type="float" setter="set_mass" getter="get_mass" default="0.75"> - The default amount of mass assigned to the Jiggle joints, if they are not overriden. Higher values lead to faster movements and more overshooting. + The default amount of mass assigned to the Jiggle joints, if they are not overridden. Higher values lead to faster movements and more overshooting. </member> <member name="stiffness" type="float" setter="set_stiffness" getter="get_stiffness" default="3.0"> - The default amount of stiffness assigned to the Jiggle joints, if they are not overriden. Higher values act more like springs, quickly moving into the correct position. + The default amount of stiffness assigned to the Jiggle joints, if they are not overridden. Higher values act more like springs, quickly moving into the correct position. </member> <member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")"> The NodePath to the node that is the target for the Jiggle modification. This node is what the Jiggle chain will attempt to rotate the bone chain to. diff --git a/doc/classes/SoftBody3D.xml b/doc/classes/SoftBody3D.xml index ddfc14ceac..d5f0e3c95c 100644 --- a/doc/classes/SoftBody3D.xml +++ b/doc/classes/SoftBody3D.xml @@ -42,6 +42,20 @@ <description> </description> </method> + <method name="get_point_transform"> + <return type="Vector3" /> + <argument index="0" name="point_index" type="int" /> + <description> + Returns local translation of a vertex in the surface array. + </description> + </method> + <method name="is_point_pinned" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="point_index" type="int" /> + <description> + Returns [code]true[/code] if vertex is set to pinned. + </description> + </method> <method name="remove_collision_exception_with"> <return type="void" /> <argument index="0" name="body" type="Node" /> @@ -65,6 +79,15 @@ Based on [code]value[/code], enables or disables the specified layer in the [member collision_mask], given a [code]layer_number[/code] between 1 and 32. </description> </method> + <method name="set_point_pinned"> + <return type="void" /> + <argument index="0" name="point_index" type="int" /> + <argument index="1" name="pinned" type="bool" /> + <argument index="2" name="attachment_path" type="NodePath" default="NodePath("")" /> + <description> + Sets the pinned state of a surface vertex. When set to [code]true[/code], the optional [code]attachment_path[/code] can define a [Node3D] the pinned vertex will be attached to. + </description> + </method> </methods> <members> <member name="collision_layer" type="int" setter="set_collision_layer" getter="get_collision_layer" default="1"> diff --git a/doc/classes/StaticBody2D.xml b/doc/classes/StaticBody2D.xml index 326bf58e22..0344c3e0d1 100644 --- a/doc/classes/StaticBody2D.xml +++ b/doc/classes/StaticBody2D.xml @@ -1,14 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="StaticBody2D" inherits="PhysicsBody2D" version="4.0"> <brief_description> - Static body for 2D physics. + Physics body for 2D physics which is static or moves only by script. Useful for floor and walls. </brief_description> <description> - Static body for 2D physics. A static body is a simple body that can't be moved by external forces or contacts. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody2D], they don't consume any CPU resources as long as they don't move. - They however have extra functionalities to move and affect other bodies: - [b]Constant velocity:[/b] [member constant_linear_velocity] and [member constant_angular_velocity] can be set for the static body, so even if it doesn't move, it affects other bodies as if it was moving (this is useful for simulating conveyor belts or conveyor wheels). - [b]Transform change:[/b] Static bodies can be also moved by code. Unless [member kinematic_motion] is enabled, they are just teleported in this case and don't affect other bodies on their path. - [b]Kinematic motion:[/b] Static bodies can have [member kinematic_motion] enabled to make them kinematic bodies that can be moved by code and push other bodies on their path. + Static body for 2D physics. + A static body is a simple body that can't be moved by external forces or contacts. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody2D], it doesn't consume any CPU resources as long as they don't move. + They have extra functionalities to move and affect other bodies: + [b]Static transform change:[/b] Static bodies can be moved by animation or script. In this case, they are just teleported and don't affect other bodies on their path. + [b]Constant velocity:[/b] When [member constant_linear_velocity] or [member constant_angular_velocity] is set, static bodies don't move themselves but affect touching bodies as if they were moving. This is useful for simulating conveyor belts or conveyor wheels. </description> <tutorials> </tutorials> @@ -16,22 +16,15 @@ </methods> <members> <member name="constant_angular_velocity" type="float" setter="set_constant_angular_velocity" getter="get_constant_angular_velocity" default="0.0"> - The body's constant angular velocity. This does not rotate the body (unless [member kinematic_motion] is enabled), but affects other bodies that touch it, as if it were rotating. + The body's constant angular velocity. This does not rotate the body, but affects touching bodies, as if it were rotating. </member> <member name="constant_linear_velocity" type="Vector2" setter="set_constant_linear_velocity" getter="get_constant_linear_velocity" default="Vector2(0, 0)"> - The body's constant linear velocity. This does not move the body (unless [member kinematic_motion] is enabled), but affects other bodies that touch it, as if it were moving. - </member> - <member name="kinematic_motion" type="bool" setter="set_kinematic_motion_enabled" getter="is_kinematic_motion_enabled" default="false"> - If [code]true[/code], the body will act the same as a [RigidBody2D] in [constant RigidBody2D.MODE_KINEMATIC] mode. - When the body is moved manually, either from code or from an [AnimationPlayer] (with [member AnimationPlayer.playback_process_mode] set to [code]physics[/code]), the physics will automatically compute an estimate of their linear and angular velocity. This makes them very useful for moving platforms or other AnimationPlayer-controlled objects (like a door, a bridge that opens, etc). + The body's constant linear velocity. This does not move the body, but affects touching bodies, as if it were moving. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> The physics material override for the body. If a material is assigned to this property, it will be used instead of any other physics material, such as an inherited one. </member> - <member name="sync_to_physics" type="bool" setter="set_sync_to_physics" getter="is_sync_to_physics_enabled" default="false"> - If [code]true[/code] and [member kinematic_motion] is enabled, the body's movement will be synchronized to the physics frame. This is useful when animating movement via [AnimationPlayer], for example on moving platforms. Do [b]not[/b] use together with [method PhysicsBody2D.move_and_collide]. - </member> </members> <constants> </constants> diff --git a/doc/classes/StaticBody3D.xml b/doc/classes/StaticBody3D.xml index 69c123002f..4cb51b60ec 100644 --- a/doc/classes/StaticBody3D.xml +++ b/doc/classes/StaticBody3D.xml @@ -1,14 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="StaticBody3D" inherits="PhysicsBody3D" version="4.0"> <brief_description> - Static body for 3D physics. + Physics body for 3D physics which is static or moves only by script. Useful for floor and walls. </brief_description> <description> - Static body for 3D physics. A static body is a simple body that can't be moved by external forces or contacts. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody3D], they don't consume any CPU resources as long as they don't move. - They however have extra functionalities to move and affect other bodies: - [b]Constant velocity:[/b] [member constant_linear_velocity] and [member constant_angular_velocity] can be set for the static body, so even if it doesn't move, it affects other bodies as if it was moving (this is useful for simulating conveyor belts or conveyor wheels). - [b]Transform change:[/b] Static bodies can be also moved by code. Unless [member kinematic_motion] is enabled, they are just teleported in this case and don't affect other bodies on their path. - [b]Kinematic motion:[/b] Static bodies can have [member kinematic_motion] enabled to make them kinematic bodies that can be moved by code and push other bodies on their path. + Static body for 3D physics. + A static body is a simple body that can't be moved by external forces or contacts. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody3D], it doesn't consume any CPU resources as long as they don't move. + They have extra functionalities to move and affect other bodies: + [b]Static transform change:[/b] Static bodies can be moved by animation or script. In this case, they are just teleported and don't affect other bodies on their path. + [b]Constant velocity:[/b] When [member constant_linear_velocity] or [member constant_angular_velocity] is set, static bodies don't move themselves but affect touching bodies as if they were moving. This is useful for simulating conveyor belts or conveyor wheels. </description> <tutorials> <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> @@ -19,22 +19,15 @@ </methods> <members> <member name="constant_angular_velocity" type="Vector3" setter="set_constant_angular_velocity" getter="get_constant_angular_velocity" default="Vector3(0, 0, 0)"> - The body's constant angular velocity. This does not rotate the body (unless [member kinematic_motion] is enabled), but affects other bodies that touch it, as if it were rotating. + The body's constant angular velocity. This does not rotate the body, but affects touching bodies, as if it were rotating. </member> <member name="constant_linear_velocity" type="Vector3" setter="set_constant_linear_velocity" getter="get_constant_linear_velocity" default="Vector3(0, 0, 0)"> - The body's constant linear velocity. This does not move the body (unless [member kinematic_motion] is enabled), but affects other bodies that touch it, as if it were moving. - </member> - <member name="kinematic_motion" type="bool" setter="set_kinematic_motion_enabled" getter="is_kinematic_motion_enabled" default="false"> - If [code]true[/code], the body will act the same as a [RigidBody3D] in [constant RigidBody3D.MODE_KINEMATIC] mode. - When the body is moved manually, either from code or from an [AnimationPlayer] (with [member AnimationPlayer.playback_process_mode] set to [code]physics[/code]), the physics will automatically compute an estimate of their linear and angular velocity. This makes them very useful for moving platforms or other AnimationPlayer-controlled objects (like a door, a bridge that opens, etc). + The body's constant linear velocity. This does not move the body, but affects touching bodies, as if it were moving. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> The physics material override for the body. If a material is assigned to this property, it will be used instead of any other physics material, such as an inherited one. </member> - <member name="sync_to_physics" type="bool" setter="set_sync_to_physics" getter="is_sync_to_physics_enabled" default="false"> - If [code]true[/code] and [member kinematic_motion] is enabled, the body's movement will be synchronized to the physics frame. This is useful when animating movement via [AnimationPlayer], for example on moving platforms. Do [b]not[/b] use together with [method PhysicsBody3D.move_and_collide]. - </member> </members> <constants> </constants> diff --git a/doc/classes/String.xml b/doc/classes/String.xml index 97efc24bd1..eb6c52d662 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -239,7 +239,7 @@ <method name="is_absolute_path" qualifiers="const"> <return type="bool" /> <description> - If the string is a path to a file or directory, returns [code]true[/code] if the path is absolute. + Returns [code]true[/code] if the string is a path to a file or directory and its starting point is explicitly defined. This includes [code]res://[/code], [code]user://[/code], [code]C:\[/code], [code]/[/code], etc. </description> </method> <method name="is_empty" qualifiers="const"> @@ -248,10 +248,10 @@ Returns [code]true[/code] if the length of the string equals [code]0[/code]. </description> </method> - <method name="is_rel_path" qualifiers="const"> + <method name="is_relative_path" qualifiers="const"> <return type="bool" /> <description> - If the string is a path to a file or directory, returns [code]true[/code] if the path is relative. + Returns [code]true[/code] if the string is a path to a file or directory and its starting point is implicitly defined within the context it is being used. The starting point may refer to the current directory ([code]./[/code]), or the current [Node]. </description> </method> <method name="is_subsequence_of" qualifiers="const"> @@ -646,6 +646,12 @@ Returns the similarity index of the text compared to this string. 1 means totally similar and 0 means totally dissimilar. </description> </method> + <method name="simplify_path" qualifiers="const"> + <return type="String" /> + <description> + Returns a simplified canonical path. + </description> + </method> <method name="split" qualifiers="const"> <return type="PackedStringArray" /> <argument index="0" name="delimiter" type="String" /> diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index fbda005865..77bd7b1a0a 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -57,6 +57,13 @@ Returns the [Texture2D] for the tab at index [code]tab_idx[/code] or [code]null[/code] if the tab has no [Texture2D]. </description> </method> + <method name="get_tab_idx_at_point" qualifiers="const"> + <return type="int" /> + <argument index="0" name="point" type="Vector2" /> + <description> + Returns the index of the tab at local coordinates [code]point[/code]. Returns [code]-1[/code] if the point is outside the control boundaries or if there's no tab at the queried position. + </description> + </method> <method name="get_tab_title" qualifiers="const"> <return type="String" /> <argument index="0" name="tab_idx" type="int" /> diff --git a/doc/classes/TextParagraph.xml b/doc/classes/TextParagraph.xml index fb94e14c8d..aa35acdbd2 100644 --- a/doc/classes/TextParagraph.xml +++ b/doc/classes/TextParagraph.xml @@ -220,13 +220,13 @@ <method name="get_spacing_bottom" qualifiers="const"> <return type="int" /> <description> - Returns extra spacing at the bottom of the line. See [member Font.extra_spacing_bottom]. + Returns extra spacing at the bottom of the line. See [member Font.spacing_bottom]. </description> </method> <method name="get_spacing_top" qualifiers="const"> <return type="int" /> <description> - Returns extra spacing at the top of the line. See [member Font.extra_spacing_top]. + Returns extra spacing at the top of the line. See [member Font.spacing_top]. </description> </method> <method name="hit_test" qualifiers="const"> diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index ac56be4392..d7af2204cf 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -9,42 +9,10 @@ <tutorials> </tutorials> <methods> - <method name="create_font_bitmap"> + <method name="create_font"> <return type="RID" /> - <argument index="0" name="height" type="float" /> - <argument index="1" name="ascent" type="float" /> - <argument index="2" name="base_size" type="int" /> <description> - Creates new, empty bitmap font. To free the resulting font, use [method free_rid] method. - </description> - </method> - <method name="create_font_memory"> - <return type="RID" /> - <argument index="0" name="data" type="PackedByteArray" /> - <argument index="1" name="type" type="String" /> - <argument index="2" name="base_size" type="int" default="16" /> - <description> - Creates new font from the data in memory. To free the resulting font, use [method free_rid] method. - Note: For non-scalable fonts [code]base_size[/code] is ignored, use [method font_get_base_size] to check actual font size. - </description> - </method> - <method name="create_font_resource"> - <return type="RID" /> - <argument index="0" name="filename" type="String" /> - <argument index="1" name="base_size" type="int" default="16" /> - <description> - Creates new font from the file. To free the resulting font, use [method free_rid] method. - Note: For non-scalable fonts [code]base_size[/code] is ignored, use [method font_get_base_size] to check actual font size. - </description> - </method> - <method name="create_font_system"> - <return type="RID" /> - <argument index="0" name="name" type="String" /> - <argument index="1" name="base_size" type="int" default="16" /> - <description> - Creates new font from the system font. To free the resulting font, use [method free_rid] method. - Note: This method is supported by servers with the [code]FEATURE_FONT_SYSTEM[/code] feature. - Note: For non-scalable fonts [code]base_size[/code] is ignored, use [method font_get_base_size] to check actual font size. + Creates new, empty font cache entry resource. To free the resulting resourec, use [method free_rid] method. </description> </method> <method name="create_shaped_text"> @@ -68,52 +36,53 @@ Draws box displaying character hexadecimal code. Used for replacing missing characters. </description> </method> - <method name="font_bitmap_add_char"> + <method name="font_clear_glyphs"> <return type="void" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="char" type="int" /> - <argument index="2" name="texture_idx" type="int" /> - <argument index="3" name="rect" type="Rect2" /> - <argument index="4" name="align" type="Vector2" /> - <argument index="5" name="advance" type="float" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> <description> - Adds a character to the font, where [code]character[/code] is the Unicode value, [code]texture[/code] is the texture index, [code]rect[/code] is the region in the texture (in pixels!), [code]align[/code] is the (optional) alignment for the character and [code]advance[/code] is the (optional) advance. + Removes all rendered glyphs information from the cache entry. Note: This function will not remove textures associated with the glyphs, use [method font_remove_texture] to remove them manually. </description> </method> - <method name="font_bitmap_add_kerning_pair"> + <method name="font_clear_kerning_map"> <return type="void" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="A" type="int" /> - <argument index="2" name="B" type="int" /> - <argument index="3" name="kerning" type="int" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> <description> - Adds a kerning pair to the bitmap font as a difference. Kerning pairs are special cases where a typeface advance is determined by the next character. + Removes all kerning overrides. </description> </method> - <method name="font_bitmap_add_texture"> + <method name="font_clear_size_cache"> <return type="void" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="texture" type="Texture" /> + <argument index="0" name="font_rid" type="RID" /> + <description> + Removes all font sizes from the cache entry + </description> + </method> + <method name="font_clear_textures"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> <description> - Adds a texture to the bitmap font. + Removes all textures from font cache entry. Note: This function will not remove glyphs associated with the texture, use [method font_remove_glyph] to remove them manually. </description> </method> <method name="font_draw_glyph" qualifiers="const"> - <return type="Vector2" /> - <argument index="0" name="font" type="RID" /> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="canvas" type="RID" /> <argument index="2" name="size" type="int" /> <argument index="3" name="pos" type="Vector2" /> <argument index="4" name="index" type="int" /> <argument index="5" name="color" type="Color" default="Color(1, 1, 1, 1)" /> <description> - Draws single glyph into a canvas item at the position, using [code]font[/code] at the size [code]size[/code]. + Draws single glyph into a canvas item at the position, using [code]font_rid[/code] at the size [code]size[/code]. Note: Glyph index is specific to the font, use glyphs indices returned by [method shaped_text_get_glyphs] or [method font_get_glyph_index]. </description> </method> <method name="font_draw_glyph_outline" qualifiers="const"> - <return type="Vector2" /> - <argument index="0" name="font" type="RID" /> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="canvas" type="RID" /> <argument index="2" name="size" type="int" /> <argument index="3" name="outline_size" type="int" /> @@ -121,68 +90,46 @@ <argument index="5" name="index" type="int" /> <argument index="6" name="color" type="Color" default="Color(1, 1, 1, 1)" /> <description> - Draws single glyph outline of size [code]outline_size[/code] into a canvas item at the position, using [code]font[/code] at the size [code]size[/code]. + Draws single glyph outline of size [code]outline_size[/code] into a canvas item at the position, using [code]font_rid[/code] at the size [code]size[/code]. Note: Glyph index is specific to the font, use glyphs indices returned by [method shaped_text_get_glyphs] or [method font_get_glyph_index]. </description> </method> - <method name="font_get_antialiased" qualifiers="const"> - <return type="bool" /> - <argument index="0" name="font" type="RID" /> - <description> - Returns [code]true[/code], if font anti-aliasing is supported and enabled. - </description> - </method> <method name="font_get_ascent" qualifiers="const"> <return type="float" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="size" type="int" /> <description> Returns the font ascent (number of pixels above the baseline). </description> </method> - <method name="font_get_base_size" qualifiers="const"> - <return type="float" /> - <argument index="0" name="font" type="RID" /> - <description> - Returns the default size of the font. - </description> - </method> <method name="font_get_descent" qualifiers="const"> <return type="float" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="size" type="int" /> <description> Returns the font descent (number of pixels below the baseline). </description> </method> - <method name="font_get_distance_field_hint" qualifiers="const"> - <return type="bool" /> - <argument index="0" name="font" type="RID" /> - <description> - Returns [code]true[/code], if distance field hint is enabled. - </description> - </method> - <method name="font_get_feature_list" qualifiers="const"> - <return type="Dictionary" /> - <argument index="0" name="font" type="RID" /> + <method name="font_get_fixed_size" qualifiers="const"> + <return type="int" /> + <argument index="0" name="font_rid" type="RID" /> <description> - Returns list of OpenType features supported by font. + Returns bitmap font fixed size. </description> </method> - <method name="font_get_force_autohinter" qualifiers="const"> - <return type="bool" /> - <argument index="0" name="font" type="RID" /> + <method name="font_get_global_oversampling" qualifiers="const"> + <return type="float" /> <description> - Returns [code]true[/code], if autohinter is supported and enabled. + Returns the font oversampling factor, shared by all fonts in the TextServer. </description> </method> <method name="font_get_glyph_advance" qualifiers="const"> <return type="Vector2" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="index" type="int" /> - <argument index="2" name="size" type="int" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="glyph" type="int" /> <description> - Returns advance of the glyph. + Returns glyph advance (offset of the next glyph). Note: advance for glyphs outlines is the same as the base glyph advance and is not saved. </description> </method> <method name="font_get_glyph_contours" qualifiers="const"> @@ -191,7 +138,7 @@ <argument index="1" name="size" type="int" /> <argument index="2" name="index" type="int" /> <description> - Returns outline contours of the glyph in a Dictionary. + Returns outline contours of the glyph as a [code]Dictionary[/code] with the following contents: [code]points[/code] - [PackedVector3Array], containing outline points. [code]x[/code] and [code]y[/code] are point coordinates. [code]z[/code] is the type of the point, using the [enum ContourPointTag] values. [code]contours[/code] - [PackedInt32Array], containing indices the end points of each contour. [code]orientation[/code] - [bool], contour orientation. If [code]true[/code], clockwise contours must be filled. @@ -199,41 +146,85 @@ </method> <method name="font_get_glyph_index" qualifiers="const"> <return type="int" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="char" type="int" /> - <argument index="2" name="variation_selector" type="int" default="0" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="char" type="int" /> + <argument index="3" name="variation_selector" type="int" /> <description> Returns the glyph index of a [code]char[/code], optionally modified by the [code]variation_selector[/code]. </description> </method> - <method name="font_get_glyph_kerning" qualifiers="const"> + <method name="font_get_glyph_list" qualifiers="const"> + <return type="Array" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <description> + Returns list of rendered glyphs in the cache entry. + </description> + </method> + <method name="font_get_glyph_offset" qualifiers="const"> <return type="Vector2" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="index_a" type="int" /> - <argument index="2" name="index_b" type="int" /> - <argument index="3" name="size" type="int" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> <description> - Returns a kerning of the pair of glyphs. + Returns glyph offset from the baseline. </description> </method> - <method name="font_get_height" qualifiers="const"> - <return type="float" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="size" type="int" /> + <method name="font_get_glyph_size" qualifiers="const"> + <return type="Vector2" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> <description> - Returns the total font height (ascent plus descent) in pixels. + Returns size of the glyph. + </description> + </method> + <method name="font_get_glyph_texture_idx" qualifiers="const"> + <return type="int" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <description> + Returns index of the cache texture containing the glyph. + </description> + </method> + <method name="font_get_glyph_uv_rect" qualifiers="const"> + <return type="Rect2" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <description> + Returns rectangle in the cache texture containing the glyph. </description> </method> <method name="font_get_hinting" qualifiers="const"> <return type="int" enum="TextServer.Hinting" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <description> - Returns the font hinting. + Returns the font hinting mode. Used by dynamic fonts only. + </description> + </method> + <method name="font_get_kerning" qualifiers="const"> + <return type="Vector2" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="glyph_pair" type="Vector2i" /> + <description> + Returns kerning for the pair of glyphs. + </description> + </method> + <method name="font_get_kerning_list" qualifiers="const"> + <return type="Array" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <description> + Returns list of the kerning overrides. </description> </method> <method name="font_get_language_support_override"> <return type="bool" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="language" type="String" /> <description> Returns [code]true[/code] if support override is enabled for the [code]language[/code]. @@ -241,20 +232,43 @@ </method> <method name="font_get_language_support_overrides"> <return type="PackedStringArray" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <description> Returns list of language support overrides. </description> </method> + <method name="font_get_msdf_pixel_range" qualifiers="const"> + <return type="int" /> + <argument index="0" name="font_rid" type="RID" /> + <description> + Return the width of the range around the shape between the minimum and maximum representable signed distance. + </description> + </method> + <method name="font_get_msdf_size" qualifiers="const"> + <return type="int" /> + <argument index="0" name="font_rid" type="RID" /> + <description> + Returns source font size used to generate MSDF textures. + </description> + </method> <method name="font_get_oversampling" qualifiers="const"> <return type="float" /> + <argument index="0" name="font_rid" type="RID" /> <description> - Returns the font oversampling factor, shared by all fonts in the TextServer. + Returns font oversampling factor, if set to [code]0.0[/code] global oversampling factor is used instead. Used by dynamic fonts only. + </description> + </method> + <method name="font_get_scale" qualifiers="const"> + <return type="float" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <description> + Returns scaling factor of the color bitmap font. </description> </method> <method name="font_get_script_support_override"> <return type="bool" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="script" type="String" /> <description> Returns [code]true[/code] if support override is enabled for the [code]script[/code]. @@ -262,98 +276,149 @@ </method> <method name="font_get_script_support_overrides"> <return type="PackedStringArray" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <description> Returns list of script support overrides. </description> </method> - <method name="font_get_spacing_glyph" qualifiers="const"> - <return type="int" /> - <argument index="0" name="font" type="RID" /> + <method name="font_get_size_cache_list" qualifiers="const"> + <return type="Array" /> + <argument index="0" name="font_rid" type="RID" /> <description> - Returns extra spacing for each glyph in pixels. + Return list of the font sizes in the cache. Each size is [code]Vector2i[/code] with font size and outline size. </description> </method> - <method name="font_get_spacing_space" qualifiers="const"> + <method name="font_get_spacing" qualifiers="const"> <return type="int" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="spacing" type="int" enum="TextServer.SpacingType" /> <description> - Sets extra spacing for each glyph in pixels. + Returns extra spacing added between glyphs in pixels. </description> </method> <method name="font_get_supported_chars" qualifiers="const"> <return type="String" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <description> Returns a string containing all the characters available in the font. </description> </method> - <method name="font_get_underline_position" qualifiers="const"> - <return type="float" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="size" type="int" /> + <method name="font_get_texture_count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> <description> - Returns underline offset (number of pixels below the baseline). + Returns number of textures used by font cache entry. </description> </method> - <method name="font_get_underline_thickness" qualifiers="const"> + <method name="font_get_texture_image" qualifiers="const"> + <return type="Image" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="texture_index" type="int" /> + <description> + Returns font cache texture image data. + </description> + </method> + <method name="font_get_texture_offsets" qualifiers="const"> + <return type="PackedInt32Array" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="texture_index" type="int" /> + <description> + Returns array containing the first free pixel in the each column of texture. Should be the same size as texture width or empty. + </description> + </method> + <method name="font_get_underline_position" qualifiers="const"> <return type="float" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="size" type="int" /> <description> - Returns underline thickness in pixels. + Returns pixel offset of the underline below the baseline. </description> </method> - <method name="font_get_variation" qualifiers="const"> + <method name="font_get_underline_thickness" qualifiers="const"> <return type="float" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="tag" type="String" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> <description> - Returns variation coordinate [code]tag[/code]. + Returns thickness of the underline in pixels. </description> </method> - <method name="font_get_variation_list" qualifiers="const"> + <method name="font_get_variation_coordinates" qualifiers="const"> <return type="Dictionary" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <description> - Returns list of supported [url=https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg]variation coordinates[/url], each coordinate is returned as [code]tag: Vector3i(min_value,max_value,default_value)[/code]. - Font variations allow for continuous change of glyph characteristics along some given design axis, such as weight, width or slant. + Returns variation coordinates for the specified font cache entry. See [method font_supported_variation_list] for more info. </description> </method> <method name="font_has_char" qualifiers="const"> <return type="bool" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="char" type="int" /> <description> - Returns [code]true[/code] if [code]char[/code] is available in the font. + Return [code]true[/code] if a Unicode [code]char[/code] is available in the font. </description> </method> - <method name="font_has_outline" qualifiers="const"> + <method name="font_is_antialiased" qualifiers="const"> <return type="bool" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> + <description> + Returns [code]true[/code] if font 8-bit anitialiased glyph rendering is supported and enabled. + </description> + </method> + <method name="font_is_force_autohinter" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="font_rid" type="RID" /> <description> - Returns [code]true[/code], if font supports glyph outlines. + Returns [code]true[/code] if auto-hinting is supported and preffered over font built-in hinting. Used by dynamic fonts only. </description> </method> <method name="font_is_language_supported" qualifiers="const"> <return type="bool" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="language" type="String" /> <description> - Returns [code]true[/code], if font supports given language (ISO 639 code). + Returns [code]true[/code], if font supports given language ([url=https://en.wikipedia.org/wiki/ISO_639-1]ISO 639[/url] code). + </description> + </method> + <method name="font_is_multichannel_signed_distance_field" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="font_rid" type="RID" /> + <description> + Returns [code]true[/code] if glyphs of all sizes are rendered using single multichannel signed distance field generated from the dynamic font vector data. </description> </method> <method name="font_is_script_supported" qualifiers="const"> <return type="bool" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="script" type="String" /> <description> Returns [code]true[/code], if font supports given script (ISO 15924 code). </description> </method> + <method name="font_remove_glyph"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <description> + Removes specified rendered glyph information from the cache entry. Note: This function will not remove textures associated with the glyphs, use [method font_remove_texture] to remove them manually. + </description> + </method> + <method name="font_remove_kerning"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="glyph_pair" type="Vector2i" /> + <description> + Removes kerning override for the pair of glyphs. + </description> + </method> <method name="font_remove_language_support_override"> <return type="void" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="language" type="String" /> <description> Remove language support override. @@ -361,92 +426,299 @@ </method> <method name="font_remove_script_support_override"> <return type="void" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="script" type="String" /> <description> Removes script support override. </description> </method> + <method name="font_remove_size_cache"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <description> + Removes specified font size from the cache entry. + </description> + </method> + <method name="font_remove_texture"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="texture_index" type="int" /> + <description> + Removes specified texture from font cache entry. Note: This function will not remove glyphs associated with the texture, remove them manually, using [method font_remove_glyph]. + </description> + </method> + <method name="font_render_glyph"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="index" type="int" /> + <description> + Renders specified glyph the the font cache texture. + </description> + </method> + <method name="font_render_range"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="start" type="int" /> + <argument index="3" name="end" type="int" /> + <description> + Renders the range of characters to the font cache texture. + </description> + </method> <method name="font_set_antialiased"> <return type="void" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="antialiased" type="bool" /> <description> - Sets font anti-aliasing. + If set to [code]true[/code], 8-bit antialiased glyph rendering is used, otherwise 1-bit rendering is used. Used by dynamic fonts only. </description> </method> - <method name="font_set_distance_field_hint"> + <method name="font_set_ascent"> <return type="void" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="distance_field" type="bool" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="ascent" type="float" /> + <description> + Sets the font ascent (number of pixels above the baseline). + </description> + </method> + <method name="font_set_data"> + <return type="void" /> + <argument index="0" name="data" type="RID" /> + <argument index="1" name="arg1" type="PackedByteArray" /> <description> - Sets font distance field hint. + Sets font source data, e.g contents of the dynamic font source file. + </description> + </method> + <method name="font_set_descent"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="descent" type="float" /> + <description> + Sets the font descent (number of pixels below the baseline). + </description> + </method> + <method name="font_set_fixed_size"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="fixed_size" type="int" /> + <description> + Sets bitmap font fixed size. If set to value greater than zero, same cache entry will be used for all font sizes. </description> </method> <method name="font_set_force_autohinter"> <return type="void" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="enabeld" type="bool" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="force_autohinter" type="bool" /> + <description> + If set to [code]true[/code] auto-hinting is preffered over font built-in hinting. + </description> + </method> + <method name="font_set_global_oversampling"> + <return type="void" /> + <argument index="0" name="oversampling" type="float" /> + <description> + Sets oversampling factor, shared by all font in the TextServer. + Note: This value can be automaticaly changed by display server. + </description> + </method> + <method name="font_set_glyph_advance"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="glyph" type="int" /> + <argument index="3" name="advance" type="Vector2" /> + <description> + Sets glyph advance (offset of the next glyph). Note: advance for glyphs outlines is the same as the base glyph advance and is not saved. + </description> + </method> + <method name="font_set_glyph_offset"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <argument index="3" name="offset" type="Vector2" /> + <description> + Sets glyph offset from the baseline. + </description> + </method> + <method name="font_set_glyph_size"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <argument index="3" name="gl_size" type="Vector2" /> <description> - Enables/disables default autohinter. + Sets size of the glyph. + </description> + </method> + <method name="font_set_glyph_texture_idx"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <argument index="3" name="texture_idx" type="int" /> + <description> + Sets index of the cache texture containing the glyph. + </description> + </method> + <method name="font_set_glyph_uv_rect"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="glyph" type="int" /> + <argument index="3" name="uv_rect" type="Rect2" /> + <description> + Sets rectangle in the cache texture containing the glyph. </description> </method> <method name="font_set_hinting"> <return type="void" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="hinting" type="int" enum="TextServer.Hinting" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="_hinting" type="int" enum="TextServer.Hinting" /> <description> - Sets font hinting. + Sets font hinting mode. Used by dynamic fonts only. + </description> + </method> + <method name="font_set_kerning"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="glyph_pair" type="Vector2i" /> + <argument index="3" name="kerning" type="Vector2" /> + <description> + Sets kerning for the pair of glyphs. </description> </method> <method name="font_set_language_support_override"> <return type="void" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="language" type="String" /> <argument index="2" name="supported" type="bool" /> <description> Adds override for [method font_is_language_supported]. </description> </method> + <method name="font_set_msdf_pixel_range"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="msdf_pixel_range" type="int" /> + <description> + Sets the width of the range around the shape between the minimum and maximum representable signed distance. + </description> + </method> + <method name="font_set_msdf_size"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="msdf_size" type="int" /> + <description> + Sets source font size used to generate MSDF textures. + </description> + </method> + <method name="font_set_multichannel_signed_distance_field"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="msdf" type="bool" /> + <description> + If set to [code]true[/code], glyphs of all sizes are rendered using single multichannel signed distance field generated from the dynamic font vector data. + </description> + </method> <method name="font_set_oversampling"> <return type="void" /> - <argument index="0" name="oversampling" type="float" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="oversampling" type="float" /> <description> - Sets oversampling factor, shared by all font in the TextServer. + Sets font oversampling factor, if set to [code]0.0[/code] global oversampling factor is used instead. Used by dynamic fonts only. + </description> + </method> + <method name="font_set_scale"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="scale" type="float" /> + <description> + Sets scaling factor of the color bitmap font. </description> </method> <method name="font_set_script_support_override"> <return type="void" /> - <argument index="0" name="font" type="RID" /> + <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="script" type="String" /> <argument index="2" name="supported" type="bool" /> <description> Adds override for [method font_is_script_supported]. </description> </method> - <method name="font_set_spacing_glyph"> + <method name="font_set_spacing"> <return type="void" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="value" type="int" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="spacing" type="int" enum="TextServer.SpacingType" /> + <argument index="3" name="value" type="int" /> <description> - Returns extra spacing for the space character in pixels. + Sets extra spacing added between glyphs in pixels. </description> </method> - <method name="font_set_spacing_space"> + <method name="font_set_texture_image"> <return type="void" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="value" type="int" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="texture_index" type="int" /> + <argument index="3" name="image" type="Image" /> <description> - Sets extra spacing for the space character in pixels. + Sets font cache texture image data. </description> </method> - <method name="font_set_variation"> + <method name="font_set_texture_offsets"> <return type="void" /> - <argument index="0" name="font" type="RID" /> - <argument index="1" name="tag" type="String" /> - <argument index="2" name="value" type="float" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="Vector2i" /> + <argument index="2" name="texture_index" type="int" /> + <argument index="3" name="offset" type="PackedInt32Array" /> + <description> + Sets array containing the first free pixel in the each column of texture. Should be the same size as texture width or empty. + </description> + </method> + <method name="font_set_underline_position"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="underline_position" type="float" /> + <description> + Sets pixel offset of the underline below the baseline. + </description> + </method> + <method name="font_set_underline_thickness"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="size" type="int" /> + <argument index="2" name="underline_thickness" type="float" /> + <description> + Sets thickness of the underline in pixels. + </description> + </method> + <method name="font_set_variation_coordinates"> + <return type="void" /> + <argument index="0" name="font_rid" type="RID" /> + <argument index="1" name="variation_coordinates" type="Dictionary" /> + <description> + Sets variation coordinates for the specified font cache entry. See [method font_supported_variation_list] for more info. + </description> + </method> + <method name="font_supported_feature_list" qualifiers="const"> + <return type="Dictionary" /> + <argument index="0" name="font_rid" type="RID" /> + <description> + </description> + </method> + <method name="font_supported_variation_list" qualifiers="const"> + <return type="Dictionary" /> + <argument index="0" name="font_rid" type="RID" /> <description> - Sets variation coordinate [code]name[/code]. Unsupported coordinates will be silently ignored. </description> </method> <method name="format_number" qualifiers="const"> @@ -478,13 +750,6 @@ Returns the name of the server interface. </description> </method> - <method name="get_system_fonts" qualifiers="const"> - <return type="PackedStringArray" /> - <description> - Returns list of available system fonts. - Note: This method is supported by servers with the [code]FEATURE_FONT_SYSTEM[/code] feature. - </description> - </method> <method name="has"> <return type="bool" /> <argument index="0" name="rid" type="RID" /> @@ -514,7 +779,7 @@ Note: This function should be called before any other TextServer functions used, otherwise it won't have any effect. </description> </method> - <method name="name_to_tag"> + <method name="name_to_tag" qualifiers="const"> <return type="int" /> <argument index="0" name="name" type="String" /> <description> @@ -890,7 +1155,7 @@ Aligns shaped text to the given tab-stops. </description> </method> - <method name="tag_to_name"> + <method name="tag_to_name" qualifiers="const"> <return type="String" /> <argument index="0" name="tag" type="int" /> <description> @@ -1023,5 +1288,17 @@ <constant name="CONTOUR_CURVE_TAG_OFF_CUBIC" value="2" enum="ContourPointTag"> Contour point isn't on the curve, but serves as a control point for a cubic Bézier arc. </constant> + <constant name="SPACING_GLYPH" value="0" enum="SpacingType"> + Spacing for each glyph. + </constant> + <constant name="SPACING_SPACE" value="1" enum="SpacingType"> + Spacing for the space character. + </constant> + <constant name="SPACING_TOP" value="2" enum="SpacingType"> + Spacing at the top of the line. + </constant> + <constant name="SPACING_BOTTOM" value="3" enum="SpacingType"> + Spacing at the bottom of the line. + </constant> </constants> </class> diff --git a/doc/classes/TextureProgressBar.xml b/doc/classes/TextureProgressBar.xml index 9da89ebe43..ee47557b39 100644 --- a/doc/classes/TextureProgressBar.xml +++ b/doc/classes/TextureProgressBar.xml @@ -60,6 +60,9 @@ [Texture2D] that clips based on the node's [code]value[/code] and [member fill_mode]. As [code]value[/code] increased, the texture fills up. It shows entirely when [code]value[/code] reaches [code]max_value[/code]. It doesn't show at all if [code]value[/code] is equal to [code]min_value[/code]. The [code]value[/code] property comes from [Range]. See [member Range.value], [member Range.min_value], [member Range.max_value]. </member> + <member name="texture_progress_offset" type="Vector2" setter="set_texture_progress_offset" getter="get_texture_progress_offset" default="Vector2(0, 0)"> + The offset of [member texture_progress]. Useful for [member texture_over] and [member texture_under] with fancy borders, to avoid transparent margins in your progress texture. + </member> <member name="texture_under" type="Texture2D" setter="set_under_texture" getter="get_under_texture"> [Texture2D] that draws under the progress bar. The bar's background. </member> diff --git a/doc/classes/Thread.xml b/doc/classes/Thread.xml index b553aad518..9c9119c664 100644 --- a/doc/classes/Thread.xml +++ b/doc/classes/Thread.xml @@ -40,6 +40,8 @@ <return type="Variant" /> <description> Joins the [Thread] and waits for it to finish. Returns what the method called returned. + Should either be used when you want to retrieve the value returned from the method called by the [Thread] or before freeing the instance that contains the [Thread]. + [b]Note:[/b] After the [Thread] finishes joining it will be disposed. If you want to use it again you will have to create a new instance of it. </description> </method> </methods> diff --git a/doc/classes/TileData.xml b/doc/classes/TileData.xml index b5031cfc63..b18ca29a8c 100644 --- a/doc/classes/TileData.xml +++ b/doc/classes/TileData.xml @@ -11,6 +11,7 @@ <return type="void" /> <argument index="0" name="layer_id" type="int" /> <description> + Adds a collision polygon to the tile on the given TileSet physics layer. </description> </method> <method name="get_collision_polygon_one_way_margin" qualifiers="const"> @@ -18,6 +19,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="polygon_index" type="int" /> <description> + Returns the one-way margin (for one-way platforms) of the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="get_collision_polygon_points" qualifiers="const"> @@ -25,42 +27,49 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="polygon_index" type="int" /> <description> + Returns the points of the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="get_collision_polygons_count" qualifiers="const"> <return type="int" /> <argument index="0" name="layer_id" type="int" /> <description> + Returns how many polygons the tile has for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="get_custom_data" qualifiers="const"> <return type="Variant" /> <argument index="0" name="layer_name" type="String" /> <description> + Returns the custom data value for custom data layer named [code]layer_name[/code]. </description> </method> <method name="get_custom_data_by_layer_id" qualifiers="const"> <return type="Variant" /> <argument index="0" name="layer_id" type="int" /> <description> + Returns the custom data value for custom data layer with index [code]layer_id[/code]. </description> </method> <method name="get_navigation_polygon" qualifiers="const"> <return type="NavigationPolygon" /> <argument index="0" name="layer_id" type="int" /> <description> + Returns the navigation polygon of the tile for the TileSet navigation layer with index [code]layer_id[/code]. </description> </method> <method name="get_occluder" qualifiers="const"> <return type="OccluderPolygon2D" /> <argument index="0" name="layer_id" type="int" /> <description> + Returns the occluder polygon of the tile for the TileSet occlusion layer with index [code]layer_id[/code]. </description> </method> <method name="get_peering_bit_terrain" qualifiers="const"> <return type="int" /> <argument index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" /> <description> + Returns the tile's terrain bit for the given [code]peering_bit[/code] direction. </description> </method> <method name="is_collision_polygon_one_way" qualifiers="const"> @@ -68,6 +77,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="polygon_index" type="int" /> <description> + Returns whether one-way collisions are enabled for the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="remove_collision_polygon"> @@ -75,6 +85,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="polygon_index" type="int" /> <description> + Removes the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="set_collision_polygon_one_way"> @@ -83,6 +94,7 @@ <argument index="1" name="polygon_index" type="int" /> <argument index="2" name="one_way" type="bool" /> <description> + Enables/disables one-way collisions on the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="set_collision_polygon_one_way_margin"> @@ -91,6 +103,7 @@ <argument index="1" name="polygon_index" type="int" /> <argument index="2" name="one_way_margin" type="float" /> <description> + Enables/disables one-way collisions on the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="set_collision_polygon_points"> @@ -99,6 +112,7 @@ <argument index="1" name="polygon_index" type="int" /> <argument index="2" name="polygon" type="PackedVector2Array" /> <description> + Sets the points of the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="set_collision_polygons_count"> @@ -106,6 +120,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="polygons_count" type="int" /> <description> + Sets the polygons count for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="set_custom_data"> @@ -113,6 +128,7 @@ <argument index="0" name="layer_name" type="String" /> <argument index="1" name="value" type="Variant" /> <description> + Sets the tile's custom data value for the TileSet custom data layer with name [code]layer_name[/code]. </description> </method> <method name="set_custom_data_by_layer_id"> @@ -120,6 +136,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="value" type="Variant" /> <description> + Sets the tile's custom data value for the TileSet custom data layer with index [code]layer_id[/code]. </description> </method> <method name="set_navigation_polygon"> @@ -127,6 +144,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="navigation_polygon" type="NavigationPolygon" /> <description> + Sets the navigation polygon for the TileSet navigation layer with index [code]layer_id[/code]. </description> </method> <method name="set_occluder"> @@ -134,6 +152,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="occluder_polygon" type="OccluderPolygon2D" /> <description> + Sets the occluder for the TileSet occlusion layer with index [code]layer_id[/code]. </description> </method> <method name="set_peering_bit_terrain"> @@ -141,17 +160,7 @@ <argument index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" /> <argument index="1" name="terrain" type="int" /> <description> - </description> - </method> - <method name="tile_get_material" qualifiers="const"> - <return type="ShaderMaterial" /> - <description> - </description> - </method> - <method name="tile_set_material"> - <return type="void" /> - <argument index="0" name="material" type="ShaderMaterial" /> - <description> + Sets the tile's terrain bit for the given [code]peering_bit[/code] direction. </description> </method> </methods> @@ -160,6 +169,8 @@ </member> <member name="flip_v" type="bool" setter="set_flip_v" getter="get_flip_v" default="false"> </member> + <member name="material" type="ShaderMaterial" setter="set_material" getter="get_material"> + </member> <member name="modulate" type="Color" setter="set_modulate" getter="get_modulate" default="Color(1, 1, 1, 1)"> </member> <member name="probability" type="float" setter="set_probability" getter="get_probability" default="1.0"> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index f3c64c3c7d..4621d138ac 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -4,8 +4,7 @@ Node for 2D tile-based maps. </brief_description> <description> - Node for 2D tile-based maps. Tilemaps use a [TileSet] which contain a list of tiles (textures plus optional collision, navigation, and/or occluder shapes) which are used to create grid-based maps. - When doing physics queries against the tilemap, the cell coordinates are encoded as [code]metadata[/code] for each detected collision shape returned by methods such as [method PhysicsDirectSpaceState2D.intersect_shape], [method PhysicsDirectBodyState2D.get_contact_collider_shape_metadata] etc. + Node for 2D tile-based maps. Tilemaps use a [TileSet] which contain a list of tiles which are used to create grid-based maps. A TileMap may have several layers, layouting tiles on top of each other. </description> <tutorials> <link title="Using Tilemaps">https://docs.godotengine.org/en/latest/tutorials/2d/using_tilemaps.html</link> @@ -17,6 +16,13 @@ <link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link> </tutorials> <methods> + <method name="add_layer"> + <return type="void" /> + <argument index="0" name="to_position" type="int" /> + <description> + Adds a layer at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + </description> + </method> <method name="clear"> <return type="void" /> <description> @@ -35,6 +41,7 @@ <argument index="1" name="coords" type="Vector2i" /> <argument index="2" name="use_proxies" type="bool" /> <description> + Returns the tile alternative ID of the cell on layer [code]layer[/code] at [code]coords[/code]. If [code]use_proxies[/code] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. </description> </method> <method name="get_cell_atlas_coords" qualifiers="const"> @@ -43,6 +50,7 @@ <argument index="1" name="coords" type="Vector2i" /> <argument index="2" name="use_proxies" type="bool" /> <description> + Returns the tile atlas coordinates ID of the cell on layer [code]layer[/code] at coordinates [code]coords[/code]. If [code]use_proxies[/code] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. </description> </method> <method name="get_cell_source_id" qualifiers="const"> @@ -51,24 +59,33 @@ <argument index="1" name="coords" type="Vector2i" /> <argument index="2" name="use_proxies" type="bool" /> <description> + Returns the tile source ID of the cell on layer [code]layer[/code] at coordinates [code]coords[/code]. If [code]use_proxies[/code] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. </description> </method> <method name="get_layer_name" qualifiers="const"> <return type="String" /> <argument index="0" name="layer" type="int" /> <description> + Returns a TileMap layer's name. </description> </method> <method name="get_layer_y_sort_origin" qualifiers="const"> <return type="int" /> <argument index="0" name="layer" type="int" /> <description> + Returns a TileMap layer's Y sort origin. </description> </method> - <method name="get_layer_z_indexd" qualifiers="const"> + <method name="get_layer_z_index" qualifiers="const"> <return type="int" /> <argument index="0" name="layer" type="int" /> <description> + Returns a TileMap layer's Z-index value. + </description> + </method> + <method name="get_layers_count" qualifiers="const"> + <return type="int" /> + <description> </description> </method> <method name="get_neighbor_cell" qualifiers="const"> @@ -76,37 +93,41 @@ <argument index="0" name="coords" type="Vector2i" /> <argument index="1" name="neighbor" type="int" enum="TileSet.CellNeighbor" /> <description> + Returns the neighboring cell to the one at coordinates [code]coords[/code], indentified by the [code]neighbor[/code] direction. This method takes into account the different layouts a TileMap can take. </description> </method> <method name="get_surrounding_tiles"> <return type="Vector2i[]" /> <argument index="0" name="coords" type="Vector2i" /> <description> + Returns the list of all neighbourings cells to the one at [code]coords[/code] </description> </method> <method name="get_used_cells" qualifiers="const"> <return type="Vector2i[]" /> <argument index="0" name="layer" type="int" /> <description> - Returns a [Vector2] array with the positions of all cells containing a tile from the tileset (i.e. a tile index different from [code]-1[/code]). + Returns a [Vector2] array with the positions of all cells containing a tile in the given layer. A cell is considered empty if its source identifier equals -1, its atlas coordinates identifiers is [code]Vector2(-1, -1)[/code] and its alternative identifier is -1. </description> </method> <method name="get_used_rect"> <return type="Rect2" /> <description> - Returns a rectangle enclosing the used (non-empty) tiles of the map. + Returns a rectangle enclosing the used (non-empty) tiles of the map, including all layers. </description> </method> <method name="is_layer_enabled" qualifiers="const"> <return type="bool" /> <argument index="0" name="layer" type="int" /> <description> + Returns if a layer is enabled. </description> </method> <method name="is_layer_y_sort_enabled" qualifiers="const"> <return type="bool" /> <argument index="0" name="layer" type="int" /> <description> + Returns if a layer Y-sorts its tiles. </description> </method> <method name="map_to_world" qualifiers="const"> @@ -116,6 +137,21 @@ Returns the local position corresponding to the given tilemap (grid-based) coordinates. </description> </method> + <method name="move_layer"> + <return type="void" /> + <argument index="0" name="layer" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. + </description> + </method> + <method name="remove_layer"> + <return type="void" /> + <argument index="0" name="layer" type="int" /> + <description> + Moves the layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. + </description> + </method> <method name="set_cell"> <return type="void" /> <argument index="0" name="layer" type="int" /> @@ -124,7 +160,10 @@ <argument index="3" name="atlas_coords" type="Vector2i" default="Vector2i(-1, -1)" /> <argument index="4" name="alternative_tile" type="int" default="-1" /> <description> - Sets the tile index for the cell given by a Vector2i. + Sets the tile indentifiers for the cell on layer [code]layer[/code] at coordinates [code]coords[/code]. Each tile of the [TileSet] is identified using three parts: + - The source indentifier [code]source_id[/code] identifies a [TileSetSource] identifier. See [method TileSet.set_source_id], + - The atlas coordinates identifier [code]atlas_coords[/code] identifies a tile coordinates in the atlas (if the source is a [TileSetAtlasSource]. For [TileSetScenesCollectionSource] it should be 0), + - The alternative tile identifier [code]alternative_tile[/code] identifies a tile alternative the source is a [TileSetAtlasSource], and the scene for a [TileSetScenesCollectionSource]. </description> </method> <method name="set_layer_enabled"> @@ -132,6 +171,7 @@ <argument index="0" name="layer" type="int" /> <argument index="1" name="enabled" type="bool" /> <description> + Enables or disables the layer [code]layer[/code]. A disabled layer is not processed at all (no rendering, no physics, etc...). </description> </method> <method name="set_layer_name"> @@ -139,6 +179,7 @@ <argument index="0" name="layer" type="int" /> <argument index="1" name="name" type="String" /> <description> + Sets a layer's name. This is mostly useful in the editor. </description> </method> <method name="set_layer_y_sort_enabled"> @@ -146,6 +187,8 @@ <argument index="0" name="layer" type="int" /> <argument index="1" name="y_sort_enabled" type="bool" /> <description> + Enables or disables a layer's Y-sorting. If a layer is Y-sorted, the layer will behave as a CanvasItem node where each of its tile gets Y-sorted. + Y-sorted layers should usually be on different Z-index values than not Y-sorted layers, otherwise, each of those layer will be Y-sorted as whole with the Y-sorted one. This is usually an undesired behvaior. </description> </method> <method name="set_layer_y_sort_origin"> @@ -153,6 +196,8 @@ <argument index="0" name="layer" type="int" /> <argument index="1" name="y_sort_origin" type="int" /> <description> + Sets a layer's Y-sort origin value. This Y-sort origin value is added to each tile's Y-sort origin value. + This allows, for example, to fake a different height level on each layer. This can be useful for top-down view games. </description> </method> <method name="set_layer_z_index"> @@ -160,6 +205,7 @@ <argument index="0" name="layer" type="int" /> <argument index="1" name="z_index" type="int" /> <description> + Sets a layers Z-index value. This Z-index is added to each tile's Z-index value. </description> </method> <method name="world_to_map" qualifiers="const"> @@ -175,10 +221,10 @@ The TileMap's quadrant size. Optimizes drawing by batching, using chunks of this size. </member> <member name="collision_visibility_mode" type="int" setter="set_collision_visibility_mode" getter="get_collision_visibility_mode" enum="TileMap.VisibilityMode" default="0"> - </member> - <member name="layers_count" type="int" setter="set_layers_count" getter="get_layers_count" default="1"> + Show or hide the TileMap's collision shapes. If set to [code]VISIBILITY_MODE_DEFAULT[/code], this depends on the show collision debug settings. </member> <member name="navigation_visibility_mode" type="int" setter="set_navigation_visibility_mode" getter="get_navigation_visibility_mode" enum="TileMap.VisibilityMode" default="0"> + Show or hide the TileMap's collision shapes. If set to [code]VISIBILITY_MODE_DEFAULT[/code], this depends on the show navigation debug settings. </member> <member name="tile_set" type="TileSet" setter="set_tileset" getter="get_tileset"> The assigned [TileSet]. @@ -193,10 +239,13 @@ </signals> <constants> <constant name="VISIBILITY_MODE_DEFAULT" value="0" enum="VisibilityMode"> + Use the debug settings to determine visibility. </constant> <constant name="VISIBILITY_MODE_FORCE_HIDE" value="2" enum="VisibilityMode"> + Always hide. </constant> <constant name="VISIBILITY_MODE_FORCE_SHOW" value="1" enum="VisibilityMode"> + Always show. </constant> </constants> </class> diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml index 439c6e3830..02baded019 100644 --- a/doc/classes/TileSet.xml +++ b/doc/classes/TileSet.xml @@ -4,8 +4,13 @@ Tile library for tilemaps. </brief_description> <description> - A TileSet is a library of tiles for a [TileMap]. It contains a list of tiles, each consisting of a sprite and optional collision shapes. - Tiles are referenced by a unique integer ID. + A TileSet is a library of tiles for a [TileMap]. A TileSet handles a list of [TileSetSource], each of them storing a set of tiles. + Tiles can either be from a [TileSetAtlasSource], that render tiles out of a texture with support for physics, navigation, etc... or from a [TileSetScenesCollectionSource] which exposes scene-based tiles. + Tiles are referenced by using three IDs: their source ID, their atlas coordinates ID and their alternative tile ID. + + A TileSet can be configured so that its tiles expose more or less properties. To do so, the TileSet resources uses property layers, that you can add or remove depending on your needs. + For example, adding a physics layer allows giving collision shapes to your tiles. Each layer having dedicated properties (physics layer an mask), you may add several TileSet physics layers for each type of collision you need. + See the functions to add new layers for more information. </description> <tutorials> <link title="Using Tilemaps">https://docs.godotengine.org/en/latest/tutorials/2d/using_tilemaps.html</link> @@ -17,21 +22,72 @@ <link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link> </tutorials> <methods> + <method name="add_custom_data_layer"> + <return type="void" /> + <argument index="0" name="to_position" type="int" default="-1" /> + <description> + Adds a custom data layer to the TileSet at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + Custom data layers allow assigning custom properties to atlas tiles. + </description> + </method> + <method name="add_navigation_layer"> + <return type="void" /> + <argument index="0" name="to_position" type="int" default="-1" /> + <description> + Adds a navigation layer to the TileSet at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + Navigation layers allow assigning a navigable area to atlas tiles. + </description> + </method> + <method name="add_occlusion_layer"> + <return type="void" /> + <argument index="0" name="to_position" type="int" default="-1" /> + <description> + Adds an occlusion layer to the TileSet at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + Occlusion layers allow assigning occlusion polygons to atlas tiles. + </description> + </method> + <method name="add_physics_layer"> + <return type="void" /> + <argument index="0" name="to_position" type="int" default="-1" /> + <description> + Adds a physics layer to the TileSet at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + Physics layers allow assigning collision polygons to atlas tiles. + </description> + </method> <method name="add_source"> <return type="int" /> - <argument index="0" name="atlas_source_id_override" type="TileSetSource" /> - <argument index="1" name="arg1" type="int" default="-1" /> + <argument index="0" name="source" type="TileSetSource" /> + <argument index="1" name="atlas_source_id_override" type="int" default="-1" /> <description> + Adds a [TileSetSource] to the TileSet. If [code]atlas_source_id_override[/code] is not -1, also set its source ID. Otherwise, a unique identifier is automatically generated. + The function returns the added source source ID or -1 if the source could not be added. + </description> + </method> + <method name="add_terrain"> + <return type="void" /> + <argument index="0" name="terrain_set" type="int" /> + <argument index="1" name="to_position" type="int" default="-1" /> + <description> + Adds a new terrain to the given terrain set [code]terrain_set[/code] at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + </description> + </method> + <method name="add_terrain_set"> + <return type="void" /> + <argument index="0" name="to_position" type="int" default="-1" /> + <description> + Adds a new terrain set at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. </description> </method> <method name="cleanup_invalid_tile_proxies"> <return type="void" /> <description> + Clears tile proxies pointing to invalid tiles. </description> </method> <method name="clear_tile_proxies"> <return type="void" /> <description> + Clears all tile proxies. </description> </method> <method name="get_alternative_level_tile_proxy"> @@ -40,6 +96,8 @@ <argument index="1" name="coords_from" type="Vector2i" /> <argument index="2" name="alternative_from" type="int" /> <description> + Returns the alternative-level proxy for the given identifiers. The returned array contains the three proxie's target identifiers (source ID, atlas coords ID and alternative tile ID). + If the TileSet has no proxy for the given identifiers, returns an empty Array. </description> </method> <method name="get_coords_level_tile_proxy"> @@ -47,70 +105,108 @@ <argument index="0" name="source_from" type="int" /> <argument index="1" name="coords_from" type="Vector2i" /> <description> + Returns the coodinate-level proxy for the given identifiers. The returned array contains the two proxie's target identifiers (source ID and atlas coords ID). + If the TileSet has no proxy for the given identifiers, returns an empty Array. + </description> + </method> + <method name="get_custom_data_layers_count" qualifiers="const"> + <return type="int" /> + <description> + Returns the custom data layers count. </description> </method> <method name="get_navigation_layer_layers" qualifiers="const"> <return type="int" /> <argument index="0" name="layer_index" type="int" /> <description> + Returns the navigation layers (as in the Navigation server) of the gives TileSet navigation layer. + </description> + </method> + <method name="get_navigation_layers_count" qualifiers="const"> + <return type="int" /> + <description> + Returns the navigation layers count. </description> </method> <method name="get_next_source_id" qualifiers="const"> <return type="int" /> <description> + Returns a new unused source ID. This generated ID is the same that a call to [code]add_source[/code] would return. </description> </method> <method name="get_occlusion_layer_light_mask" qualifiers="const"> <return type="int" /> - <argument index="0" name="arg0" type="int" /> + <argument index="0" name="layer_index" type="int" /> <description> + Returns the light mask of the occlusion layer. </description> </method> <method name="get_occlusion_layer_sdf_collision" qualifiers="const"> <return type="bool" /> - <argument index="0" name="arg0" type="int" /> + <argument index="0" name="layer_index" type="int" /> + <description> + Returns if the occluders from this layer use [code]sdf_collision[/code]. + </description> + </method> + <method name="get_occlusion_layers_count" qualifiers="const"> + <return type="int" /> <description> + Returns the occlusion layers count. </description> </method> <method name="get_physics_layer_collision_layer" qualifiers="const"> <return type="int" /> <argument index="0" name="layer_index" type="int" /> <description> + Returns the collision layer (as in the physics server) bodies on the given TileSet's physics layer are in. </description> </method> <method name="get_physics_layer_collision_mask" qualifiers="const"> <return type="int" /> <argument index="0" name="layer_index" type="int" /> <description> + Returns the collision mask of bodies on the given TileSet's physics layer. </description> </method> <method name="get_physics_layer_physics_material" qualifiers="const"> <return type="PhysicsMaterial" /> <argument index="0" name="layer_index" type="int" /> <description> + Returns the physics material of bodies on the given TileSet's physics layer. + </description> + </method> + <method name="get_physics_layers_count" qualifiers="const"> + <return type="int" /> + <description> + Returns the physics layers count. </description> </method> <method name="get_source" qualifiers="const"> <return type="TileSetSource" /> - <argument index="0" name="index" type="int" /> + <argument index="0" name="source_id" type="int" /> <description> + Returns the [TileSetSource] with ID [code]source_id[/code]. </description> </method> <method name="get_source_count" qualifiers="const"> <return type="int" /> <description> + Returns the number of [TileSetSource] in this TileSet. </description> </method> <method name="get_source_id" qualifiers="const"> <return type="int" /> <argument index="0" name="index" type="int" /> <description> + Returns the source ID for source with index [code]index[/code]. </description> </method> <method name="get_source_level_tile_proxy"> <return type="int" /> <argument index="0" name="source_from" type="int" /> <description> + Returns the source-level proxy for the given source identifier. + If the TileSet has no proxy for the given identifier, returns -1. </description> </method> <method name="get_terrain_color" qualifiers="const"> @@ -118,6 +214,7 @@ <argument index="0" name="terrain_set" type="int" /> <argument index="1" name="terrain_index" type="int" /> <description> + Returns a terrain's color. </description> </method> <method name="get_terrain_name" qualifiers="const"> @@ -125,18 +222,27 @@ <argument index="0" name="terrain_set" type="int" /> <argument index="1" name="terrain_index" type="int" /> <description> + Returns a terrain's name. </description> </method> <method name="get_terrain_set_mode" qualifiers="const"> <return type="int" enum="TileSet.TerrainMode" /> <argument index="0" name="terrain_set" type="int" /> <description> + Returns a terrain set mode. + </description> + </method> + <method name="get_terrain_sets_count" qualifiers="const"> + <return type="int" /> + <description> + Returns the terrain sets count. </description> </method> <method name="get_terrains_count" qualifiers="const"> <return type="int" /> <argument index="0" name="terrain_set" type="int" /> <description> + Returns the number of terrains in the given terrain set. </description> </method> <method name="has_alternative_level_tile_proxy"> @@ -145,6 +251,7 @@ <argument index="1" name="coords_from" type="Vector2i" /> <argument index="2" name="alternative_from" type="int" /> <description> + Returns if there is and alternative-level proxy for the given identifiers. </description> </method> <method name="has_coords_level_tile_proxy"> @@ -152,18 +259,21 @@ <argument index="0" name="source_from" type="int" /> <argument index="1" name="coords_from" type="Vector2i" /> <description> + Returns if there is a coodinates-level proxy for the given identifiers. </description> </method> <method name="has_source" qualifiers="const"> <return type="bool" /> - <argument index="0" name="index" type="int" /> + <argument index="0" name="source_id" type="int" /> <description> + Returns if this TileSet has a source for the given source ID. </description> </method> <method name="has_source_level_tile_proxy"> <return type="bool" /> <argument index="0" name="source_from" type="int" /> <description> + Returns if there is a source-level proxy for the given source ID. </description> </method> <method name="map_tile_proxy" qualifiers="const"> @@ -172,6 +282,58 @@ <argument index="1" name="coords_from" type="Vector2i" /> <argument index="2" name="alternative_from" type="int" /> <description> + According to the configured proxies, maps the provided indentifiers to a new set of identifiers. The source ID, atlas coordinates ID and alternative tile ID are returned as a 3 elements Array. + This function first look for matching alternative-level proxies, then coordinates-level proxies, then source-level proxies. + If no proxy corresponding to provided identifiers are found, returns the same values the ones used as arguments. + </description> + </method> + <method name="move_custom_data_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the custom data layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="move_navigation_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the navigation layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="move_occlusion_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the occlusion layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="move_physics_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the physics layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="move_terrain"> + <return type="void" /> + <argument index="0" name="terrain_set" type="int" /> + <argument index="1" name="terrain_index" type="int" /> + <argument index="2" name="to_position" type="int" /> + <description> + Moves the terrain at index [code]terrain_index[/code] for terrain set [code]terrain_set[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="move_terrain_set"> + <return type="void" /> + <argument index="0" name="terrain_set" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the terrain set at index [code]terrain_set[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. </description> </method> <method name="remove_alternative_level_tile_proxy"> @@ -180,6 +342,7 @@ <argument index="1" name="coords_from" type="Vector2i" /> <argument index="2" name="alternative_from" type="int" /> <description> + Removes an alternative-level proxy for the given identifiers. </description> </method> <method name="remove_coords_level_tile_proxy"> @@ -187,18 +350,64 @@ <argument index="0" name="source_from" type="int" /> <argument index="1" name="coords_from" type="Vector2i" /> <description> + Removes a coordinates-level proxy for the given identifiers. + </description> + </method> + <method name="remove_custom_data_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <description> + Removes the custom data layer at index [code]layer_index[/code]. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="remove_navigation_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <description> + Removes the navigation layer at index [code]layer_index[/code]. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="remove_occlusion_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <description> + Removes the occlusion layer at index [code]layer_index[/code]. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="remove_physics_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <description> + Removes the physics layer at index [code]layer_index[/code]. Also updates the atlas tiles accordingly. </description> </method> <method name="remove_source"> <return type="void" /> <argument index="0" name="source_id" type="int" /> <description> + Removes the source with the given source ID. </description> </method> <method name="remove_source_level_tile_proxy"> <return type="void" /> <argument index="0" name="source_from" type="int" /> <description> + Removes a source-level tile proxy. + </description> + </method> + <method name="remove_terrain"> + <return type="void" /> + <argument index="0" name="terrain_set" type="int" /> + <argument index="1" name="terrain_index" type="int" /> + <description> + Removes the terrain at index [code]terrain_index[/code] in the given terrain set [code]terrain_set[/code]. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="remove_terrain_set"> + <return type="void" /> + <argument index="0" name="terrain_set" type="int" /> + <description> + Removes the terrain set at index [code]terrain_set[/code]. Also updates the atlas tiles accordingly. </description> </method> <method name="set_alternative_level_tile_proxy"> @@ -210,6 +419,9 @@ <argument index="4" name="coords_to" type="Vector2i" /> <argument index="5" name="alternative_to" type="int" /> <description> + Create an alternative-level proxy for the given identifiers. A proxy will map set of tile identifiers to another set of identifiers. + This can be used to replace a tile in all TileMaps using this TileSet, as TileMap nodes will find and use the proxy's target tile when one is available. + Proxied tiles can be automatically replaced in TileMap nodes using the editor. </description> </method> <method name="set_coords_level_tile_proxy"> @@ -219,6 +431,9 @@ <argument index="2" name="source_to" type="int" /> <argument index="3" name="coords_to" type="Vector2i" /> <description> + Creates a coordinates-level proxy for the given identifiers. A proxy will map set of tile identifiers to another set of identifiers. The alternative tile ID is kept the same when using coordinates-level proxies. + This can be used to replace a tile in all TileMaps using this TileSet, as TileMap nodes will find and use the proxy's target tile when one is available. + Proxied tiles can be automatically replaced in TileMap nodes using the editor. </description> </method> <method name="set_navigation_layer_layers"> @@ -226,6 +441,7 @@ <argument index="0" name="layer_index" type="int" /> <argument index="1" name="layers" type="int" /> <description> + Sets the navigation layers (as in the navigation server) for navigation regions is the given TileSet navigation layer. </description> </method> <method name="set_occlusion_layer_light_mask"> @@ -233,13 +449,15 @@ <argument index="0" name="layer_index" type="int" /> <argument index="1" name="light_mask" type="int" /> <description> + Sets the occlusion layer (as in the rendering server) for occluders in the given TileSet occlusion layer. </description> </method> <method name="set_occlusion_layer_sdf_collision"> <return type="void" /> <argument index="0" name="layer_index" type="int" /> - <argument index="1" name="sdf_collision" type="int" /> + <argument index="1" name="sdf_collision" type="bool" /> <description> + Enables or disables sdf collision for occluders in the given TileSet occlusion layer. </description> </method> <method name="set_physics_layer_collision_layer"> @@ -247,6 +465,7 @@ <argument index="0" name="layer_index" type="int" /> <argument index="1" name="layer" type="int" /> <description> + Sets the physics layer (as in the physics server) for bodies in the given TileSet physics layer. </description> </method> <method name="set_physics_layer_collision_mask"> @@ -254,6 +473,7 @@ <argument index="0" name="layer_index" type="int" /> <argument index="1" name="mask" type="int" /> <description> + Sets the physics layer (as in the physics server) for bodies in the given TileSet physics layer. </description> </method> <method name="set_physics_layer_physics_material"> @@ -261,13 +481,15 @@ <argument index="0" name="layer_index" type="int" /> <argument index="1" name="physics_material" type="PhysicsMaterial" /> <description> + Sets the physics material for bodies in the given TileSet physics layer. </description> </method> <method name="set_source_id"> <return type="void" /> <argument index="0" name="source_id" type="int" /> - <argument index="1" name="arg1" type="int" /> + <argument index="1" name="new_source_id" type="int" /> <description> + Changes a source's ID. </description> </method> <method name="set_source_level_tile_proxy"> @@ -275,6 +497,9 @@ <argument index="0" name="source_from" type="int" /> <argument index="1" name="source_to" type="int" /> <description> + Creates a source-level proxy for the given source ID. A proxy will map set of tile identifiers to another set of identifiers. Both the atlac coordinates ID and the alternative tile ID are kept the same when using source-level proxies. + This can be used to replace a source in all TileMaps using this TileSet, as TileMap nodes will find and use the proxy's target source when one is available. + Proxied tiles can be automatically replaced in TileMap nodes using the editor. </description> </method> <method name="set_terrain_color"> @@ -283,6 +508,7 @@ <argument index="1" name="terrain_index" type="int" /> <argument index="2" name="color" type="Color" /> <description> + Sets a terrain's color. This color is used for identifying the different terrains in the TileSet editor. </description> </method> <method name="set_terrain_name"> @@ -291,6 +517,7 @@ <argument index="1" name="terrain_index" type="int" /> <argument index="2" name="name" type="String" /> <description> + Sets a terrain's name. </description> </method> <method name="set_terrain_set_mode"> @@ -298,103 +525,120 @@ <argument index="0" name="terrain_set" type="int" /> <argument index="1" name="mode" type="int" enum="TileSet.TerrainMode" /> <description> - </description> - </method> - <method name="set_terrains_count"> - <return type="void" /> - <argument index="0" name="terrain_set" type="int" /> - <argument index="1" name="terrains_count" type="int" /> - <description> + Sets a terrain mode. Each mode determines which bits of a tile shape is used to match the neighbouring tiles' terrains. </description> </method> </methods> <members> - <member name="custom_data_layers_count" type="int" setter="set_custom_data_layers_count" getter="get_custom_data_layers_count" default="0"> - </member> - <member name="navigation_layers_count" type="int" setter="set_navigation_layers_count" getter="get_navigation_layers_count" default="0"> - </member> - <member name="occlusion_layers_count" type="int" setter="set_occlusion_layers_count" getter="get_occlusion_layers_count" default="0"> - </member> - <member name="physics_layers_count" type="int" setter="set_physics_layers_count" getter="get_physics_layers_count" default="0"> - </member> - <member name="terrains_sets_count" type="int" setter="set_terrain_sets_count" getter="get_terrain_sets_count" default="0"> - </member> <member name="tile_layout" type="int" setter="set_tile_layout" getter="get_tile_layout" enum="TileSet.TileLayout" default="0"> + For all half-offset shapes (Isometric, Hexagonal and Half-Offset square), changes the way tiles are indexed in the TileMap grid. </member> <member name="tile_offset_axis" type="int" setter="set_tile_offset_axis" getter="get_tile_offset_axis" enum="TileSet.TileOffsetAxis" default="0"> + For all half-offset shapes (Isometric, Hexagonal and Half-Offset square), determines the offset axis. </member> <member name="tile_shape" type="int" setter="set_tile_shape" getter="get_tile_shape" enum="TileSet.TileShape" default="0"> + The tile shape. </member> <member name="tile_size" type="Vector2i" setter="set_tile_size" getter="get_tile_size" default="Vector2i(16, 16)"> + The tile size, in pixels. For all tile shapes, this size corresponds to the encompassing rectangle of the tile shape. This is thus the minimal cell size required in an atlas. </member> <member name="uv_clipping" type="bool" setter="set_uv_clipping" getter="is_uv_clipping" default="false"> + Enables/Disable uv clipping when rendering the tiles. </member> </members> <constants> <constant name="TILE_SHAPE_SQUARE" value="0" enum="TileShape"> - Orthogonal orientation mode. + Rectangular tile shape. </constant> <constant name="TILE_SHAPE_ISOMETRIC" value="1" enum="TileShape"> - Isometric orientation mode. + Diamond tile shape (for isometric look). </constant> <constant name="TILE_SHAPE_HALF_OFFSET_SQUARE" value="2" enum="TileShape"> + Rectangular tile shape with one row/colum out of two offset by half a tile. </constant> <constant name="TILE_SHAPE_HEXAGON" value="3" enum="TileShape"> - Hexagon orientation mode. + Hexagonal tile shape. </constant> <constant name="TILE_LAYOUT_STACKED" value="0" enum="TileLayout"> + Tile coordinates layout where both axis stay consistent with their respective local horizontal and vertical axis. </constant> <constant name="TILE_LAYOUT_STACKED_OFFSET" value="1" enum="TileLayout"> + Same as [code]TILE_LAYOUT_STAKED[/code], but the first half-offset is negative instead of positive. </constant> <constant name="TILE_LAYOUT_STAIRS_RIGHT" value="2" enum="TileLayout"> + Tile coordinates layout where the horizontal axis stay horizontal, and the vertical one goes down-right. </constant> <constant name="TILE_LAYOUT_STAIRS_DOWN" value="3" enum="TileLayout"> + Tile coordinates layout where the vertical axis stay vertical, and the horizontal one goes down-right. </constant> <constant name="TILE_LAYOUT_DIAMOND_RIGHT" value="4" enum="TileLayout"> + Tile coordinates layout where the horizontal axis goes up-right, and the vertical one goes down-right. </constant> <constant name="TILE_LAYOUT_DIAMOND_DOWN" value="5" enum="TileLayout"> + Tile coordinates layout where the horizontal axis goes down-right, and the vertical one goes down-left. </constant> <constant name="TILE_OFFSET_AXIS_HORIZONTAL" value="0" enum="TileOffsetAxis"> + Horizontal half-offset. </constant> <constant name="TILE_OFFSET_AXIS_VERTICAL" value="1" enum="TileOffsetAxis"> + Vertical half-offset. </constant> <constant name="CELL_NEIGHBOR_RIGHT_SIDE" value="0" enum="CellNeighbor"> + Neighbor on the right side. </constant> <constant name="CELL_NEIGHBOR_RIGHT_CORNER" value="1" enum="CellNeighbor"> + Neighbor in the right corner. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE" value="2" enum="CellNeighbor"> + Neighbor on the bottom right side. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER" value="3" enum="CellNeighbor"> + Neighbor in the bottom right corner. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_SIDE" value="4" enum="CellNeighbor"> + Neighbor on the bottom side. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_CORNER" value="5" enum="CellNeighbor"> + Neighbor in the bottom corner. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_LEFT_SIDE" value="6" enum="CellNeighbor"> + Neighbor on the bottom left side. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_LEFT_CORNER" value="7" enum="CellNeighbor"> + Neighbor in the bottom left corner. </constant> <constant name="CELL_NEIGHBOR_LEFT_SIDE" value="8" enum="CellNeighbor"> + Neighbor on the left side. </constant> <constant name="CELL_NEIGHBOR_LEFT_CORNER" value="9" enum="CellNeighbor"> + Neighbor in the left corner. </constant> <constant name="CELL_NEIGHBOR_TOP_LEFT_SIDE" value="10" enum="CellNeighbor"> + Neighbor on the top left side. </constant> <constant name="CELL_NEIGHBOR_TOP_LEFT_CORNER" value="11" enum="CellNeighbor"> + Neighbor in the top left corner. </constant> <constant name="CELL_NEIGHBOR_TOP_SIDE" value="12" enum="CellNeighbor"> + Neighbor on the top side. </constant> <constant name="CELL_NEIGHBOR_TOP_CORNER" value="13" enum="CellNeighbor"> + Neighbor in the top corner. </constant> <constant name="CELL_NEIGHBOR_TOP_RIGHT_SIDE" value="14" enum="CellNeighbor"> + Neighbor on the top right side. </constant> <constant name="CELL_NEIGHBOR_TOP_RIGHT_CORNER" value="15" enum="CellNeighbor"> + Neighbor in the top right corner. </constant> <constant name="TERRAIN_MODE_MATCH_CORNERS_AND_SIDES" value="0" enum="TerrainMode"> + Requires both corners and side to match with neighboring tiles' terrains. </constant> <constant name="TERRAIN_MODE_MATCH_CORNERS" value="1" enum="TerrainMode"> + Requires corners to match with neighboring tiles' terrains. </constant> <constant name="TERRAIN_MODE_MATCH_SIDES" value="2" enum="TerrainMode"> + Requires sides to match with neighboring tiles' terrains. </constant> </constants> </class> diff --git a/doc/classes/TileSetAtlasSource.xml b/doc/classes/TileSetAtlasSource.xml index 8caa3a7c39..fd3dbd1e4d 100644 --- a/doc/classes/TileSetAtlasSource.xml +++ b/doc/classes/TileSetAtlasSource.xml @@ -1,8 +1,16 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="TileSetAtlasSource" inherits="TileSetSource" version="4.0"> <brief_description> + Exposes a 2D atlas texture as a set of tiles for a [TileSet] resource. </brief_description> <description> + An atlas is a grid of tiles laid out on a texture. Each tile in the grid must be exposed using [method create_tile]. Those tiles are then indexed using their coordinates in the grid. + Each tile can also have a size in the grid coordinates, making it more or less cells in the atlas. + + Alternatives version of a tile can be created using [method create_alternative_tile], which are then indexed using an alternative ID. The main tile (the one in the grid), is accessed with an alternative ID equal to 0. + + Each tile alternate has a set of properties that is defined by the source's [TileSet] layers. Those properties are stored in a TileData object that can be accessed and modified using [method get_tile_data]. + As TileData properties are stored directly in the TileSetAtlasSource resource, their properties might also be set using [code]TileSetAtlasSource.set("<coords_x>:<coords_y>/<alternative_id>/<tile_data_property>")[/code]. </description> <tutorials> </tutorials> @@ -13,11 +21,14 @@ <argument index="1" name="new_atlas_coords" type="Vector2i" default="Vector2i(-1, -1)" /> <argument index="2" name="new_size" type="Vector2i" default="Vector2i(-1, -1)" /> <description> + Returns true if the tile at the [code]atlas_coords[/code] coordinates can be moved to the [code]new_atlas_coords[/code] coordinates with the [code]new_size[/code] size. This functions returns false if a tile is already present in the given area, or if this area is outside the atlas boundaries. + If [code]new_atlas_coords[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's coordinates. If [code]new_size[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's size. </description> </method> <method name="clear_tiles_outside_texture"> <return type="void" /> <description> + Clears all tiles that are defined outside the texture boundaries. </description> </method> <method name="create_alternative_tile"> @@ -25,6 +36,8 @@ <argument index="0" name="atlas_coords" type="Vector2i" /> <argument index="1" name="alternative_id_override" type="int" default="-1" /> <description> + Creates an alternative tile for the tile at coords [code]atlas_coords[/code]. If [code]alternative_id_override[/code] is -1, give it an automatically generated unique ID, or assigns it the given ID otherwise. + Returns the new alternative identifier, or -1 if the alternative could not be created with a provided [code]alternative_id_override[/code]. </description> </method> <method name="create_tile"> @@ -32,84 +45,55 @@ <argument index="0" name="atlas_coords" type="Vector2i" /> <argument index="1" name="size" type="Vector2i" default="Vector2i(1, 1)" /> <description> - </description> - </method> - <method name="get_alternative_tile_id" qualifiers="const"> - <return type="int" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <argument index="1" name="index" type="int" /> - <description> - </description> - </method> - <method name="get_alternative_tiles_count" qualifiers="const"> - <return type="int" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <description> + Creates a new tile at coords [code]atlas_coords[/code] with size [code]size[/code]. </description> </method> <method name="get_atlas_grid_size" qualifiers="const"> <return type="Vector2i" /> <description> + Returns the atlas grid size, which depends on how many tiles can fit in the texture. It thus depends on the Texture's size, the atlas [code]margins[/code] the tiles' [code]texture_region_size[/code]. </description> </method> <method name="get_next_alternative_tile_id" qualifiers="const"> <return type="int" /> <argument index="0" name="atlas_coords" type="Vector2i" /> <description> + Returns the alternative ID a following call to [method create_alternative_tile] would return. </description> </method> <method name="get_tile_at_coords" qualifiers="const"> <return type="Vector2i" /> <argument index="0" name="atlas_coords" type="Vector2i" /> <description> + If there is a tile covering the [code]atlas_coords[/code] coordinates, returns the top-left coordinates of the tile (thus its coordinate ID). Returns [code]Vector2i(-1, -1)[/code] otherwise. </description> </method> <method name="get_tile_data" qualifiers="const"> <return type="Object" /> <argument index="0" name="atlas_coords" type="Vector2i" /> - <argument index="1" name="index" type="int" /> - <description> - </description> - </method> - <method name="get_tile_id" qualifiers="const"> - <return type="Vector2i" /> - <argument index="0" name="index" type="int" /> + <argument index="1" name="alternative_tile" type="int" /> <description> + Returns the [TileData] object for the given atlas coordinates and alternative ID. </description> </method> <method name="get_tile_size_in_atlas" qualifiers="const"> <return type="Vector2i" /> <argument index="0" name="atlas_coords" type="Vector2i" /> <description> + Returns the size of the tile (in the grid coordinates system) at coordinates [code]atlas_coords[/code]. </description> </method> <method name="get_tile_texture_region" qualifiers="const"> <return type="Rect2i" /> <argument index="0" name="atlas_coords" type="Vector2i" /> <description> - </description> - </method> - <method name="get_tiles_count" qualifiers="const"> - <return type="int" /> - <description> - </description> - </method> - <method name="has_alternative_tile" qualifiers="const"> - <return type="bool" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <argument index="1" name="alternative_tile" type="int" /> - <description> - </description> - </method> - <method name="has_tile" qualifiers="const"> - <return type="bool" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <description> + Returns a tile's texture region in the atlas texture. </description> </method> <method name="has_tiles_outside_texture"> <return type="bool" /> <description> + Returns if this atlas has tiles outside of its texture. </description> </method> <method name="move_tile_in_atlas"> @@ -118,6 +102,9 @@ <argument index="1" name="new_atlas_coords" type="Vector2i" default="Vector2i(-1, -1)" /> <argument index="2" name="new_size" type="Vector2i" default="Vector2i(-1, -1)" /> <description> + Move the tile and its alternatives at the [code]atlas_coords[/code] coordinates to the [code]new_atlas_coords[/code] coordinates with the [code]new_size[/code] size. This functions will fail if a tile is already present in the given area. + If [code]new_atlas_coords[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's coordinates. If [code]new_size[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's size. + To avoid an error, first check if a move is possible using [method can_move_tile_in_atlas]. </description> </method> <method name="remove_alternative_tile"> @@ -125,12 +112,15 @@ <argument index="0" name="atlas_coords" type="Vector2i" /> <argument index="1" name="alternative_tile" type="int" /> <description> + Remove a tile's alternative with alternative ID [code]alternative_tile[/code]. + Calling this function with [code]alternative_tile[/code] equals to 0 will fail, as the base tile alternative cannot be removed. </description> </method> <method name="remove_tile"> <return type="void" /> <argument index="0" name="atlas_coords" type="Vector2i" /> <description> + Remove a tile and its alternative at coordinates [code]atlas_coords[/code]. </description> </method> <method name="set_alternative_tile_id"> @@ -139,17 +129,23 @@ <argument index="1" name="alternative_tile" type="int" /> <argument index="2" name="new_id" type="int" /> <description> + Change a tile's alternative ID from [code]alternative_tile[/code] to [code]new_id[/code]. + Calling this function with [code]alternative_id[/code] equals to 0 will fail, as the base tile alternative cannot be moved. </description> </method> </methods> <members> <member name="margins" type="Vector2i" setter="set_margins" getter="get_margins" default="Vector2i(0, 0)"> + Margins, in pixels, to offset the origin of the grid in the texture. </member> <member name="separation" type="Vector2i" setter="set_separation" getter="get_separation" default="Vector2i(0, 0)"> + Separation, in pixels, between each tile texture region of the grid. </member> <member name="texture" type="Texture2D" setter="set_texture" getter="get_texture"> + The atlas texture. </member> - <member name="tile_size" type="Vector2i" setter="set_texture_region_size" getter="get_texture_region_size" default="Vector2i(16, 16)"> + <member name="texture_region_size" type="Vector2i" setter="set_texture_region_size" getter="get_texture_region_size" default="Vector2i(16, 16)"> + The base tile size in the texture (in pixel). This size must be bigger than the TileSet's [code]tile_size[/code] value. </member> </members> <constants> diff --git a/doc/classes/TileSetScenesCollectionSource.xml b/doc/classes/TileSetScenesCollectionSource.xml index a44f519a4c..119a04c25f 100644 --- a/doc/classes/TileSetScenesCollectionSource.xml +++ b/doc/classes/TileSetScenesCollectionSource.xml @@ -1,8 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="TileSetScenesCollectionSource" inherits="TileSetSource" version="4.0"> <brief_description> + Exposes a set of scenes as tiles for a [TileSet] resource. </brief_description> <description> + When placed on a [TileMap], tiles from [TileSetScenesCollectionSource] will automatically instanciate an assiciated scene at the cell's position in the TileMap. + Scenes are instanciated as children of the [TileMap] when it enters the tree. If you add/remove a scene tile in the [TileMap] that is already inside the tree, the [TileMap] will automatically instanciate/free the scene accordingly. </description> <tutorials> </tutorials> @@ -12,83 +15,55 @@ <argument index="0" name="packed_scene" type="PackedScene" /> <argument index="1" name="id_override" type="int" default="-1" /> <description> - </description> - </method> - <method name="get_alternative_tile_id" qualifiers="const"> - <return type="int" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <argument index="1" name="index" type="int" /> - <description> - </description> - </method> - <method name="get_alternative_tiles_count" qualifiers="const"> - <return type="int" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <description> + Creates a scene-based tile out of the given scene. + Returns a newly generated unique ID. </description> </method> <method name="get_next_scene_tile_id" qualifiers="const"> <return type="int" /> <description> + Returns the scene ID a following call to [method create_scene_tile] would return. </description> </method> <method name="get_scene_tile_display_placeholder" qualifiers="const"> <return type="bool" /> <argument index="0" name="id" type="int" /> <description> + Returns whether the scene tile with id [code]id[/code] displays a placeholder in the editor. </description> </method> <method name="get_scene_tile_id"> <return type="int" /> <argument index="0" name="index" type="int" /> <description> + Returns the scene tile ID of the scene tile at index [code]index[/code]. </description> </method> <method name="get_scene_tile_scene" qualifiers="const"> <return type="PackedScene" /> <argument index="0" name="id" type="int" /> <description> + Returns the [PackedScene] resource of scene tile with id [code]id[/code]. </description> </method> <method name="get_scene_tiles_count"> <return type="int" /> <description> - </description> - </method> - <method name="get_tile_id" qualifiers="const"> - <return type="Vector2i" /> - <argument index="0" name="index" type="int" /> - <description> - </description> - </method> - <method name="get_tiles_count" qualifiers="const"> - <return type="int" /> - <description> - </description> - </method> - <method name="has_alternative_tile" qualifiers="const"> - <return type="bool" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <argument index="1" name="alternative_tile" type="int" /> - <description> + Returns the number or scene tiles this TileSet source has. </description> </method> <method name="has_scene_tile_id"> <return type="bool" /> <argument index="0" name="id" type="int" /> <description> - </description> - </method> - <method name="has_tile" qualifiers="const"> - <return type="bool" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <description> + Returns whether this TileSet source has a scene tile with id [code]id[/code]. </description> </method> <method name="remove_scene_tile"> <return type="void" /> <argument index="0" name="id" type="int" /> <description> + Remove the scene tile with id [code]id[/code]. </description> </method> <method name="set_scene_tile_display_placeholder"> @@ -96,6 +71,7 @@ <argument index="0" name="id" type="int" /> <argument index="1" name="display_placeholder" type="bool" /> <description> + Sets whether or not the scene tile with id [code]id[/code] should display a placeholder in the editor. This might be useful for scenes that are not visible. </description> </method> <method name="set_scene_tile_id"> @@ -103,6 +79,7 @@ <argument index="0" name="id" type="int" /> <argument index="1" name="new_id" type="int" /> <description> + Changes a scene tile's ID from [code]id[/code] to [code]new_id[/code]. This will fail if there is already a tile with a ID equal to [code]new_id[/code]. </description> </method> <method name="set_scene_tile_scene"> @@ -110,6 +87,7 @@ <argument index="0" name="id" type="int" /> <argument index="1" name="packed_scene" type="PackedScene" /> <description> + Assigns a [PackedScene] resource to the scene tile with id [code]id[/code]. This will fail if the scene does not extend CanvasItem, as positionning properties are needed to place the scene on the TileMap. </description> </method> </methods> diff --git a/doc/classes/TileSetSource.xml b/doc/classes/TileSetSource.xml index 6a3029bb3f..442d845f6c 100644 --- a/doc/classes/TileSetSource.xml +++ b/doc/classes/TileSetSource.xml @@ -1,12 +1,63 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="TileSetSource" inherits="Resource" version="4.0"> <brief_description> + Exposes a set of tiles for a [TileSet] resource. </brief_description> <description> + Exposes a set of tiles for a [TileSet] resource. + Tiles in a source are indexed with two IDs, coordinates ID (of type Vector2i) and an alternative ID (of type int), named according to their use in the [TileSetAtlasSource] class. + Depending on the TileSet source type, those IDs might have restrictions on their values, this is why the base [TileSetSource] class only exposes getters for them. + + You can iterate over all tiles exposed by a TileSetSource by first iterating over coordinates IDs using [method get_tiles_count] and [method get_tile_id], then over alternative IDs using [method get_alternative_tiles_count] and [method get_alternative_tile_id]. </description> <tutorials> </tutorials> <methods> + <method name="get_alternative_tile_id" qualifiers="const"> + <return type="int" /> + <argument index="0" name="atlas_coords" type="Vector2i" /> + <argument index="1" name="index" type="int" /> + <description> + Returns the alternative ID for the tile with coordinates ID [code]atlas_coords[/code] at index [code]index[/code]. + </description> + </method> + <method name="get_alternative_tiles_count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="atlas_coords" type="Vector2i" /> + <description> + Returns the number of alternatives tiles for the coordinates ID [code]atlas_coords[/code]. + For [TileSetAtlasSource], this always return at least 1, as the base tile with ID 0 is always part of the alternatives list. + Returns -1 if there is not tile at the given coords. + </description> + </method> + <method name="get_tile_id" qualifiers="const"> + <return type="Vector2i" /> + <argument index="0" name="index" type="int" /> + <description> + Returns the tile coordinates ID of the tile with index [code]index[/code]. + </description> + </method> + <method name="get_tiles_count" qualifiers="const"> + <return type="int" /> + <description> + Returns how many tiles this atlas source defines (not including alternative tiles). + </description> + </method> + <method name="has_alternative_tile" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="atlas_coords" type="Vector2i" /> + <argument index="1" name="alternative_tile" type="int" /> + <description> + Returns if the base tile at coordinates [code]atlas_coords[/code] has an alternative with ID [code]alternative_tile[/code]. + </description> + </method> + <method name="has_tile" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="atlas_coords" type="Vector2i" /> + <description> + Returns if this atlas has a tile with coordinates ID [code]atlas_coordinates[/code]. + </description> + </method> </methods> <constants> </constants> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 406c074758..b997d87ac0 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -140,6 +140,14 @@ <return type="Font" /> <argument index="0" name="column" type="int" /> <description> + Returns custom font used to draw text in the column [code]column[/code]. + </description> + </method> + <method name="get_custom_font_size" qualifiers="const"> + <return type="int" /> + <argument index="0" name="column" type="int" /> + <description> + Returns custom font size used to draw text in the column [code]column[/code]. </description> </method> <method name="get_expand_right" qualifiers="const"> @@ -464,6 +472,15 @@ <argument index="0" name="column" type="int" /> <argument index="1" name="font" type="Font" /> <description> + Sets custom font used to draw text in the column [code]column[/code]. + </description> + </method> + <method name="set_custom_font_size"> + <return type="void" /> + <argument index="0" name="column" type="int" /> + <argument index="1" name="font_size" type="int" /> + <description> + Sets custom font size used to draw text in the column [code]column[/code]. </description> </method> <method name="set_editable"> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index cb5662419e..ab4d0e181a 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -155,6 +155,18 @@ Returns the vector with all components rounded down (towards negative infinity). </description> </method> + <method name="from_angle" qualifiers="static"> + <return type="Vector2" /> + <argument index="0" name="angle" type="float" /> + <description> + Creates a unit [Vector2] rotated to the given [code]angle[/code] in radians. This is equivalent to doing [code]Vector2(cos(angle), sin(angle))[/code] or [code]Vector2.RIGHT.rotated(angle)[/code]. + [codeblock] + print(Vector2.from_angle(0)) # Prints (1, 0). + print(Vector2(1, 0).angle()) # Prints 0, which is the angle used above. + print(Vector2.from_angle(PI / 2)) # Prints (0, 1). + [/codeblock] + </description> + </method> <method name="is_equal_approx" qualifiers="const"> <return type="bool" /> <argument index="0" name="to" type="Vector2" /> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 4a62d3ec7b..a02a23517f 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -113,26 +113,34 @@ Returns [code]true[/code] if the viewport is currently performing a drag operation. </description> </method> - <method name="input"> + <method name="is_embedding_subwindows" qualifiers="const"> + <return type="bool" /> + <description> + </description> + </method> + <method name="is_input_handled" qualifiers="const"> + <return type="bool" /> + <description> + </description> + </method> + <method name="push_input"> <return type="void" /> <argument index="0" name="event" type="InputEvent" /> <argument index="1" name="in_local_coords" type="bool" default="false" /> <description> </description> </method> - <method name="input_text"> + <method name="push_text_input"> <return type="void" /> <argument index="0" name="text" type="String" /> <description> + Returns [code]true[/code] if the viewport is currently embedding windows. </description> </method> - <method name="is_embedding_subwindows" qualifiers="const"> - <return type="bool" /> - <description> - </description> - </method> - <method name="is_input_handled" qualifiers="const"> - <return type="bool" /> + <method name="push_unhandled_input"> + <return type="void" /> + <argument index="0" name="event" type="InputEvent" /> + <argument index="1" name="in_local_coords" type="bool" default="false" /> <description> </description> </method> @@ -150,13 +158,6 @@ Sets the number of subdivisions to use in the specified quadrant. A higher number of subdivisions allows you to have more shadows in the scene at once, but reduces the quality of the shadows. A good practice is to have quadrants with a varying number of subdivisions and to have as few subdivisions as possible. </description> </method> - <method name="unhandled_input"> - <return type="void" /> - <argument index="0" name="event" type="InputEvent" /> - <argument index="1" name="in_local_coords" type="bool" default="false" /> - <description> - </description> - </method> <method name="warp_mouse"> <return type="void" /> <argument index="0" name="to_position" type="Vector2" /> @@ -211,6 +212,9 @@ <member name="physics_object_picking" type="bool" setter="set_physics_object_picking" getter="get_physics_object_picking" default="false"> If [code]true[/code], the objects rendered by viewport become subjects of mouse picking process. </member> + <member name="scale_3d" type="int" setter="set_scale_3d" getter="get_scale_3d" enum="Viewport.Scale3D" default="0"> + The scale at which 3D content is rendered. + </member> <member name="screen_space_aa" type="int" setter="set_screen_space_aa" getter="get_screen_space_aa" enum="Viewport.ScreenSpaceAA" default="0"> Sets the screen-space antialiasing method used. Screen-space antialiasing works by selectively blurring edges in a post-process shader. It differs from MSAA which takes multiple coverage samples while rendering objects. Screen-space AA methods are typically faster than MSAA and will smooth out specular aliasing, but tend to make scenes appear blurry. </member> @@ -271,6 +275,16 @@ </signal> </signals> <constants> + <constant name="SCALE_3D_DISABLED" value="0" enum="Scale3D"> + </constant> + <constant name="SCALE_3D_75_PERCENT" value="1" enum="Scale3D"> + </constant> + <constant name="SCALE_3D_50_PERCENT" value="2" enum="Scale3D"> + </constant> + <constant name="SCALE_3D_33_PERCENT" value="3" enum="Scale3D"> + </constant> + <constant name="SCALE_3D_25_PERCENT" value="4" enum="Scale3D"> + </constant> <constant name="SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED" value="0" enum="ShadowAtlasQuadrantSubdiv"> This quadrant will not be used. </constant> diff --git a/modules/visual_script/doc_classes/VisualScriptEditor.xml b/doc/classes/VisualScriptCustomNodes.xml index 9ea889c77b..3ef8022f5e 100644 --- a/modules/visual_script/doc_classes/VisualScriptEditor.xml +++ b/doc/classes/VisualScriptCustomNodes.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptEditor" inherits="Object" version="4.0"> +<class name="VisualScriptCustomNodes" inherits="Object" version="4.0"> <brief_description> + Manages custom nodes for the Visual Script editor. </brief_description> <description> + This singleton can be used to manage (i.e., add or remove) custom nodes for the Visual Script editor. </description> <tutorials> </tutorials> diff --git a/doc/classes/XRInterface.xml b/doc/classes/XRInterface.xml index 1fb73e59b4..2a740ab1e8 100644 --- a/doc/classes/XRInterface.xml +++ b/doc/classes/XRInterface.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="XRInterface" inherits="RefCounted" version="4.0"> <brief_description> - Base class for an AR/VR interface implementation. + Base class for an XR interface implementation. </brief_description> <description> This class needs to be implemented to make an AR or VR platform available to Godot and these should be implemented as C++ modules or GDNative modules (note that for GDNative the subclass XRScriptInterface should be used). Part of the interface is exposed to GDScript so you can detect, enable and configure an AR or VR platform. @@ -26,17 +26,17 @@ <method name="get_name" qualifiers="const"> <return type="StringName" /> <description> - Returns the name of this interface (OpenVR, OpenHMD, ARKit, etc). + Returns the name of this interface (OpenXR, OpenVR, OpenHMD, ARKit, etc). </description> </method> - <method name="get_render_targetsize"> + <method name="get_render_target_size"> <return type="Vector2" /> <description> Returns the resolution at which we should render our intermediate results before things like lens distortion are applied by the VR platform. </description> </method> <method name="get_tracking_status" qualifiers="const"> - <return type="int" enum="XRInterface.Tracking_status" /> + <return type="int" enum="XRInterface.TrackingStatus" /> <description> If supported, returns the status of our tracking. This will allow you to provide feedback to the user whether there are issues with positional tracking. </description> @@ -52,11 +52,17 @@ <description> Call this to initialize this interface. The first interface that is initialized is identified as the primary interface and it will be used for rendering output. After initializing the interface you want to use you then need to enable the AR/VR mode of a viewport and rendering should commence. - [b]Note:[/b] You must enable the AR/VR mode on the main viewport for any device that uses the main output of Godot, such as for mobile VR. + [b]Note:[/b] You must enable the XR mode on the main viewport for any device that uses the main output of Godot, such as for mobile VR. If you do this for a platform that handles its own output (such as OpenVR) Godot will show just one eye without distortion on screen. Alternatively, you can add a separate viewport node to your scene and enable AR/VR on that viewport. It will be used to output to the HMD, leaving you free to do anything you like in the main window, such as using a separate camera as a spectator camera or rendering something completely different. While currently not used, you can activate additional interfaces. You may wish to do this if you want to track controllers from other platforms. However, at this point in time only one interface can render to an HMD. </description> </method> + <method name="is_initialized" qualifiers="const"> + <return type="bool" /> + <description> + Is [code]true[/code] if this interface has been initialised. + </description> + </method> <method name="uninitialize"> <return type="void" /> <description> @@ -68,10 +74,7 @@ <member name="ar_is_anchor_detection_enabled" type="bool" setter="set_anchor_detection_is_enabled" getter="get_anchor_detection_is_enabled" default="false"> On an AR interface, [code]true[/code] if anchor detection is enabled. </member> - <member name="interface_is_initialized" type="bool" setter="set_is_initialized" getter="is_initialized" default="false"> - [code]true[/code] if this interface been initialized. - </member> - <member name="interface_is_primary" type="bool" setter="set_is_primary" getter="is_primary" default="false"> + <member name="interface_is_primary" type="bool" setter="set_primary" getter="is_primary" default="false"> [code]true[/code] if this is the primary interface. </member> </members> @@ -89,7 +92,7 @@ This interface supports AR (video background and real world tracking). </constant> <constant name="XR_EXTERNAL" value="8" enum="Capabilities"> - This interface outputs to an external device. If the main viewport is used, the on screen output is an unmodified buffer of either the left or right eye (stretched if the viewport size is not changed to the same aspect ratio of [method get_render_targetsize]). Using a separate viewport node frees up the main viewport for other purposes. + This interface outputs to an external device. If the main viewport is used, the on screen output is an unmodified buffer of either the left or right eye (stretched if the viewport size is not changed to the same aspect ratio of [method get_render_target_size]). Using a separate viewport node frees up the main viewport for other purposes. </constant> <constant name="EYE_MONO" value="0" enum="Eyes"> Mono output, this is mostly used internally when retrieving positioning information for our camera node or when stereo scopic rendering is not supported. @@ -100,19 +103,19 @@ <constant name="EYE_RIGHT" value="2" enum="Eyes"> Right eye output, this is mostly used internally when rendering the image for the right eye and obtaining positioning and projection information. </constant> - <constant name="XR_NORMAL_TRACKING" value="0" enum="Tracking_status"> + <constant name="XR_NORMAL_TRACKING" value="0" enum="TrackingStatus"> Tracking is behaving as expected. </constant> - <constant name="XR_EXCESSIVE_MOTION" value="1" enum="Tracking_status"> + <constant name="XR_EXCESSIVE_MOTION" value="1" enum="TrackingStatus"> Tracking is hindered by excessive motion (the player is moving faster than tracking can keep up). </constant> - <constant name="XR_INSUFFICIENT_FEATURES" value="2" enum="Tracking_status"> + <constant name="XR_INSUFFICIENT_FEATURES" value="2" enum="TrackingStatus"> Tracking is hindered by insufficient features, it's too dark (for camera-based tracking), player is blocked, etc. </constant> - <constant name="XR_UNKNOWN_TRACKING" value="3" enum="Tracking_status"> + <constant name="XR_UNKNOWN_TRACKING" value="3" enum="TrackingStatus"> We don't know the status of the tracking or this interface does not provide feedback. </constant> - <constant name="XR_NOT_TRACKING" value="4" enum="Tracking_status"> + <constant name="XR_NOT_TRACKING" value="4" enum="TrackingStatus"> Tracking is not functional (camera not plugged in or obscured, lighthouses turned off, etc.). </constant> </constants> diff --git a/doc/classes/XRInterfaceExtension.xml b/doc/classes/XRInterfaceExtension.xml new file mode 100644 index 0000000000..fb79926043 --- /dev/null +++ b/doc/classes/XRInterfaceExtension.xml @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="XRInterfaceExtension" inherits="XRInterface" version="4.0"> + <brief_description> + Base class for XR interface extensions (plugins). + </brief_description> + <description> + External XR interface plugins should inherit from this class. + </description> + <tutorials> + </tutorials> + <methods> + <method name="_commit_views" qualifiers="virtual"> + <return type="void" /> + <argument index="0" name="render_target" type="RID" /> + <argument index="1" name="screen_rect" type="Rect2" /> + <description> + </description> + </method> + <method name="_get_anchor_detection_is_enabled" qualifiers="virtual const"> + <return type="bool" /> + <description> + </description> + </method> + <method name="_get_camera_feed_id" qualifiers="virtual const"> + <return type="int" /> + <description> + </description> + </method> + <method name="_get_camera_transform" qualifiers="virtual"> + <return type="Transform3D" /> + <description> + </description> + </method> + <method name="_get_capabilities" qualifiers="virtual const"> + <return type="int" /> + <description> + </description> + </method> + <method name="_get_name" qualifiers="virtual const"> + <return type="StringName" /> + <description> + </description> + </method> + <method name="_get_projection_for_view" qualifiers="virtual"> + <return type="PackedFloat64Array" /> + <argument index="0" name="view" type="int" /> + <argument index="1" name="aspect" type="float" /> + <argument index="2" name="z_near" type="float" /> + <argument index="3" name="z_far" type="float" /> + <description> + </description> + </method> + <method name="_get_render_target_size" qualifiers="virtual"> + <return type="Vector2" /> + <description> + </description> + </method> + <method name="_get_tracking_status" qualifiers="virtual const"> + <return type="int" /> + <description> + </description> + </method> + <method name="_get_transform_for_view" qualifiers="virtual"> + <return type="Transform3D" /> + <argument index="0" name="view" type="int" /> + <argument index="1" name="cam_transform" type="Transform3D" /> + <description> + </description> + </method> + <method name="_get_view_count" qualifiers="virtual"> + <return type="int" /> + <description> + </description> + </method> + <method name="_initialize" qualifiers="virtual"> + <return type="bool" /> + <description> + </description> + </method> + <method name="_is_initialized" qualifiers="virtual const"> + <return type="bool" /> + <description> + </description> + </method> + <method name="_notification" qualifiers="virtual"> + <return type="void" /> + <argument index="0" name="what" type="int" /> + <description> + </description> + </method> + <method name="_process" qualifiers="virtual"> + <return type="void" /> + <description> + </description> + </method> + <method name="_set_anchor_detection_is_enabled" qualifiers="virtual"> + <return type="void" /> + <argument index="0" name="enabled" type="bool" /> + <description> + </description> + </method> + <method name="_uninitialize" qualifiers="virtual"> + <return type="void" /> + <description> + </description> + </method> + <method name="add_blit"> + <return type="void" /> + <argument index="0" name="render_target" type="RID" /> + <argument index="1" name="src_rect" type="Rect2" /> + <argument index="2" name="dst_rect" type="Rect2i" /> + <argument index="3" name="use_layer" type="bool" /> + <argument index="4" name="layer" type="int" /> + <argument index="5" name="apply_lens_distortion" type="bool" /> + <argument index="6" name="eye_center" type="Vector2" /> + <argument index="7" name="k1" type="float" /> + <argument index="8" name="k2" type="float" /> + <argument index="9" name="upscale" type="float" /> + <argument index="10" name="aspect_ratio" type="float" /> + <description> + Blits our render results to screen optionally applying lens distortion. This can only be called while processing [code]_commit_views[/code]. + </description> + </method> + <method name="get_render_target_texture"> + <return type="RID" /> + <argument index="0" name="render_target" type="RID" /> + <description> + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/XRServer.xml b/doc/classes/XRServer.xml index 5dd9b75ad2..85170804cc 100644 --- a/doc/classes/XRServer.xml +++ b/doc/classes/XRServer.xml @@ -37,13 +37,6 @@ You should call this method after a few seconds have passed. For instance, when the user requests a realignment of the display holding a designated button on a controller for a short period of time, or when implementing a teleport mechanism. </description> </method> - <method name="clear_primary_interface_if"> - <return type="void" /> - <argument index="0" name="interface" type="XRInterface" /> - <description> - Clears our current primary interface if it is set to the provided interface. - </description> - </method> <method name="find_interface" qualifiers="const"> <return type="XRInterface" /> <argument index="0" name="name" type="String" /> diff --git a/doc/tools/makerst.py b/doc/tools/makerst.py index 4691b61e1b..a23324fd24 100755 --- a/doc/tools/makerst.py +++ b/doc/tools/makerst.py @@ -1007,6 +1007,8 @@ def format_table(f, data, remove_empty_columns=False): # type: (TextIO, Iterabl def make_type(klass, state): # type: (str, State) -> str + if klass.find("*") != -1: # Pointer, ignore + return klass link_type = klass if link_type.endswith("[]"): # Typed array, strip [] to link to contained type. link_type = link_type[:-2] diff --git a/doc/translations/ar.po b/doc/translations/ar.po index 4199bca6c7..b06b6f013e 100644 --- a/doc/translations/ar.po +++ b/doc/translations/ar.po @@ -9896,7 +9896,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33047,8 +33047,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/ca.po b/doc/translations/ca.po index c72cba18bc..8b1b10a7db 100644 --- a/doc/translations/ca.po +++ b/doc/translations/ca.po @@ -9927,7 +9927,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33078,8 +33078,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/classes.pot b/doc/translations/classes.pot index 321e67f759..375a9e850c 100644 --- a/doc/translations/classes.pot +++ b/doc/translations/classes.pot @@ -9897,7 +9897,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33048,8 +33048,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/cs.po b/doc/translations/cs.po index 3584fc5062..d6d7025d92 100644 --- a/doc/translations/cs.po +++ b/doc/translations/cs.po @@ -10389,7 +10389,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33547,8 +33547,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/de.po b/doc/translations/de.po index 76eff809ff..83eabb14fe 100644 --- a/doc/translations/de.po +++ b/doc/translations/de.po @@ -10211,7 +10211,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33464,8 +33464,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/es.po b/doc/translations/es.po index 44b3b22597..1426b57e30 100644 --- a/doc/translations/es.po +++ b/doc/translations/es.po @@ -12986,7 +12986,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" "Convierte un valor [String] a un valor booleano, este método devolverá " @@ -44845,8 +44845,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" @@ -44878,8 +44878,8 @@ msgstr "" "usando [code]in[/code]:\n" "[codeblock]\n" "var nodo = Node2D.new()\n" -"print(\"position\" in nodo) # Imprime \"True\".\n" -"print(\"otra_propiedad\" in nodo) # Imprime \"False\".\n" +"print(\"position\" in nodo) # Imprime \"true\".\n" +"print(\"otra_propiedad\" in nodo) # Imprime \"false\".\n" "[/codeblock]\n" "El operador [code]in[/code] evaluará a [code]true[/code] siempre que la " "clave exista, incluso si el valor es [code]null[/code].\n" diff --git a/doc/translations/fa.po b/doc/translations/fa.po index 2a185fadc5..7455b828de 100644 --- a/doc/translations/fa.po +++ b/doc/translations/fa.po @@ -9902,7 +9902,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33053,8 +33053,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/fi.po b/doc/translations/fi.po index a5f470c60b..94cda2b9d9 100644 --- a/doc/translations/fi.po +++ b/doc/translations/fi.po @@ -9915,7 +9915,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33066,8 +33066,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/fr.po b/doc/translations/fr.po index 2d9b1db565..e0aef69af3 100644 --- a/doc/translations/fr.po +++ b/doc/translations/fr.po @@ -10233,7 +10233,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33414,8 +33414,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/id.po b/doc/translations/id.po index cd841fc553..fa90969628 100644 --- a/doc/translations/id.po +++ b/doc/translations/id.po @@ -9928,7 +9928,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33079,8 +33079,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/it.po b/doc/translations/it.po index aa085f6158..d8b888535f 100644 --- a/doc/translations/it.po +++ b/doc/translations/it.po @@ -10186,7 +10186,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33345,8 +33345,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/ja.po b/doc/translations/ja.po index ee900d58c2..fea270ad95 100644 --- a/doc/translations/ja.po +++ b/doc/translations/ja.po @@ -11116,7 +11116,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -34317,8 +34317,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/ko.po b/doc/translations/ko.po index 60416fb63c..0dce30d48f 100644 --- a/doc/translations/ko.po +++ b/doc/translations/ko.po @@ -9904,7 +9904,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33055,8 +33055,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/nl.po b/doc/translations/nl.po index c0dc01c653..e409bd00b4 100644 --- a/doc/translations/nl.po +++ b/doc/translations/nl.po @@ -9930,7 +9930,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33081,8 +33081,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/pl.po b/doc/translations/pl.po index 2664f263cb..06f09dcf81 100644 --- a/doc/translations/pl.po +++ b/doc/translations/pl.po @@ -9948,7 +9948,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33100,8 +33100,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/pt_BR.po b/doc/translations/pt_BR.po index f86bed9585..28ee6b7668 100644 --- a/doc/translations/pt_BR.po +++ b/doc/translations/pt_BR.po @@ -9943,7 +9943,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33094,8 +33094,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/ro.po b/doc/translations/ro.po index b25c3911cc..8cabaeebe1 100644 --- a/doc/translations/ro.po +++ b/doc/translations/ro.po @@ -9904,7 +9904,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33055,8 +33055,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/ru.po b/doc/translations/ru.po index cf5289d7f9..e515ff50ff 100644 --- a/doc/translations/ru.po +++ b/doc/translations/ru.po @@ -10409,7 +10409,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33592,8 +33592,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/sr_Cyrl.po b/doc/translations/sr_Cyrl.po index 06399d5e87..655a4a825c 100644 --- a/doc/translations/sr_Cyrl.po +++ b/doc/translations/sr_Cyrl.po @@ -9914,7 +9914,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33065,8 +33065,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/th.po b/doc/translations/th.po index cdf1c6d7e2..7f3ef3a1e2 100644 --- a/doc/translations/th.po +++ b/doc/translations/th.po @@ -9920,7 +9920,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33071,8 +33071,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/tr.po b/doc/translations/tr.po index 646a3fb5b3..d102161901 100644 --- a/doc/translations/tr.po +++ b/doc/translations/tr.po @@ -9896,7 +9896,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33047,8 +33047,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/uk.po b/doc/translations/uk.po index c2232d81ab..42daf9cd30 100644 --- a/doc/translations/uk.po +++ b/doc/translations/uk.po @@ -9982,7 +9982,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33133,8 +33133,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/zh_Hans.po b/doc/translations/zh_Hans.po index 40d1eb68bc..312fbd37c0 100644 --- a/doc/translations/zh_Hans.po +++ b/doc/translations/zh_Hans.po @@ -10129,7 +10129,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33283,8 +33283,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/doc/translations/zh_Hant.po b/doc/translations/zh_Hant.po index 13515dff67..09d52a63c7 100644 --- a/doc/translations/zh_Hant.po +++ b/doc/translations/zh_Hant.po @@ -9933,7 +9933,7 @@ msgid "" "Cast a [String] value to a boolean value, this method will return " "[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] " "for all non-empty strings.\n" -"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], " +"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], " "[code]bool(\"\")[/code] returns [code]false[/code]." msgstr "" @@ -33084,8 +33084,8 @@ msgid "" "code]:\n" "[codeblock]\n" "var n = Node2D.new()\n" -"print(\"position\" in n) # Prints \"True\".\n" -"print(\"other_property\" in n) # Prints \"False\".\n" +"print(\"position\" in n) # Prints \"true\".\n" +"print(\"other_property\" in n) # Prints \"false\".\n" "[/codeblock]\n" "The [code]in[/code] operator will evaluate to [code]true[/code] as long as " "the key exists, even if the value is [code]null[/code].\n" diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp index a2c9bae852..1754b47c85 100644 --- a/drivers/unix/dir_access_unix.cpp +++ b/drivers/unix/dir_access_unix.cpp @@ -71,7 +71,7 @@ Error DirAccessUnix::list_dir_begin() { bool DirAccessUnix::file_exists(String p_file) { GLOBAL_LOCK_FUNCTION - if (p_file.is_rel_path()) { + if (p_file.is_relative_path()) { p_file = current_dir.plus_file(p_file); } @@ -90,7 +90,7 @@ bool DirAccessUnix::file_exists(String p_file) { bool DirAccessUnix::dir_exists(String p_dir) { GLOBAL_LOCK_FUNCTION - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { p_dir = get_current_dir().plus_file(p_dir); } @@ -105,7 +105,7 @@ bool DirAccessUnix::dir_exists(String p_dir) { bool DirAccessUnix::is_readable(String p_dir) { GLOBAL_LOCK_FUNCTION - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { p_dir = get_current_dir().plus_file(p_dir); } @@ -116,7 +116,7 @@ bool DirAccessUnix::is_readable(String p_dir) { bool DirAccessUnix::is_writable(String p_dir) { GLOBAL_LOCK_FUNCTION - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { p_dir = get_current_dir().plus_file(p_dir); } @@ -125,7 +125,7 @@ bool DirAccessUnix::is_writable(String p_dir) { } uint64_t DirAccessUnix::get_modified_time(String p_file) { - if (p_file.is_rel_path()) { + if (p_file.is_relative_path()) { p_file = current_dir.plus_file(p_file); } @@ -293,7 +293,7 @@ bool DirAccessUnix::drives_are_shortcuts() { Error DirAccessUnix::make_dir(String p_dir) { GLOBAL_LOCK_FUNCTION - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { p_dir = get_current_dir().plus_file(p_dir); } @@ -328,7 +328,7 @@ Error DirAccessUnix::change_dir(String p_dir) { // try_dir is the directory we are trying to change into String try_dir = ""; - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { String next_dir = current_dir.plus_file(p_dir); next_dir = next_dir.simplify_path(); try_dir = next_dir; @@ -372,13 +372,13 @@ String DirAccessUnix::get_current_dir(bool p_include_drive) { } Error DirAccessUnix::rename(String p_path, String p_new_path) { - if (p_path.is_rel_path()) { + if (p_path.is_relative_path()) { p_path = get_current_dir().plus_file(p_path); } p_path = fix_path(p_path); - if (p_new_path.is_rel_path()) { + if (p_new_path.is_relative_path()) { p_new_path = get_current_dir().plus_file(p_new_path); } @@ -388,7 +388,7 @@ Error DirAccessUnix::rename(String p_path, String p_new_path) { } Error DirAccessUnix::remove(String p_path) { - if (p_path.is_rel_path()) { + if (p_path.is_relative_path()) { p_path = get_current_dir().plus_file(p_path); } @@ -407,7 +407,7 @@ Error DirAccessUnix::remove(String p_path) { } bool DirAccessUnix::is_link(String p_file) { - if (p_file.is_rel_path()) { + if (p_file.is_relative_path()) { p_file = get_current_dir().plus_file(p_file); } @@ -422,7 +422,7 @@ bool DirAccessUnix::is_link(String p_file) { } String DirAccessUnix::read_link(String p_file) { - if (p_file.is_rel_path()) { + if (p_file.is_relative_path()) { p_file = get_current_dir().plus_file(p_file); } @@ -439,7 +439,7 @@ String DirAccessUnix::read_link(String p_file) { } Error DirAccessUnix::create_link(String p_source, String p_target) { - if (p_target.is_rel_path()) { + if (p_target.is_relative_path()) { p_target = get_current_dir().plus_file(p_target); } diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index f6a3e93b55..3032c31629 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -392,7 +392,7 @@ String OS_Unix::get_locale() const { Error OS_Unix::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { String path = p_path; - if (FileAccess::exists(path) && path.is_rel_path()) { + if (FileAccess::exists(path) && path.is_relative_path()) { // dlopen expects a slash, in this case a leading ./ for it to be interpreted as a relative path, // otherwise it will end up searching various system directories for the lib instead and finally failing. path = "./" + path; diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index 63510d261a..a4324f0a2c 100644 --- a/drivers/vulkan/rendering_device_vulkan.cpp +++ b/drivers/vulkan/rendering_device_vulkan.cpp @@ -2740,6 +2740,14 @@ bool RenderingDeviceVulkan::texture_is_valid(RID p_texture) { return texture_owner.owns(p_texture); } +Size2i RenderingDeviceVulkan::texture_size(RID p_texture) { + _THREAD_SAFE_METHOD_ + + Texture *tex = texture_owner.getornull(p_texture); + ERR_FAIL_COND_V(!tex, Size2i()); + return Size2i(tex->width, tex->height); +} + Error RenderingDeviceVulkan::texture_copy(RID p_from_texture, RID p_to_texture, const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_size, uint32_t p_src_mipmap, uint32_t p_dst_mipmap, uint32_t p_src_layer, uint32_t p_dst_layer, uint32_t p_post_barrier) { _THREAD_SAFE_METHOD_ @@ -7580,7 +7588,7 @@ Error RenderingDeviceVulkan::_draw_list_allocate(const Rect2i &p_viewport, uint3 VkCommandPoolCreateInfo cmd_pool_info; cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cmd_pool_info.pNext = nullptr; - cmd_pool_info.queueFamilyIndex = context->get_graphics_queue(); + cmd_pool_info.queueFamilyIndex = context->get_graphics_queue_family_index(); cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; VkResult res = vkCreateCommandPool(device, &cmd_pool_info, nullptr, &split_draw_list_allocators.write[i].command_pool); @@ -7957,13 +7965,13 @@ void RenderingDeviceVulkan::compute_list_bind_uniform_set(ComputeListID p_list, textures_to_storage[i]->used_in_compute = false; textures_to_storage[i]->used_in_raster = false; - textures_to_storage[i]->used_in_compute = false; + textures_to_storage[i]->used_in_transfer = false; } else { src_access_flags = 0; textures_to_storage[i]->used_in_compute = false; textures_to_storage[i]->used_in_raster = false; - textures_to_storage[i]->used_in_compute = false; + textures_to_storage[i]->used_in_transfer = false; textures_to_storage[i]->used_in_frame = frames_drawn; } @@ -8838,7 +8846,7 @@ void RenderingDeviceVulkan::initialize(VulkanContext *p_context, bool p_local_de VkCommandPoolCreateInfo cmd_pool_info; cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cmd_pool_info.pNext = nullptr; - cmd_pool_info.queueFamilyIndex = p_context->get_graphics_queue(); + cmd_pool_info.queueFamilyIndex = p_context->get_graphics_queue_family_index(); cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; VkResult res = vkCreateCommandPool(device, &cmd_pool_info, nullptr, &frames[i].command_pool); @@ -9008,6 +9016,92 @@ void RenderingDeviceVulkan::capture_timestamp(const String &p_name) { frames[frame].timestamp_count++; } +uint64_t RenderingDeviceVulkan::get_driver_resource(DriverResource p_resource, RID p_rid, uint64_t p_index) { + _THREAD_SAFE_METHOD_ + + switch (p_resource) { + case DRIVER_RESOURCE_VULKAN_DEVICE: { + return (uint64_t)context->get_device(); + }; break; + case DRIVER_RESOURCE_VULKAN_PHYSICAL_DEVICE: { + return (uint64_t)context->get_physical_device(); + }; break; + case DRIVER_RESOURCE_VULKAN_INSTANCE: { + return (uint64_t)context->get_instance(); + }; break; + case DRIVER_RESOURCE_VULKAN_QUEUE: { + return (uint64_t)context->get_graphics_queue(); + }; break; + case DRIVER_RESOURCE_VULKAN_QUEUE_FAMILY_INDEX: { + return context->get_graphics_queue_family_index(); + }; break; + case DRIVER_RESOURCE_VULKAN_IMAGE: { + Texture *tex = texture_owner.getornull(p_rid); + ERR_FAIL_NULL_V(tex, 0); + + return (uint64_t)tex->image; + }; break; + case DRIVER_RESOURCE_VULKAN_IMAGE_VIEW: { + Texture *tex = texture_owner.getornull(p_rid); + ERR_FAIL_NULL_V(tex, 0); + + return (uint64_t)tex->view; + }; break; + case DRIVER_RESOURCE_VULKAN_IMAGE_NATIVE_TEXTURE_FORMAT: { + Texture *tex = texture_owner.getornull(p_rid); + ERR_FAIL_NULL_V(tex, 0); + + return vulkan_formats[tex->format]; + }; break; + case DRIVER_RESOURCE_VULKAN_SAMPLER: { + VkSampler *sampler = sampler_owner.getornull(p_rid); + ERR_FAIL_NULL_V(sampler, 0); + + return uint64_t(*sampler); + }; break; + case DRIVER_RESOURCE_VULKAN_DESCRIPTOR_SET: { + UniformSet *uniform_set = uniform_set_owner.getornull(p_rid); + ERR_FAIL_NULL_V(uniform_set, 0); + + return uint64_t(uniform_set->descriptor_set); + }; break; + case DRIVER_RESOURCE_VULKAN_BUFFER: { + Buffer *buffer = nullptr; + if (vertex_buffer_owner.owns(p_rid)) { + buffer = vertex_buffer_owner.getornull(p_rid); + } else if (index_buffer_owner.owns(p_rid)) { + buffer = index_buffer_owner.getornull(p_rid); + } else if (uniform_buffer_owner.owns(p_rid)) { + buffer = uniform_buffer_owner.getornull(p_rid); + } else if (texture_buffer_owner.owns(p_rid)) { + buffer = &texture_buffer_owner.getornull(p_rid)->buffer; + } else if (storage_buffer_owner.owns(p_rid)) { + buffer = storage_buffer_owner.getornull(p_rid); + } + + ERR_FAIL_NULL_V(buffer, 0); + + return uint64_t(buffer->buffer); + }; break; + case DRIVER_RESOURCE_VULKAN_COMPUTE_PIPELINE: { + ComputePipeline *compute_pipeline = compute_pipeline_owner.getornull(p_rid); + ERR_FAIL_NULL_V(compute_pipeline, 0); + + return uint64_t(compute_pipeline->pipeline); + }; break; + case DRIVER_RESOURCE_VULKAN_RENDER_PIPELINE: { + RenderPipeline *render_pipeline = render_pipeline_owner.getornull(p_rid); + ERR_FAIL_NULL_V(render_pipeline, 0); + + return uint64_t(render_pipeline->pipeline); + }; break; + default: { + // not supported for this driver + return 0; + }; break; + } +} + uint32_t RenderingDeviceVulkan::get_captured_timestamps_count() const { return frames[frame].timestamp_result_count; } diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h index 5ee2ca07f2..cf0b725cfc 100644 --- a/drivers/vulkan/rendering_device_vulkan.h +++ b/drivers/vulkan/rendering_device_vulkan.h @@ -813,7 +813,7 @@ class RenderingDeviceVulkan : public RenderingDevice { // When using split command lists, this is // implemented internally using secondary command // buffers. As they can be created in threads, - // each needs it's own command pool. + // each needs its own command pool. struct SplitDrawListAllocator { VkCommandPool command_pool = VK_NULL_HANDLE; @@ -1044,6 +1044,7 @@ public: virtual bool texture_is_format_supported_for_usage(DataFormat p_format, uint32_t p_usage) const; virtual bool texture_is_shared(RID p_texture); virtual bool texture_is_valid(RID p_texture); + virtual Size2i texture_size(RID p_texture); virtual Error texture_copy(RID p_from_texture, RID p_to_texture, const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_size, uint32_t p_src_mipmap, uint32_t p_dst_mipmap, uint32_t p_src_layer, uint32_t p_dst_layer, uint32_t p_post_barrier = BARRIER_MASK_ALL); virtual Error texture_clear(RID p_texture, const Color &p_color, uint32_t p_base_mipmap, uint32_t p_mipmaps, uint32_t p_base_layer, uint32_t p_layers, uint32_t p_post_barrier = BARRIER_MASK_ALL); @@ -1226,6 +1227,8 @@ public: virtual String get_device_name() const; virtual String get_device_pipeline_cache_uuid() const; + virtual uint64_t get_driver_resource(DriverResource p_resource, RID p_rid = RID(), uint64_t p_index = 0); + RenderingDeviceVulkan(); ~RenderingDeviceVulkan(); }; diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 625c222b67..c14e3f0e93 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -2028,7 +2028,11 @@ int VulkanContext::get_swapchain_image_count() const { return swapchainImageCount; } -uint32_t VulkanContext::get_graphics_queue() const { +VkQueue VulkanContext::get_graphics_queue() const { + return graphics_queue; +} + +uint32_t VulkanContext::get_graphics_queue_family_index() const { return graphics_queue_family_index; } diff --git a/drivers/vulkan/vulkan_context.h b/drivers/vulkan/vulkan_context.h index fe09d4c497..19ea806616 100644 --- a/drivers/vulkan/vulkan_context.h +++ b/drivers/vulkan/vulkan_context.h @@ -243,7 +243,8 @@ public: VkPhysicalDevice get_physical_device(); VkInstance get_instance() { return inst; } int get_swapchain_image_count() const; - uint32_t get_graphics_queue() const; + VkQueue get_graphics_queue() const; + uint32_t get_graphics_queue_family_index() const; void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); int window_get_width(DisplayServer::WindowID p_window = 0); diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp index 43c8722b06..0b5cfceadc 100644 --- a/drivers/wasapi/audio_driver_wasapi.cpp +++ b/drivers/wasapi/audio_driver_wasapi.cpp @@ -35,8 +35,60 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +#include <stdint.h> // INT32_MAX + #include <functiondiscoverykeys.h> +// Define IAudioClient3 if not already defined by MinGW headers +#if defined __MINGW32__ || defined __MINGW64__ + +#ifndef __IAudioClient3_FWD_DEFINED__ +#define __IAudioClient3_FWD_DEFINED__ + +typedef interface IAudioClient3 IAudioClient3; + +#endif // __IAudioClient3_FWD_DEFINED__ + +#ifndef __IAudioClient3_INTERFACE_DEFINED__ +#define __IAudioClient3_INTERFACE_DEFINED__ + +MIDL_INTERFACE("7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42") +IAudioClient3 : public IAudioClient2 { +public: + virtual HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod( + /* [annotation][in] */ + _In_ const WAVEFORMATEX *pFormat, + /* [annotation][out] */ + _Out_ UINT32 *pDefaultPeriodInFrames, + /* [annotation][out] */ + _Out_ UINT32 *pFundamentalPeriodInFrames, + /* [annotation][out] */ + _Out_ UINT32 *pMinPeriodInFrames, + /* [annotation][out] */ + _Out_ UINT32 *pMaxPeriodInFrames) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod( + /* [unique][annotation][out] */ + _Out_ WAVEFORMATEX * *ppFormat, + /* [annotation][out] */ + _Out_ UINT32 * pCurrentPeriodInFrames) = 0; + + virtual HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream( + /* [annotation][in] */ + _In_ DWORD StreamFlags, + /* [annotation][in] */ + _In_ UINT32 PeriodInFrames, + /* [annotation][in] */ + _In_ const WAVEFORMATEX *pFormat, + /* [annotation][in] */ + _In_opt_ LPCGUID AudioSessionGuid) = 0; +}; +__CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B, 0x7A, 0x59, 0x87, 0xAD, 0x42) + +#endif // __IAudioClient3_INTERFACE_DEFINED__ + +#endif // __MINGW32__ || __MINGW64__ + #ifndef PKEY_Device_FriendlyName #undef DEFINE_PROPERTYKEY @@ -51,6 +103,7 @@ DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0 const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); const IID IID_IAudioClient = __uuidof(IAudioClient); +const IID IID_IAudioClient3 = __uuidof(IAudioClient3); const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient); const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); @@ -221,7 +274,22 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c ERR_PRINT("WASAPI: RegisterEndpointNotificationCallback error"); } - hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client); + bool using_audio_client_3 = !p_capture; // IID_IAudioClient3 is only used for adjustable output latency (not input) + if (using_audio_client_3) { + hr = device->Activate(IID_IAudioClient3, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client); + if (hr != S_OK) { + // IID_IAudioClient3 will never activate on OS versions before Windows 10. + // Older Windows versions should fall back gracefully. + using_audio_client_3 = false; + print_verbose("WASAPI: Couldn't activate device with IAudioClient3 interface, falling back to IAudioClient interface"); + } else { + print_verbose("WASAPI: Activated device using IAudioClient3 interface"); + } + } + if (!using_audio_client_3) { + hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client); + } + SAFE_RELEASE(device) if (reinit) { @@ -232,6 +300,16 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN); } + if (using_audio_client_3) { + AudioClientProperties audioProps; + audioProps.cbSize = sizeof(AudioClientProperties); + audioProps.bIsOffload = FALSE; + audioProps.eCategory = AudioCategory_GameEffects; + + hr = ((IAudioClient3 *)p_device->audio_client)->SetClientProperties(&audioProps); + ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: SetClientProperties failed with error 0x" + String::num_uint64(hr, 16) + "."); + } + hr = p_device->audio_client->GetMixFormat(&pwfex); ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN); @@ -285,15 +363,55 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c } } - DWORD streamflags = 0; - if ((DWORD)mix_rate != pwfex->nSamplesPerSec) { - streamflags |= AUDCLNT_STREAMFLAGS_RATEADJUST; - pwfex->nSamplesPerSec = mix_rate; - pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nChannels * (pwfex->wBitsPerSample / 8); - } + if (!using_audio_client_3) { + DWORD streamflags = 0; + if ((DWORD)mix_rate != pwfex->nSamplesPerSec) { + streamflags |= AUDCLNT_STREAMFLAGS_RATEADJUST; + pwfex->nSamplesPerSec = mix_rate; + pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nChannels * (pwfex->wBitsPerSample / 8); + } + hr = p_device->audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, streamflags, p_capture ? REFTIMES_PER_SEC : 0, 0, pwfex, nullptr); + ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + "."); + UINT32 max_frames; + HRESULT hr = p_device->audio_client->GetBufferSize(&max_frames); + ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN); - hr = p_device->audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, streamflags, p_capture ? REFTIMES_PER_SEC : 0, 0, pwfex, nullptr); - ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + "."); + // Due to WASAPI Shared Mode we have no control of the buffer size + buffer_frames = max_frames; + } else { + IAudioClient3 *device_audio_client_3 = (IAudioClient3 *)p_device->audio_client; + + // AUDCLNT_STREAMFLAGS_RATEADJUST is an invalid flag with IAudioClient3, therefore we have to use + // the closest supported mix rate supported by the audio driver. + mix_rate = pwfex->nSamplesPerSec; + print_verbose("WASAPI: mix_rate = " + itos(mix_rate)); + + UINT32 default_period_frames, fundamental_period_frames, min_period_frames, max_period_frames; + hr = device_audio_client_3->GetSharedModeEnginePeriod( + pwfex, + &default_period_frames, + &fundamental_period_frames, + &min_period_frames, + &max_period_frames); + ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: GetSharedModeEnginePeriod failed with error 0x" + String::num_uint64(hr, 16) + "."); + + // Period frames must be an integral multiple of fundamental_period_frames or IAudioClient3 initialization will fail, + // so we need to select the closest multiple to the user-specified latency. + UINT32 desired_period_frames = target_latency_ms * mix_rate / 1000; + UINT32 period_frames = (desired_period_frames / fundamental_period_frames) * fundamental_period_frames; + if (ABS((int64_t)period_frames - (int64_t)desired_period_frames) > ABS((int64_t)(period_frames + fundamental_period_frames) - (int64_t)desired_period_frames)) { + period_frames = period_frames + fundamental_period_frames; + } + period_frames = CLAMP(period_frames, min_period_frames, max_period_frames); + print_verbose("WASAPI: fundamental_period_frames = " + itos(fundamental_period_frames)); + print_verbose("WASAPI: min_period_frames = " + itos(min_period_frames)); + print_verbose("WASAPI: max_period_frames = " + itos(max_period_frames)); + print_verbose("WASAPI: selected a period frame size of " + itos(period_frames)); + buffer_frames = period_frames; + + hr = device_audio_client_3->InitializeSharedAudioStream(0, period_frames, pwfex, nullptr); + ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: InitializeSharedAudioStream failed with error 0x" + String::num_uint64(hr, 16) + "."); + } if (p_capture) { hr = p_device->audio_client->GetService(IID_IAudioCaptureClient, (void **)&p_device->capture_client); @@ -328,13 +446,6 @@ Error AudioDriverWASAPI::init_render_device(bool reinit) { break; } - UINT32 max_frames; - HRESULT hr = audio_output.audio_client->GetBufferSize(&max_frames); - ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN); - - // Due to WASAPI Shared Mode we have no control of the buffer size - buffer_frames = max_frames; - // Sample rate is independent of channels (ref: https://stackoverflow.com/questions/11048825/audio-sample-frequency-rely-on-channels) samples_in.resize(buffer_frames * channels); @@ -367,7 +478,6 @@ Error AudioDriverWASAPI::audio_device_finish(AudioDeviceWASAPI *p_device) { if (p_device->audio_client) { p_device->audio_client->Stop(); } - p_device->active = false; } @@ -389,6 +499,8 @@ Error AudioDriverWASAPI::finish_capture_device() { Error AudioDriverWASAPI::init() { mix_rate = GLOBAL_GET("audio/driver/mix_rate"); + target_latency_ms = GLOBAL_GET("audio/output_latency"); + Error err = init_render_device(); if (err != OK) { ERR_PRINT("WASAPI: init_render_device error"); diff --git a/drivers/wasapi/audio_driver_wasapi.h b/drivers/wasapi/audio_driver_wasapi.h index b9b325f0fb..312b6a6781 100644 --- a/drivers/wasapi/audio_driver_wasapi.h +++ b/drivers/wasapi/audio_driver_wasapi.h @@ -71,6 +71,7 @@ class AudioDriverWASAPI : public AudioDriver { unsigned int channels = 0; int mix_rate = 0; int buffer_frames = 0; + int target_latency_ms = 0; bool thread_exited = false; mutable bool exit_thread = false; @@ -114,5 +115,5 @@ public: AudioDriverWASAPI(); }; +#endif // WASAPI_ENABLED #endif // AUDIO_DRIVER_WASAPI_H -#endif diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp index 325bae5b56..f7a5f7279e 100644 --- a/drivers/windows/dir_access_windows.cpp +++ b/drivers/windows/dir_access_windows.cpp @@ -155,7 +155,7 @@ Error DirAccessWindows::make_dir(String p_dir) { GLOBAL_LOCK_FUNCTION p_dir = fix_path(p_dir); - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { p_dir = current_dir.plus_file(p_dir); } @@ -227,7 +227,7 @@ bool DirAccessWindows::file_exists(String p_file) { bool DirAccessWindows::dir_exists(String p_dir) { GLOBAL_LOCK_FUNCTION - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { p_dir = get_current_dir().plus_file(p_dir); } @@ -242,13 +242,13 @@ bool DirAccessWindows::dir_exists(String p_dir) { } Error DirAccessWindows::rename(String p_path, String p_new_path) { - if (p_path.is_rel_path()) { + if (p_path.is_relative_path()) { p_path = get_current_dir().plus_file(p_path); } p_path = fix_path(p_path); - if (p_new_path.is_rel_path()) { + if (p_new_path.is_relative_path()) { p_new_path = get_current_dir().plus_file(p_new_path); } @@ -281,7 +281,7 @@ Error DirAccessWindows::rename(String p_path, String p_new_path) { } Error DirAccessWindows::remove(String p_path) { - if (p_path.is_rel_path()) { + if (p_path.is_relative_path()) { p_path = get_current_dir().plus_file(p_path); } diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index ea2ae53e82..fca69f34f3 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -217,6 +217,8 @@ void AnimationBezierTrackEdit::_draw_line_clipped(const Vector2 &p_from, const V void AnimationBezierTrackEdit::_notification(int p_what) { if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { + close_button->set_icon(get_theme_icon(SNAME("Close"), SNAME("EditorIcons"))); + bezier_icon = get_theme_icon(SNAME("KeyBezierPoint"), SNAME("EditorIcons")); bezier_handle_icon = get_theme_icon(SNAME("KeyBezierHandle"), SNAME("EditorIcons")); selected_icon = get_theme_icon(SNAME("KeyBezierSelected"), SNAME("EditorIcons")); @@ -231,8 +233,8 @@ void AnimationBezierTrackEdit::_notification(int p_what) { int hsep = get_theme_constant(SNAME("hseparation"), SNAME("ItemList")); int vsep = get_theme_constant(SNAME("vseparation"), SNAME("ItemList")); - handle_mode_option->set_position(Vector2(right_limit + hsep, get_size().height - handle_mode_option->get_combined_minimum_size().height - vsep)); - handle_mode_option->set_size(Vector2(timeline->get_buttons_width() - hsep * 2, handle_mode_option->get_combined_minimum_size().height)); + right_column->set_position(Vector2(right_limit + hsep, vsep)); + right_column->set_size(Vector2(timeline->get_buttons_width() - hsep * 2, get_size().y - vsep * 2)); } if (p_what == NOTIFICATION_DRAW) { if (animation.is_null()) { @@ -261,12 +263,6 @@ void AnimationBezierTrackEdit::_notification(int p_what) { draw_line(Point2(right_limit, 0), Point2(right_limit, get_size().height), linecolor, Math::round(EDSCALE)); - Ref<Texture2D> close_icon = get_theme_icon(SNAME("Close"), SNAME("EditorIcons")); - - close_icon_rect.position = Vector2(get_size().width - close_icon->get_width() - hsep, hsep); - close_icon_rect.size = close_icon->get_size(); - draw_texture(close_icon, close_icon_rect.position); - String base_path = animation->track_get_path(track); int end = base_path.find(":"); if (end != -1) { @@ -606,7 +602,7 @@ void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int update(); } -void AnimationBezierTrackEdit::_gui_input(const Ref<InputEvent> &p_event) { +void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (p_event->is_pressed()) { @@ -1126,13 +1122,7 @@ void AnimationBezierTrackEdit::delete_selection() { } } -void AnimationBezierTrackEdit::set_block_animation_update_ptr(bool *p_block_ptr) { - block_animation_update_ptr = p_block_ptr; -} - void AnimationBezierTrackEdit::_bind_methods() { - ClassDB::bind_method("_gui_input", &AnimationBezierTrackEdit::_gui_input); - ClassDB::bind_method("_clear_selection", &AnimationBezierTrackEdit::_clear_selection); ClassDB::bind_method("_clear_selection_for_anim", &AnimationBezierTrackEdit::_clear_selection_for_anim); ClassDB::bind_method("_select_at_anim", &AnimationBezierTrackEdit::_select_at_anim); @@ -1152,21 +1142,6 @@ void AnimationBezierTrackEdit::_bind_methods() { } AnimationBezierTrackEdit::AnimationBezierTrackEdit() { - undo_redo = nullptr; - timeline = nullptr; - root = nullptr; - menu = nullptr; - block_animation_update_ptr = nullptr; - - moving_selection_attempt = false; - moving_selection = false; - select_single_attempt = -1; - box_selecting = false; - box_selecting_attempt = false; - - moving_handle = 0; - - play_position_pos = 0; play_position = memnew(Control); play_position->set_mouse_filter(MOUSE_FILTER_PASS); add_child(play_position); @@ -1174,18 +1149,21 @@ AnimationBezierTrackEdit::AnimationBezierTrackEdit() { play_position->connect("draw", callable_mp(this, &AnimationBezierTrackEdit::_play_position_draw)); set_focus_mode(FOCUS_CLICK); - v_scroll = 0; - v_zoom = 1; - - panning_timeline = false; set_clip_contents(true); handle_mode = HANDLE_MODE_FREE; handle_mode_option = memnew(OptionButton); - add_child(handle_mode_option); + + close_button = memnew(Button); + close_button->connect("pressed", Callable(this, SNAME("emit_signal")), varray(SNAME("close_request"))); + close_button->set_text(TTR("Close")); + + right_column = memnew(VBoxContainer); + right_column->add_child(close_button); + right_column->add_spacer(); + right_column->add_child(handle_mode_option); + add_child(right_column); menu = memnew(PopupMenu); add_child(menu); menu->connect("id_pressed", callable_mp(this, &AnimationBezierTrackEdit::_menu_selected)); - - //set_mouse_filter(MOUSE_FILTER_PASS); //scroll has to work too for selection } diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h index b082cae3ea..578c6f9337 100644 --- a/editor/animation_bezier_editor.h +++ b/editor/animation_bezier_editor.h @@ -51,11 +51,14 @@ class AnimationBezierTrackEdit : public Control { HandleMode handle_mode; OptionButton *handle_mode_option; - AnimationTimelineEdit *timeline; - UndoRedo *undo_redo; - Node *root; + VBoxContainer *right_column; + Button *close_button; + + AnimationTimelineEdit *timeline = nullptr; + UndoRedo *undo_redo = nullptr; + Node *root = nullptr; Control *play_position; //separate control used to draw so updates for only position changed are much faster - float play_position_pos; + float play_position_pos = 0; Ref<Animation> animation; int track; @@ -70,37 +73,35 @@ class AnimationBezierTrackEdit : public Control { Map<int, Rect2> subtracks; - float v_scroll; - float v_zoom; + float v_scroll = 0; + float v_zoom = 1; - PopupMenu *menu; + PopupMenu *menu = nullptr; void _zoom_changed(); - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _menu_selected(int p_index); - bool *block_animation_update_ptr; //used to block all tracks re-gen (speed up) - void _play_position_draw(); Vector2 insert_at_pos; - bool moving_selection_attempt; - int select_single_attempt; - bool moving_selection; + bool moving_selection_attempt = false; + int select_single_attempt = -1; + bool moving_selection = false; int moving_selection_from_key; Vector2 moving_selection_offset; - bool box_selecting_attempt; - bool box_selecting; - bool box_selecting_add; + bool box_selecting_attempt = false; + bool box_selecting = false; + bool box_selecting_add = false; Vector2 box_selection_from; Vector2 box_selection_to; - int moving_handle; //0 no move -1 or +1 out - int moving_handle_key; + int moving_handle = 0; //0 no move -1 or +1 out + int moving_handle_key = 0; Vector2 moving_handle_left; Vector2 moving_handle_right; @@ -129,7 +130,7 @@ class AnimationBezierTrackEdit : public Control { Set<int> selection; - bool panning_timeline; + bool panning_timeline = false; float panning_timeline_from; float panning_timeline_at; @@ -155,8 +156,6 @@ public: void set_editor(AnimationTrackEditor *p_editor); void set_root(Node *p_root); - void set_block_animation_update_ptr(bool *p_block_ptr); - void set_play_position(float p_pos); void update_play_position(); diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index ff2818f027..324237ff82 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -1642,7 +1642,7 @@ void AnimationTimelineEdit::_play_position_draw() { } } -void AnimationTimelineEdit::_gui_input(const Ref<InputEvent> &p_event) { +void AnimationTimelineEdit::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); const Ref<InputEventMouseButton> mb = p_event; @@ -1754,8 +1754,6 @@ void AnimationTimelineEdit::_track_added(int p_track) { } void AnimationTimelineEdit::_bind_methods() { - ClassDB::bind_method("_gui_input", &AnimationTimelineEdit::_gui_input); - ADD_SIGNAL(MethodInfo("zoom_changed")); ADD_SIGNAL(MethodInfo("name_limit_changed")); ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag"))); @@ -2551,7 +2549,7 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { return Control::get_tooltip(p_pos); } -void AnimationTrackEdit::_gui_input(const Ref<InputEvent> &p_event) { +void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (p_event->is_pressed()) { @@ -2965,8 +2963,6 @@ void AnimationTrackEdit::append_to_selection(const Rect2 &p_box, bool p_deselect } void AnimationTrackEdit::_bind_methods() { - ClassDB::bind_method("_gui_input", &AnimationTrackEdit::_gui_input); - ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag"))); ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track"))); ADD_SIGNAL(MethodInfo("dropped", PropertyInfo(Variant::INT, "from_track"), PropertyInfo(Variant::INT, "to_track"))); @@ -5761,7 +5757,7 @@ void AnimationTrackEditor::_pick_track_filter_input(const Ref<InputEvent> &p_ie) case KEY_DOWN: case KEY_PAGEUP: case KEY_PAGEDOWN: { - pick_track->get_scene_tree()->get_scene_tree()->call("_gui_input", k); + pick_track->get_scene_tree()->get_scene_tree()->gui_input(k); pick_track->get_filter_line_edit()->accept_event(); } break; default: diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 6d977e5a3f..4da708dd1c 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -89,7 +89,7 @@ class AnimationTimelineEdit : public Range { float dragging_hsize_from; float dragging_hsize_at; - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _track_added(int p_track); protected: @@ -195,7 +195,7 @@ protected: static void _bind_methods(); void _notification(int p_what); - virtual void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; public: virtual Variant get_drag_data(const Point2 &p_point) override; diff --git a/editor/animation_track_editor_plugins.cpp b/editor/animation_track_editor_plugins.cpp index 0caed1e8e3..4ee8b991e4 100644 --- a/editor/animation_track_editor_plugins.cpp +++ b/editor/animation_track_editor_plugins.cpp @@ -1035,7 +1035,7 @@ void AnimationTrackEditTypeAudio::drop_data(const Point2 &p_point, const Variant AnimationTrackEdit::drop_data(p_point, p_data); } -void AnimationTrackEditTypeAudio::_gui_input(const Ref<InputEvent> &p_event) { +void AnimationTrackEditTypeAudio::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> mm = p_event; @@ -1132,7 +1132,7 @@ void AnimationTrackEditTypeAudio::_gui_input(const Ref<InputEvent> &p_event) { return; } - AnimationTrackEdit::_gui_input(p_event); + AnimationTrackEdit::gui_input(p_event); } //////////////////// diff --git a/editor/animation_track_editor_plugins.h b/editor/animation_track_editor_plugins.h index 66229c3012..a362422c2b 100644 --- a/editor/animation_track_editor_plugins.h +++ b/editor/animation_track_editor_plugins.h @@ -124,7 +124,7 @@ protected: static void _bind_methods(); public: - virtual void _gui_input(const Ref<InputEvent> &p_event) override; + virtual void gui_input(const Ref<InputEvent> &p_event) override; virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override; virtual void drop_data(const Point2 &p_point, const Variant &p_data) override; diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 39095c42a4..89c2e49814 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -114,7 +114,7 @@ void FindReplaceBar::_notification(int p_what) { } } -void FindReplaceBar::_unhandled_input(const Ref<InputEvent> &p_event) { +void FindReplaceBar::unhandled_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventKey> k = p_event; @@ -611,7 +611,6 @@ void FindReplaceBar::set_text_edit(CodeTextEditor *p_text_editor) { } void FindReplaceBar::_bind_methods() { - ClassDB::bind_method("_unhandled_input", &FindReplaceBar::_unhandled_input); ClassDB::bind_method("_search_current", &FindReplaceBar::search_current); ADD_SIGNAL(MethodInfo("search")); @@ -712,7 +711,7 @@ FindReplaceBar::FindReplaceBar() { // This function should be used to handle shortcuts that could otherwise // be handled too late if they weren't handled here. -void CodeTextEditor::_input(const Ref<InputEvent> &event) { +void CodeTextEditor::input(const Ref<InputEvent> &event) { ERR_FAIL_COND(event.is_null()); const Ref<InputEventKey> key_event = event; @@ -1753,8 +1752,6 @@ void CodeTextEditor::remove_all_bookmarks() { } void CodeTextEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_input"), &CodeTextEditor::_input); - ADD_SIGNAL(MethodInfo("validate_script")); ADD_SIGNAL(MethodInfo("load_theme_settings")); ADD_SIGNAL(MethodInfo("show_errors_panel")); diff --git a/editor/code_editor.h b/editor/code_editor.h index ee8f4366dd..dfe6561f13 100644 --- a/editor/code_editor.h +++ b/editor/code_editor.h @@ -105,7 +105,7 @@ class FindReplaceBar : public HBoxContainer { protected: void _notification(int p_what); - void _unhandled_input(const Ref<InputEvent> &p_event); + virtual void unhandled_input(const Ref<InputEvent> &p_event) override; bool _search(uint32_t p_flags, int p_from_line, int p_from_col); @@ -173,7 +173,7 @@ class CodeTextEditor : public VBoxContainer { void _font_resize_timeout(); bool _add_font_size(int p_delta); - void _input(const Ref<InputEvent> &event); + virtual void input(const Ref<InputEvent> &event) override; void _text_editor_gui_input(const Ref<InputEvent> &p_event); void _zoom_in(); void _zoom_out(); diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index eeab0fc2f5..f0b27702e7 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -356,7 +356,7 @@ void CreateDialog::_sbox_input(const Ref<InputEvent> &p_ie) { case KEY_DOWN: case KEY_PAGEUP: case KEY_PAGEDOWN: { - search_options->call("_gui_input", k); + search_options->gui_input(k); search_box->accept_event(); } break; default: diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp index 945291b163..fbd3aaa409 100644 --- a/editor/debugger/debug_adapter/debug_adapter_parser.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp @@ -33,12 +33,15 @@ #include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/script_editor_debugger.h" #include "editor/editor_node.h" +#include "editor/editor_run_native.h" void DebugAdapterParser::_bind_methods() { // Requests ClassDB::bind_method(D_METHOD("req_initialize", "params"), &DebugAdapterParser::req_initialize); - ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::prepare_success_response); + ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::req_disconnect); ClassDB::bind_method(D_METHOD("req_launch", "params"), &DebugAdapterParser::req_launch); + ClassDB::bind_method(D_METHOD("req_attach", "params"), &DebugAdapterParser::req_attach); + ClassDB::bind_method(D_METHOD("req_restart", "params"), &DebugAdapterParser::req_restart); ClassDB::bind_method(D_METHOD("req_terminate", "params"), &DebugAdapterParser::req_terminate); ClassDB::bind_method(D_METHOD("req_configurationDone", "params"), &DebugAdapterParser::prepare_success_response); ClassDB::bind_method(D_METHOD("req_pause", "params"), &DebugAdapterParser::req_pause); @@ -46,10 +49,13 @@ void DebugAdapterParser::_bind_methods() { ClassDB::bind_method(D_METHOD("req_threads", "params"), &DebugAdapterParser::req_threads); ClassDB::bind_method(D_METHOD("req_stackTrace", "params"), &DebugAdapterParser::req_stackTrace); ClassDB::bind_method(D_METHOD("req_setBreakpoints", "params"), &DebugAdapterParser::req_setBreakpoints); + ClassDB::bind_method(D_METHOD("req_breakpointLocations", "params"), &DebugAdapterParser::req_breakpointLocations); ClassDB::bind_method(D_METHOD("req_scopes", "params"), &DebugAdapterParser::req_scopes); ClassDB::bind_method(D_METHOD("req_variables", "params"), &DebugAdapterParser::req_variables); ClassDB::bind_method(D_METHOD("req_next", "params"), &DebugAdapterParser::req_next); ClassDB::bind_method(D_METHOD("req_stepIn", "params"), &DebugAdapterParser::req_stepIn); + ClassDB::bind_method(D_METHOD("req_evaluate", "params"), &DebugAdapterParser::req_evaluate); + ClassDB::bind_method(D_METHOD("req_godot/put_msg", "params"), &DebugAdapterParser::req_godot_put_msg); } Dictionary DebugAdapterParser::prepare_base_event() const { @@ -80,13 +86,31 @@ Dictionary DebugAdapterParser::prepare_error_response(const Dictionary &p_params DAP::Message message; String error, error_desc; switch (err_type) { + case DAP::ErrorType::WRONG_PATH: + error = "wrong_path"; + error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\""; + break; + case DAP::ErrorType::NOT_RUNNING: + error = "not_running"; + error_desc = "Can't attach to a running session since there isn't one."; + break; + case DAP::ErrorType::TIMEOUT: + error = "timeout"; + error_desc = "Timeout reached while processing a request."; + break; + case DAP::ErrorType::UNKNOWN_PLATFORM: + error = "unknown_platform"; + error_desc = "The specified platform is unknown."; + break; + case DAP::ErrorType::MISSING_DEVICE: + error = "missing_device"; + error_desc = "There's no connected device with specified id."; + break; case DAP::ErrorType::UNKNOWN: + default: error = "unknown"; error_desc = "An unknown error has ocurred when processing the request."; break; - case DAP::ErrorType::WRONG_PATH: - error = "wrong_path"; - error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\""; } message.id = err_type; @@ -114,10 +138,35 @@ Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const DebugAdapterProtocol::get_singleton()->notify_initialized(); + if (DebugAdapterProtocol::get_singleton()->_sync_breakpoints) { + // Send all current breakpoints + List<String> breakpoints; + ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); + for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) { + String breakpoint = E->get(); + + String path = breakpoint.left(breakpoint.find(":", 6)); // Skip initial part of path, aka "res://" + int line = breakpoint.substr(path.size()).to_int(); + + DebugAdapterProtocol::get_singleton()->on_debug_breakpoint_toggled(path, line, true); + } + } else { + // Remove all current breakpoints + EditorDebuggerNode::get_singleton()->get_default_debugger()->_clear_breakpoints(); + } + return response; } -Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) { +Dictionary DebugAdapterParser::req_disconnect(const Dictionary &p_params) const { + if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->attached) { + EditorNode::get_singleton()->run_stop(); + } + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const { Dictionary args = p_params["arguments"]; if (args.has("project") && !is_valid_path(args["project"])) { Dictionary variables; @@ -126,17 +175,85 @@ Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) { return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables); } + if (args.has("godot/custom_data")) { + DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsCustomData = args["godot/custom_data"]; + } + ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger(); if ((bool)args["noDebug"] != dbg->is_skip_breakpoints()) { dbg->debug_skip_breakpoints(); } - EditorNode::get_singleton()->run_play(); + String platform_string = args.get("platform", "host"); + if (platform_string == "host") { + EditorNode::get_singleton()->run_play(); + } else { + int device = args.get("device", -1); + int idx = -1; + if (platform_string == "android") { + for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { + if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Android") { + idx = i; + break; + } + } + } else if (platform_string == "web") { + for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { + if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "HTML5") { + idx = i; + break; + } + } + } + + if (idx == -1) { + return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN_PLATFORM); + } + + EditorNode *editor = EditorNode::get_singleton(); + Error err = platform_string == "android" ? editor->run_play_native(device, idx) : editor->run_play_native(-1, idx); + if (err) { + if (err == ERR_INVALID_PARAMETER && platform_string == "android") { + return prepare_error_response(p_params, DAP::ErrorType::MISSING_DEVICE); + } else { + return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN); + } + } + } + + DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = false; DebugAdapterProtocol::get_singleton()->notify_process(); return prepare_success_response(p_params); } +Dictionary DebugAdapterParser::req_attach(const Dictionary &p_params) const { + ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger(); + if (!dbg->is_session_active()) { + return prepare_error_response(p_params, DAP::ErrorType::NOT_RUNNING); + } + + DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = true; + DebugAdapterProtocol::get_singleton()->notify_process(); + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_restart(const Dictionary &p_params) const { + // Extract embedded "arguments" so it can be given to req_launch/req_attach + Dictionary params = p_params, args; + args = params["arguments"]; + args = args["arguments"]; + params["arguments"] = args; + + Dictionary response = DebugAdapterProtocol::get_singleton()->get_current_peer()->attached ? req_attach(params) : req_launch(params); + if (!response["success"]) { + response["command"] = p_params["command"]; + return response; + } + + return prepare_success_response(p_params); +} + Dictionary DebugAdapterParser::req_terminate(const Dictionary &p_params) const { EditorNode::get_singleton()->run_stop(); @@ -205,7 +322,7 @@ Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const return response; } -Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) { +Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) const { Dictionary response = prepare_success_response(p_params), body; response["body"] = body; @@ -230,14 +347,30 @@ Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) { lines.push_back(breakpoint.line + !lines_at_one); } - EditorDebuggerNode::get_singleton()->set_breakpoints(ProjectSettings::get_singleton()->localize_path(source.path), lines); Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines); body["breakpoints"] = updated_breakpoints; return response; } -Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) { +Dictionary DebugAdapterParser::req_breakpointLocations(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + Dictionary args = p_params["arguments"]; + + Array locations; + DAP::BreakpointLocation location; + location.line = args["line"]; + if (args.has("endLine")) { + location.endLine = args["endLine"]; + } + locations.push_back(location.to_json()); + + body["breakpoints"] = locations; + return response; +} + +Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) const { Dictionary response = prepare_success_response(p_params), body; response["body"] = body; @@ -291,7 +424,14 @@ Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const { int variable_id = args["variablesReference"]; Map<int, Array>::Element *E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id); + if (E) { + if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) { + for (int i = 0; i < E->value().size(); i++) { + Dictionary variable = E->value()[i]; + variable.erase("type"); + } + } body["variables"] = E ? E->value() : Array(); return response; } else { @@ -313,6 +453,29 @@ Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const { return prepare_success_response(p_params); } +Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Dictionary args = p_params["arguments"]; + + String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]); + body["result"] = value; + + return response; +} + +Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const { + Dictionary args = p_params["arguments"]; + + String msg = args["message"]; + Array data = args["data"]; + + EditorDebuggerNode::get_singleton()->get_default_debugger()->_put_msg(msg, data); + + return prepare_success_response(p_params); +} + Dictionary DebugAdapterParser::ev_initialized() const { Dictionary event = prepare_base_event(); event["event"] = "initialized"; @@ -423,3 +586,25 @@ Dictionary DebugAdapterParser::ev_output(const String &p_message) const { return event; } + +Dictionary DebugAdapterParser::ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "breakpoint"; + event["body"] = body; + + body["reason"] = p_enabled ? "new" : "removed"; + body["breakpoint"] = p_breakpoint.to_json(); + + return event; +} + +Dictionary DebugAdapterParser::ev_custom_data(const String &p_msg, const Array &p_data) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "godot/custom_data"; + event["body"] = body; + + body["message"] = p_msg; + body["data"] = p_data; + + return event; +} diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.h b/editor/debugger/debug_adapter/debug_adapter_parser.h index b86b37d067..4c93464e39 100644 --- a/editor/debugger/debug_adapter/debug_adapter_parser.h +++ b/editor/debugger/debug_adapter/debug_adapter_parser.h @@ -44,7 +44,7 @@ class DebugAdapterParser : public Object { private: friend DebugAdapterProtocol; - _FORCE_INLINE_ bool is_valid_path(const String &p_path) { + _FORCE_INLINE_ bool is_valid_path(const String &p_path) const { return p_path.begins_with(ProjectSettings::get_singleton()->get_resource_path()); } @@ -60,17 +60,23 @@ protected: public: // Requests Dictionary req_initialize(const Dictionary &p_params) const; - Dictionary req_launch(const Dictionary &p_params); + Dictionary req_launch(const Dictionary &p_params) const; + Dictionary req_disconnect(const Dictionary &p_params) const; + Dictionary req_attach(const Dictionary &p_params) const; + Dictionary req_restart(const Dictionary &p_params) const; Dictionary req_terminate(const Dictionary &p_params) const; Dictionary req_pause(const Dictionary &p_params) const; Dictionary req_continue(const Dictionary &p_params) const; Dictionary req_threads(const Dictionary &p_params) const; Dictionary req_stackTrace(const Dictionary &p_params) const; - Dictionary req_setBreakpoints(const Dictionary &p_params); - Dictionary req_scopes(const Dictionary &p_params); + Dictionary req_setBreakpoints(const Dictionary &p_params) const; + Dictionary req_breakpointLocations(const Dictionary &p_params) const; + Dictionary req_scopes(const Dictionary &p_params) const; Dictionary req_variables(const Dictionary &p_params) const; Dictionary req_next(const Dictionary &p_params) const; Dictionary req_stepIn(const Dictionary &p_params) const; + Dictionary req_evaluate(const Dictionary &p_params) const; + Dictionary req_godot_put_msg(const Dictionary &p_params) const; // Events Dictionary ev_initialized() const; @@ -83,6 +89,8 @@ public: Dictionary ev_stopped_step() const; Dictionary ev_continued() const; Dictionary ev_output(const String &p_message) const; + Dictionary ev_custom_data(const String &p_msg, const Array &p_data) const; + Dictionary ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const; }; #endif diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp index 0482271432..2e0e6cb7c8 100644 --- a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp @@ -94,11 +94,17 @@ Error DAPeer::handle_data() { String msg; msg.parse_utf8((const char *)req_buf, req_pos); + // Apply a timestamp if it there's none yet + if (!timestamp) { + timestamp = OS::get_singleton()->get_ticks_msec(); + } + // Response if (DebugAdapterProtocol::get_singleton()->process_message(msg)) { // Reset to read again req_pos = 0; has_header = false; + timestamp = 0; } } return OK; @@ -180,12 +186,460 @@ void DebugAdapterProtocol::reset_stack_info() { variable_list.clear(); } +int DebugAdapterProtocol::parse_variant(const Variant &p_var) { + switch (p_var.get_type()) { + case Variant::VECTOR2: + case Variant::VECTOR2I: { + int id = variable_id++; + Vector2 vec = p_var; + DAP::Variable x, y; + x.name = "x"; + y.name = "y"; + x.type = y.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR2 ? Variant::FLOAT : Variant::INT); + x.value = rtos(vec.x); + y.value = rtos(vec.y); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::RECT2: + case Variant::RECT2I: { + int id = variable_id++; + Rect2 rect = p_var; + DAP::Variable x, y, w, h; + x.name = "x"; + y.name = "y"; + w.name = "w"; + h.name = "h"; + x.type = y.type = w.type = h.type = Variant::get_type_name(p_var.get_type() == Variant::RECT2 ? Variant::FLOAT : Variant::INT); + x.value = rtos(rect.position.x); + y.value = rtos(rect.position.y); + w.value = rtos(rect.size.x); + h.value = rtos(rect.size.y); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(w.to_json()); + arr.push_back(h.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::VECTOR3: + case Variant::VECTOR3I: { + int id = variable_id++; + Vector3 vec = p_var; + DAP::Variable x, y, z; + x.name = "x"; + y.name = "y"; + z.name = "z"; + x.type = y.type = z.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR3 ? Variant::FLOAT : Variant::INT); + x.value = rtos(vec.x); + y.value = rtos(vec.y); + z.value = rtos(vec.z); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::TRANSFORM2D: { + int id = variable_id++; + Transform2D transform = p_var; + DAP::Variable x, y, origin; + x.name = "x"; + y.name = "y"; + origin.name = "origin"; + x.type = y.type = origin.type = Variant::get_type_name(Variant::VECTOR2); + x.value = transform.elements[0]; + y.value = transform.elements[1]; + origin.value = transform.elements[2]; + x.variablesReference = parse_variant(transform.elements[0]); + y.variablesReference = parse_variant(transform.elements[1]); + origin.variablesReference = parse_variant(transform.elements[2]); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(origin.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::PLANE: { + int id = variable_id++; + Plane plane = p_var; + DAP::Variable d, normal; + d.name = "d"; + normal.name = "normal"; + d.type = Variant::get_type_name(Variant::FLOAT); + normal.type = Variant::get_type_name(Variant::VECTOR3); + d.value = rtos(plane.d); + normal.value = plane.normal; + normal.variablesReference = parse_variant(plane.normal); + + Array arr; + arr.push_back(d.to_json()); + arr.push_back(normal.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::QUATERNION: { + int id = variable_id++; + Quaternion quat = p_var; + DAP::Variable x, y, z, w; + x.name = "x"; + y.name = "y"; + z.name = "z"; + w.name = "w"; + x.type = y.type = z.type = w.type = Variant::get_type_name(Variant::FLOAT); + x.value = rtos(quat.x); + y.value = rtos(quat.y); + z.value = rtos(quat.z); + w.value = rtos(quat.w); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + arr.push_back(w.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::AABB: { + int id = variable_id++; + AABB aabb = p_var; + DAP::Variable position, size; + position.name = "position"; + size.name = "size"; + position.type = size.type = Variant::get_type_name(Variant::VECTOR3); + position.value = aabb.position; + size.value = aabb.size; + position.variablesReference = parse_variant(aabb.position); + size.variablesReference = parse_variant(aabb.size); + + Array arr; + arr.push_back(position.to_json()); + arr.push_back(size.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::BASIS: { + int id = variable_id++; + Basis basis = p_var; + DAP::Variable x, y, z; + x.name = "x"; + y.name = "y"; + z.name = "z"; + x.type = y.type = z.type = Variant::get_type_name(Variant::VECTOR2); + x.value = basis.elements[0]; + y.value = basis.elements[1]; + z.value = basis.elements[2]; + x.variablesReference = parse_variant(basis.elements[0]); + y.variablesReference = parse_variant(basis.elements[1]); + z.variablesReference = parse_variant(basis.elements[2]); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::TRANSFORM3D: { + int id = variable_id++; + Transform3D transform = p_var; + DAP::Variable basis, origin; + basis.name = "basis"; + origin.name = "origin"; + basis.type = Variant::get_type_name(Variant::BASIS); + origin.type = Variant::get_type_name(Variant::VECTOR3); + basis.value = transform.basis; + origin.value = transform.origin; + basis.variablesReference = parse_variant(transform.basis); + origin.variablesReference = parse_variant(transform.origin); + + Array arr; + arr.push_back(basis.to_json()); + arr.push_back(origin.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::COLOR: { + int id = variable_id++; + Color color = p_var; + DAP::Variable r, g, b, a; + r.name = "r"; + g.name = "g"; + b.name = "b"; + a.name = "a"; + r.type = g.type = b.type = a.type = Variant::get_type_name(Variant::FLOAT); + r.value = rtos(color.r); + g.value = rtos(color.g); + b.value = rtos(color.b); + a.value = rtos(color.a); + + Array arr; + arr.push_back(r.to_json()); + arr.push_back(g.to_json()); + arr.push_back(b.to_json()); + arr.push_back(a.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::ARRAY: { + int id = variable_id++; + Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(array[i].get_type()); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::DICTIONARY: { + int id = variable_id++; + Dictionary dictionary = p_var; + Array arr; + + for (int i = 0; i < dictionary.size(); i++) { + DAP::Variable var; + var.name = dictionary.get_key_at_index(i); + Variant value = dictionary.get_value_at_index(i); + var.type = Variant::get_type_name(value.get_type()); + var.value = value; + var.variablesReference = parse_variant(value); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_BYTE_ARRAY: { + int id = variable_id++; + PackedByteArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "byte"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_INT32_ARRAY: { + int id = variable_id++; + PackedInt32Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "int"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_INT64_ARRAY: { + int id = variable_id++; + PackedInt64Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "long"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_FLOAT32_ARRAY: { + int id = variable_id++; + PackedFloat32Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "float"; + var.value = rtos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_FLOAT64_ARRAY: { + int id = variable_id++; + PackedFloat64Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "double"; + var.value = rtos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_STRING_ARRAY: { + int id = variable_id++; + PackedStringArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::STRING); + var.value = array[i]; + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_VECTOR2_ARRAY: { + int id = variable_id++; + PackedVector2Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::VECTOR2); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_VECTOR3_ARRAY: { + int id = variable_id++; + PackedVector2Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::VECTOR3); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_COLOR_ARRAY: { + int id = variable_id++; + PackedColorArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::COLOR); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + default: + // Simple atomic stuff, or too complex to be manipulated + return 0; + } +} + bool DebugAdapterProtocol::process_message(const String &p_text) { JSON json; ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Mal-formed message!"); Dictionary params = json.get_data(); bool completed = true; + if (OS::get_singleton()->get_ticks_msec() - _current_peer->timestamp > _request_timeout) { + Dictionary response = parser->prepare_error_response(params, DAP::ErrorType::TIMEOUT); + _current_peer->res_queue.push_front(response); + return true; + } + // Append "req_" to any command received; prevents name clash with existing functions, and possibly exploiting String command = "req_" + (String)params["command"]; if (parser->has_method(command)) { @@ -211,7 +665,7 @@ void DebugAdapterProtocol::notify_initialized() { } void DebugAdapterProtocol::notify_process() { - String launch_mode = _current_request.is_empty() ? "launch" : _current_request; + String launch_mode = _current_peer->attached ? "attach" : "launch"; Dictionary event = parser->ev_process(launch_mode); for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { @@ -222,7 +676,7 @@ void DebugAdapterProtocol::notify_process() { void DebugAdapterProtocol::notify_terminated() { Dictionary event = parser->ev_terminated(); for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { - if (_current_request == "launch" && _current_peer == E->get()) { + if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) { continue; } E->get()->res_queue.push_back(event); @@ -232,7 +686,7 @@ void DebugAdapterProtocol::notify_terminated() { void DebugAdapterProtocol::notify_exited(const int &p_exitcode) { Dictionary event = parser->ev_exited(p_exitcode); for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { - if (_current_request == "launch" && _current_peer == E->get()) { + if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) { continue; } E->get()->res_queue.push_back(event); @@ -286,25 +740,46 @@ void DebugAdapterProtocol::notify_output(const String &p_message) { } } +void DebugAdapterProtocol::notify_custom_data(const String &p_msg, const Array &p_data) { + Dictionary event = parser->ev_custom_data(p_msg, p_data); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + Ref<DAPeer> peer = E->get(); + if (peer->supportsCustomData) { + peer->res_queue.push_back(event); + } + } +} + +void DebugAdapterProtocol::notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) { + Dictionary event = parser->ev_breakpoint(p_breakpoint, p_enabled); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + if (_current_request == "setBreakpoints" && E->get() == _current_peer) { + continue; + } + E->get()->res_queue.push_back(event); + } +} + Array DebugAdapterProtocol::update_breakpoints(const String &p_path, const Array &p_lines) { Array updated_breakpoints; + // Add breakpoints for (int i = 0; i < p_lines.size(); i++) { + EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, p_lines[i], true); DAP::Breakpoint breakpoint; - breakpoint.verified = true; - breakpoint.source.path = p_path; - breakpoint.source.compute_checksums(); breakpoint.line = p_lines[i]; + breakpoint.source.path = p_path; - List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint); - if (E) { - breakpoint.id = E->get().id; - } else { - breakpoint.id = breakpoint_id++; - breakpoint_list.push_back(breakpoint); - } + ERR_FAIL_COND_V(!breakpoint_list.find(breakpoint), Array()); + updated_breakpoints.push_back(breakpoint_list.find(breakpoint)->get().to_json()); + } - updated_breakpoints.push_back(breakpoint.to_json()); + // Remove breakpoints + for (List<DAP::Breakpoint>::Element *E = breakpoint_list.front(); E; E = E->next()) { + DAP::Breakpoint b = E->get(); + if (b.source.path == p_path && !p_lines.has(b.line)) { + EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, b.line, false); + } } return updated_breakpoints; @@ -347,6 +822,29 @@ void DebugAdapterProtocol::on_debug_breaked(const bool &p_reallydid, const bool _processing_stackdump = p_has_stackdump; } +void DebugAdapterProtocol::on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled) { + DAP::Breakpoint breakpoint; + breakpoint.verified = true; + breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(p_path); + breakpoint.source.compute_checksums(); + breakpoint.line = p_line; + + if (p_enabled) { + // Add the breakpoint + breakpoint.id = breakpoint_id++; + breakpoint_list.push_back(breakpoint); + } else { + // Remove the breakpoint + List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint); + if (E) { + breakpoint.id = E->get().id; + breakpoint_list.erase(E); + } + } + + notify_breakpoint(breakpoint, p_enabled); +} + void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) { if (_processing_breakpoint && !p_stack_dump.is_empty()) { // Find existing breakpoint @@ -424,11 +922,21 @@ void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) { variable.name = stack_var.name; variable.value = stack_var.value; variable.type = Variant::get_type_name(stack_var.value.get_type()); + variable.variablesReference = parse_variant(stack_var.value); variable_list.find(variable_id)->value().push_back(variable.to_json()); _remaining_vars--; } +void DebugAdapterProtocol::on_debug_data(const String &p_msg, const Array &p_data) { + // Ignore data that is already handled by DAP + if (p_msg == "debug_enter" || p_msg == "debug_exit" || p_msg == "stack_dump" || p_msg == "stack_frame_vars" || p_msg == "stack_frame_var" || p_msg == "output" || p_msg == "request_quit") { + return; + } + + notify_custom_data(p_msg, p_data); +} + void DebugAdapterProtocol::poll() { if (server->is_connection_available()) { on_client_connected(); @@ -459,6 +967,8 @@ void DebugAdapterProtocol::poll() { } Error DebugAdapterProtocol::start(int p_port, const IPAddress &p_bind_ip) { + _request_timeout = (uint64_t)_EDITOR_GET("network/debug_adapter/request_timeout"); + _sync_breakpoints = (bool)_EDITOR_GET("network/debug_adapter/sync_breakpoints"); _initialized = true; return server->listen(p_port, p_bind_ip); } @@ -484,12 +994,15 @@ DebugAdapterProtocol::DebugAdapterProtocol() { node->get_pause_button()->connect("pressed", callable_mp(this, &DebugAdapterProtocol::on_debug_paused)); EditorDebuggerNode *debugger_node = EditorDebuggerNode::get_singleton(); + debugger_node->connect("breakpoint_toggled", callable_mp(this, &DebugAdapterProtocol::on_debug_breakpoint_toggled)); + debugger_node->get_default_debugger()->connect("stopped", callable_mp(this, &DebugAdapterProtocol::on_debug_stopped)); debugger_node->get_default_debugger()->connect("output", callable_mp(this, &DebugAdapterProtocol::on_debug_output)); debugger_node->get_default_debugger()->connect("breaked", callable_mp(this, &DebugAdapterProtocol::on_debug_breaked)); debugger_node->get_default_debugger()->connect("stack_dump", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_dump)); debugger_node->get_default_debugger()->connect("stack_frame_vars", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_vars)); debugger_node->get_default_debugger()->connect("stack_frame_var", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_var)); + debugger_node->get_default_debugger()->connect("debug_data", callable_mp(this, &DebugAdapterProtocol::on_debug_data)); } DebugAdapterProtocol::~DebugAdapterProtocol() { diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.h b/editor/debugger/debug_adapter/debug_adapter_protocol.h index 6b542033ed..d4291992bf 100644 --- a/editor/debugger/debug_adapter/debug_adapter_protocol.h +++ b/editor/debugger/debug_adapter/debug_adapter_protocol.h @@ -52,12 +52,17 @@ struct DAPeer : RefCounted { int content_length = 0; List<Dictionary> res_queue; int seq = 0; + uint64_t timestamp = 0; // Client specific info bool linesStartAt1 = false; bool columnsStartAt1 = false; bool supportsVariableType = false; bool supportsInvalidatedEvent = false; + bool supportsCustomData = false; + + // Internal client info + bool attached = false; Error handle_data(); Error send_data(); @@ -82,20 +87,26 @@ private: void on_debug_stopped(); void on_debug_output(const String &p_message); void on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump); + void on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled); void on_debug_stack_dump(const Array &p_stack_dump); void on_debug_stack_frame_vars(const int &p_size); void on_debug_stack_frame_var(const Array &p_data); + void on_debug_data(const String &p_msg, const Array &p_data); void reset_current_info(); void reset_ids(); void reset_stack_info(); + int parse_variant(const Variant &p_var); + bool _initialized = false; bool _processing_breakpoint = false; bool _stepping = false; bool _processing_stackdump = false; int _remaining_vars = 0; int _current_frame = 0; + uint64_t _request_timeout = 1000; + bool _sync_breakpoints = false; String _current_request; Ref<DAPeer> _current_peer; @@ -108,6 +119,8 @@ private: Map<int, Array> variable_list; public: + friend class DebugAdapterServer; + _FORCE_INLINE_ static DebugAdapterProtocol *get_singleton() { return singleton; } _FORCE_INLINE_ bool is_active() const { return _initialized && clients.size() > 0; } @@ -126,8 +139,10 @@ public: void notify_stopped_step(); void notify_continued(); void notify_output(const String &p_message); + void notify_custom_data(const String &p_msg, const Array &p_data); + void notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled); - Array update_breakpoints(const String &p_path, const Array &p_breakpoints); + Array update_breakpoints(const String &p_path, const Array &p_lines); void poll(); Error start(int p_port, const IPAddress &p_bind_ip); diff --git a/editor/debugger/debug_adapter/debug_adapter_server.cpp b/editor/debugger/debug_adapter/debug_adapter_server.cpp index f9092a1791..4775e2c8b0 100644 --- a/editor/debugger/debug_adapter/debug_adapter_server.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_server.cpp @@ -36,7 +36,8 @@ DebugAdapterServer::DebugAdapterServer() { _EDITOR_DEF("network/debug_adapter/remote_port", remote_port); - _EDITOR_DEF("network/debug_adapter/use_thread", use_thread); + _EDITOR_DEF("network/debug_adapter/request_timeout", protocol._request_timeout); + _EDITOR_DEF("network/debug_adapter/sync_breakpoints", protocol._sync_breakpoints); } void DebugAdapterServer::_notification(int p_what) { @@ -50,16 +51,17 @@ void DebugAdapterServer::_notification(int p_what) { case NOTIFICATION_INTERNAL_PROCESS: { // The main loop can be run again during request processing, which modifies internal state of the protocol. // Thus, "polling" is needed to prevent it from parsing other requests while the current one isn't finished. - if (started && !use_thread && !polling) { + if (started && !polling) { polling = true; protocol.poll(); polling = false; } } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + protocol._request_timeout = EditorSettings::get_singleton()->get("network/debug_adapter/request_timeout"); + protocol._sync_breakpoints = EditorSettings::get_singleton()->get("network/debug_adapter/sync_breakpoints"); int remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port"); - bool use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread"); - if (remote_port != this->remote_port || use_thread != this->use_thread) { + if (remote_port != this->remote_port) { this->stop(); this->start(); } @@ -67,35 +69,16 @@ void DebugAdapterServer::_notification(int p_what) { } } -void DebugAdapterServer::thread_func(void *p_userdata) { - DebugAdapterServer *self = static_cast<DebugAdapterServer *>(p_userdata); - while (self->thread_running) { - // Poll 20 times per second - self->protocol.poll(); - OS::get_singleton()->delay_usec(50000); - } -} - void DebugAdapterServer::start() { remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port"); - use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread"); if (protocol.start(remote_port, IPAddress("127.0.0.1")) == OK) { EditorNode::get_log()->add_message("--- Debug adapter server started ---", EditorLog::MSG_TYPE_EDITOR); - if (use_thread) { - thread_running = true; - thread.start(DebugAdapterServer::thread_func, this); - } - set_process_internal(!use_thread); + set_process_internal(true); started = true; } } void DebugAdapterServer::stop() { - if (use_thread) { - ERR_FAIL_COND(!thread.is_started()); - thread_running = false; - thread.wait_to_finish(); - } protocol.stop(); started = false; EditorNode::get_log()->add_message("--- Debug adapter server stopped ---", EditorLog::MSG_TYPE_EDITOR); diff --git a/editor/debugger/debug_adapter/debug_adapter_server.h b/editor/debugger/debug_adapter/debug_adapter_server.h index f8a38965cc..c449403cc2 100644 --- a/editor/debugger/debug_adapter/debug_adapter_server.h +++ b/editor/debugger/debug_adapter/debug_adapter_server.h @@ -39,11 +39,9 @@ class DebugAdapterServer : public EditorPlugin { DebugAdapterProtocol protocol; - Thread thread; int remote_port = 6006; bool thread_running = false; bool started = false; - bool use_thread = false; bool polling = false; static void thread_func(void *p_userdata); diff --git a/editor/debugger/debug_adapter/debug_adapter_types.h b/editor/debugger/debug_adapter/debug_adapter_types.h index aa9ab1adcd..5156c91d14 100644 --- a/editor/debugger/debug_adapter/debug_adapter_types.h +++ b/editor/debugger/debug_adapter/debug_adapter_types.h @@ -38,7 +38,11 @@ namespace DAP { enum ErrorType { UNKNOWN, - WRONG_PATH + WRONG_PATH, + NOT_RUNNING, + TIMEOUT, + UNKNOWN_PLATFORM, + MISSING_DEVICE }; struct Checksum { @@ -118,10 +122,14 @@ struct Breakpoint { struct BreakpointLocation { int line; + int endLine = -1; _FORCE_INLINE_ Dictionary to_json() const { Dictionary dict; dict["line"] = line; + if (endLine >= 0) { + dict["endLine"] = endLine; + } return dict; } diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index a9cb1a0131..07c02eb022 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -164,6 +164,7 @@ void EditorDebuggerNode::_bind_methods() { ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"))); ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script"))); ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug"))); + ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled"))); } EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() { @@ -487,6 +488,8 @@ void EditorDebuggerNode::set_breakpoint(const String &p_path, int p_line, bool p _for_all(tabs, [&](ScriptEditorDebugger *dbg) { dbg->set_breakpoint(p_path, p_line, p_enabled); }); + + emit_signal("breakpoint_toggled", p_path, p_line, p_enabled); } void EditorDebuggerNode::set_breakpoints(const String &p_path, Array p_lines) { diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 2a5013893f..2f5bde64a9 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -296,6 +296,7 @@ Size2 ScriptEditorDebugger::get_minimum_size() const { } void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) { + emit_signal(SNAME("debug_data"), p_msg, p_data); if (p_msg == "debug_enter") { _put_msg("get_stack_dump", Array()); @@ -872,6 +873,16 @@ void ScriptEditorDebugger::_clear_execution() { inspector->clear_stack_variables(); } +void ScriptEditorDebugger::_set_breakpoint(const String &p_file, const int &p_line, const bool &p_enabled) { + Ref<Script> script = ResourceLoader::load(p_file); + emit_signal("set_breakpoint", script, p_line - 1, p_enabled); + script.unref(); +} + +void ScriptEditorDebugger::_clear_breakpoints() { + emit_signal("clear_breakpoints"); +} + void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) { error_count = 0; warning_count = 0; @@ -1503,6 +1514,9 @@ void ScriptEditorDebugger::_bind_methods() { ADD_SIGNAL(MethodInfo("stack_dump", PropertyInfo(Variant::ARRAY, "stack_dump"))); ADD_SIGNAL(MethodInfo("stack_frame_vars", PropertyInfo(Variant::INT, "num_vars"))); ADD_SIGNAL(MethodInfo("stack_frame_var", PropertyInfo(Variant::ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("debug_data", PropertyInfo(Variant::STRING, "msg"), PropertyInfo(Variant::ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("set_breakpoint", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled"))); + ADD_SIGNAL(MethodInfo("clear_breakpoints")); } void ScriptEditorDebugger::add_debugger_plugin(const Ref<Script> &p_script) { diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index 499dda86da..1c1c0fd3e5 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -205,6 +205,9 @@ private: void _clear_execution(); void _stop_and_notify(); + void _set_breakpoint(const String &p_path, const int &p_line, const bool &p_enabled); + void _clear_breakpoints(); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index 8d1c22dabd..d04875f188 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -64,35 +64,42 @@ void DocTools::merge_from(const DocTools &p_data) { if (cf.methods[j].name != m.name) { continue; } - if (cf.methods[j].arguments.size() != m.arguments.size()) { - continue; - } - // since polymorphic functions are allowed we need to check the type of - // the arguments so we make sure they are different. - int arg_count = cf.methods[j].arguments.size(); - Vector<bool> arg_used; - arg_used.resize(arg_count); - for (int l = 0; l < arg_count; ++l) { - arg_used.write[l] = false; - } - // also there is no guarantee that argument ordering will match, so we - // have to check one by one so we make sure we have an exact match - for (int k = 0; k < arg_count; ++k) { + + const char *operator_prefix = "operator "; // Operators use a space at the end, making this prefix an invalid identifier (and differentiating from methods). + + if (cf.methods[j].name == c.name || cf.methods[j].name.begins_with(operator_prefix)) { + // Since constructors and operators can repeat, we need to check the type of + // the arguments so we make sure they are different. + + if (cf.methods[j].arguments.size() != m.arguments.size()) { + continue; + } + + int arg_count = cf.methods[j].arguments.size(); + Vector<bool> arg_used; + arg_used.resize(arg_count); for (int l = 0; l < arg_count; ++l) { - if (cf.methods[j].arguments[k].type == m.arguments[l].type && !arg_used[l]) { - arg_used.write[l] = true; - break; + arg_used.write[l] = false; + } + // also there is no guarantee that argument ordering will match, so we + // have to check one by one so we make sure we have an exact match + for (int k = 0; k < arg_count; ++k) { + for (int l = 0; l < arg_count; ++l) { + if (cf.methods[j].arguments[k].type == m.arguments[l].type && !arg_used[l]) { + arg_used.write[l] = true; + break; + } } } - } - bool not_the_same = false; - for (int l = 0; l < arg_count; ++l) { - if (!arg_used[l]) { // at least one of the arguments was different - not_the_same = true; + bool not_the_same = false; + for (int l = 0; l < arg_count; ++l) { + if (!arg_used[l]) { // at least one of the arguments was different + not_the_same = true; + } + } + if (not_the_same) { + continue; } - } - if (not_the_same) { - continue; } const DocData::MethodDoc &mf = cf.methods[j]; @@ -270,7 +277,7 @@ void DocTools::generate(bool p_basic_types) { EO = EO->next(); } - if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL) { + if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL || (E.type == Variant::NIL && E.usage & PROPERTY_USAGE_ARRAY)) { continue; } @@ -427,6 +434,18 @@ void DocTools::generate(bool p_basic_types) { } } + Vector<Error> errs = ClassDB::get_method_error_return_values(name, E.name); + if (errs.size()) { + if (errs.find(OK) == -1) { + errs.insert(0, OK); + } + for (int i = 0; i < errs.size(); i++) { + if (method.errors_returned.find(errs[i]) == -1) { + method.errors_returned.push_back(errs[i]); + } + } + } + c.methods.push_back(method); } @@ -867,6 +886,9 @@ static Error _parse_methods(Ref<XMLParser> &parser, Vector<DocData::MethodDoc> & if (parser->has_attribute("enum")) { method.return_enum = parser->get_attribute_value("enum"); } + } else if (name == "returns_error") { + ERR_FAIL_COND_V(!parser->has_attribute("number"), ERR_FILE_CORRUPT); + method.errors_returned.push_back(parser->get_attribute_value("number").to_int()); } else if (name == "argument") { DocData::ArgumentDoc argument; ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); @@ -1215,6 +1237,11 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str } _write_string(f, 3, "<return type=\"" + m.return_type + "\"" + enum_text + " />"); } + if (m.errors_returned.size() > 0) { + for (int j = 0; j < m.errors_returned.size(); j++) { + _write_string(f, 3, "<returns_error number=\"" + itos(m.errors_returned[j]) + "\"/>"); + } + } for (int j = 0; j < m.arguments.size(); j++) { const DocData::ArgumentDoc &a = m.arguments[j]; diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp index 5209ee06c6..88087664d7 100644 --- a/editor/editor_audio_buses.cpp +++ b/editor/editor_audio_buses.cpp @@ -530,7 +530,7 @@ void EditorAudioBus::_effect_add(int p_which) { ur->commit_action(); } -void EditorAudioBus::_gui_input(const Ref<InputEvent> &p_event) { +void EditorAudioBus::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseButton> mb = p_event; @@ -744,7 +744,7 @@ void EditorAudioBus::_effect_rmb(const Vector2 &p_pos) { void EditorAudioBus::_bind_methods() { ClassDB::bind_method("update_bus", &EditorAudioBus::update_bus); ClassDB::bind_method("update_send", &EditorAudioBus::update_send); - ClassDB::bind_method("_gui_input", &EditorAudioBus::_gui_input); + ClassDB::bind_method("_get_drag_data_fw", &EditorAudioBus::get_drag_data_fw); ClassDB::bind_method("_can_drop_data_fw", &EditorAudioBus::can_drop_data_fw); ClassDB::bind_method("_drop_data_fw", &EditorAudioBus::drop_data_fw); diff --git a/editor/editor_audio_buses.h b/editor/editor_audio_buses.h index 0fbda8ece9..e1aaa060c6 100644 --- a/editor/editor_audio_buses.h +++ b/editor/editor_audio_buses.h @@ -90,7 +90,7 @@ class EditorAudioBus : public PanelContainer { bool is_master; mutable bool hovering_drop; - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _effects_gui_input(Ref<InputEvent> p_event); void _bus_popup_pressed(int p_option); diff --git a/editor/editor_command_palette.cpp b/editor/editor_command_palette.cpp index cf6ede2277..25250e231e 100644 --- a/editor/editor_command_palette.cpp +++ b/editor/editor_command_palette.cpp @@ -149,7 +149,7 @@ void EditorCommandPalette::_sbox_input(const Ref<InputEvent> &p_ie) { case KEY_DOWN: case KEY_PAGEUP: case KEY_PAGEDOWN: { - search_options->call("_gui_input", k); + search_options->gui_input(k); } break; default: break; @@ -226,7 +226,7 @@ void EditorCommandPalette::register_shortcuts_as_command() { ev.instantiate(); ev->set_shortcut(shortcut); String shortcut_text = String(shortcut->get_as_text()); - add_command(command_name, *key, callable_mp(EditorNode::get_singleton()->get_viewport(), &Viewport::unhandled_input), varray(ev, false), shortcut_text); + add_command(command_name, *key, callable_mp(EditorNode::get_singleton()->get_viewport(), &Viewport::push_unhandled_input), varray(ev, false), shortcut_text); key = unregistered_shortcuts.next(key); } unregistered_shortcuts.clear(); @@ -238,7 +238,7 @@ Ref<Shortcut> EditorCommandPalette::add_shortcut_command(const String &p_command ev.instantiate(); ev->set_shortcut(p_shortcut); String shortcut_text = String(p_shortcut->get_as_text()); - add_command(p_command, p_key, callable_mp(EditorNode::get_singleton()->get_viewport(), &Viewport::unhandled_input), varray(ev, false), shortcut_text); + add_command(p_command, p_key, callable_mp(EditorNode::get_singleton()->get_viewport(), &Viewport::push_unhandled_input), varray(ev, false), shortcut_text); } else { const String key_name = String(p_key); const String command_name = String(p_command); diff --git a/editor/editor_command_palette.h b/editor/editor_command_palette.h index cfd8b964c8..093f4b797d 100644 --- a/editor/editor_command_palette.h +++ b/editor/editor_command_palette.h @@ -31,9 +31,9 @@ #ifndef EDITOR_COMMAND_PALETTE_H #define EDITOR_COMMAND_PALETTE_H +#include "core/input/shortcut.h" #include "core/os/thread_safe.h" #include "scene/gui/dialogs.h" -#include "scene/gui/shortcut.h" #include "scene/gui/tree.h" class EditorCommandPalette : public ConfirmationDialog { diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index c62e5b75b2..e40bbefef8 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -438,6 +438,21 @@ const Vector<Callable> EditorData::get_undo_redo_inspector_hook_callback() { return undo_redo_callbacks; } +void EditorData::add_move_array_element_function(const StringName &p_class, Callable p_callable) { + move_element_functions.insert(p_class, p_callable); +} + +void EditorData::remove_move_array_element_function(const StringName &p_class) { + move_element_functions.erase(p_class); +} + +Callable EditorData::get_move_array_element_function(const StringName &p_class) const { + if (move_element_functions.has(p_class)) { + return move_element_functions[p_class]; + } + return Callable(); +} + void EditorData::remove_editor_plugin(EditorPlugin *p_plugin) { p_plugin->undo_redo = nullptr; editor_plugins.erase(p_plugin); diff --git a/editor/editor_data.h b/editor/editor_data.h index df6ba9d0c9..9184ddcf39 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -133,6 +133,7 @@ private: List<PropertyData> clipboard; UndoRedo undo_redo; Vector<Callable> undo_redo_callbacks; + Map<StringName, Callable> move_element_functions; void _cleanup_history(); @@ -167,10 +168,14 @@ public: EditorPlugin *get_editor_plugin(int p_idx); UndoRedo &get_undo_redo(); - void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have 4 args: (Object* undo_redo, Object *modified_object, String property, Variant new_value) + void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value) void remove_undo_redo_inspector_hook_callback(Callable p_callable); const Vector<Callable> get_undo_redo_inspector_hook_callback(); + void add_move_array_element_function(const StringName &p_class, Callable p_callable); // Function should have this signature: void (Object* undo_redo, Object *modified_object, String array_prefix, int element_index, int new_position) + void remove_move_array_element_function(const StringName &p_class); + Callable get_move_array_element_function(const StringName &p_class) const; + void save_editor_global_states(); void restore_editor_global_states(); diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index 91c3c51c4d..10ed76673e 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/crypto/crypto_core.h" +#include "core/extension/native_extension.h" #include "core/io/config_file.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" @@ -1050,6 +1051,14 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } } + if (FileAccess::exists(NativeExtension::EXTENSION_LIST_CONFIG_FILE)) { + Vector<uint8_t> array = FileAccess::get_file_as_array(NativeExtension::EXTENSION_LIST_CONFIG_FILE); + err = p_func(p_udata, NativeExtension::EXTENSION_LIST_CONFIG_FILE, array, idx, total, enc_in_filters, enc_ex_filters, key); + if (err != OK) { + return err; + } + } + // Store text server data if it is supported. if (TS->has_feature(TextServer::FEATURE_USE_SUPPORT_DATA)) { bool use_data = ProjectSettings::get_singleton()->get("internationalization/locale/include_text_server_data"); @@ -1939,7 +1948,7 @@ void EditorExportPlatformPC::set_debug_32(const String &p_file) { void EditorExportPlatformPC::get_platform_features(List<String> *r_features) { r_features->push_back("pc"); //all pcs support "pc" r_features->push_back("s3tc"); //all pcs support "s3tc" compression - r_features->push_back(get_os_name()); //OS name is a feature + r_features->push_back(get_os_name().to_lower()); //OS name is a feature } void EditorExportPlatformPC::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp index 5ccbed1b49..bf95e6cf62 100644 --- a/editor/editor_file_dialog.cpp +++ b/editor/editor_file_dialog.cpp @@ -124,7 +124,7 @@ void EditorFileDialog::_notification(int p_what) { } } -void EditorFileDialog::_unhandled_input(const Ref<InputEvent> &p_event) { +void EditorFileDialog::unhandled_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventKey> k = p_event; @@ -728,6 +728,7 @@ void EditorFileDialog::update_file_list() { item_list->set_icon_mode(ItemList::ICON_MODE_TOP); item_list->set_fixed_column_width(thumbnail_size * 3 / 2); item_list->set_max_text_lines(2); + item_list->set_text_overrun_behavior(TextParagraph::OVERRUN_TRIM_ELLIPSIS); item_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size)); if (thumbnail_size < 64) { @@ -957,7 +958,7 @@ String EditorFileDialog::get_current_path() const { } void EditorFileDialog::set_current_dir(const String &p_dir) { - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { dir_access->change_dir(OS::get_singleton()->get_resource_dir()); } dir_access->change_dir(p_dir); @@ -1354,8 +1355,6 @@ EditorFileDialog::DisplayMode EditorFileDialog::get_display_mode() const { } void EditorFileDialog::_bind_methods() { - ClassDB::bind_method(D_METHOD("_unhandled_input"), &EditorFileDialog::_unhandled_input); - ClassDB::bind_method(D_METHOD("_cancel_pressed"), &EditorFileDialog::_cancel_pressed); ClassDB::bind_method(D_METHOD("clear_filters"), &EditorFileDialog::clear_filters); @@ -1645,6 +1644,7 @@ EditorFileDialog::EditorFileDialog() { item_list->connect("item_rmb_selected", callable_mp(this, &EditorFileDialog::_item_list_item_rmb_selected)); item_list->connect("rmb_clicked", callable_mp(this, &EditorFileDialog::_item_list_rmb_clicked)); item_list->set_allow_rmb_select(true); + list_vb->add_child(item_list); item_menu = memnew(PopupMenu); diff --git a/editor/editor_file_dialog.h b/editor/editor_file_dialog.h index d789956a3e..ed427dc76e 100644 --- a/editor/editor_file_dialog.h +++ b/editor/editor_file_dialog.h @@ -193,7 +193,7 @@ private: void _thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata); void _request_single_thumbnail(const String &p_path); - void _unhandled_input(const Ref<InputEvent> &p_event); + virtual void unhandled_input(const Ref<InputEvent> &p_event) override; bool _is_open_should_be_disabled(); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 78861eff9d..8523833d52 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -31,6 +31,7 @@ #include "editor_file_system.h" #include "core/config/project_settings.h" +#include "core/extension/native_extension_manager.h" #include "core/io/file_access.h" #include "core/io/resource_importer.h" #include "core/io/resource_loader.h" @@ -443,6 +444,10 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name); + if (importer.is_null()) { + return true; // the importer has possibly changed, try to reimport. + } + if (importer->get_format_version() > version) { return true; // version changed, reimport } @@ -605,6 +610,18 @@ bool EditorFileSystem::_update_scan_actions() { } } + if (_scan_extensions()) { + //needs editor restart + //extensions also may provide filetypes to be imported, so they must run before importing + if (EditorNode::immediate_confirmation_dialog(TTR("Some extensions need the editor to restart to take effect."), first_scan ? TTR("Restart") : TTR("Save&Restart"), TTR("Continue"))) { + if (!first_scan) { + EditorNode::get_singleton()->save_all_scenes(); + } + EditorNode::get_singleton()->restart_editor(); + //do not import + return true; + } + } if (reimports.size()) { reimport_files(reimports); } else { @@ -2222,6 +2239,76 @@ ResourceUID::ID EditorFileSystem::_resource_saver_get_resource_id_for_path(const } } +static void _scan_extensions_dir(EditorFileSystemDirectory *d, Set<String> &extensions) { + int fc = d->get_file_count(); + for (int i = 0; i < fc; i++) { + if (d->get_file_type(i) == SNAME("NativeExtension")) { + extensions.insert(d->get_file_path(i)); + } + } + int dc = d->get_subdir_count(); + for (int i = 0; i < dc; i++) { + _scan_extensions_dir(d->get_subdir(i), extensions); + } +} +bool EditorFileSystem::_scan_extensions() { + EditorFileSystemDirectory *d = get_filesystem(); + Set<String> extensions; + _scan_extensions_dir(d, extensions); + + //verify against loaded extensions + + Vector<String> extensions_added; + Vector<String> extensions_removed; + + for (const String &E : extensions) { + if (!NativeExtensionManager::get_singleton()->is_extension_loaded(E)) { + extensions_added.push_back(E); + } + } + + Vector<String> loaded_extensions = NativeExtensionManager::get_singleton()->get_loaded_extensions(); + for (int i = 0; i < loaded_extensions.size(); i++) { + if (!extensions.has(loaded_extensions[i])) { + extensions_removed.push_back(loaded_extensions[i]); + } + } + + if (extensions.size()) { + if (extensions_added.size() || extensions_removed.size()) { //extensions were added or removed + FileAccessRef f = FileAccess::open(NativeExtension::EXTENSION_LIST_CONFIG_FILE, FileAccess::WRITE); + for (const String &E : extensions) { + f->store_line(E); + } + } + } else { + if (loaded_extensions.size() || FileAccess::exists(NativeExtension::EXTENSION_LIST_CONFIG_FILE)) { //extensions were removed + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + da->remove(NativeExtension::EXTENSION_LIST_CONFIG_FILE); + } + } + + bool needs_restart = false; + for (int i = 0; i < extensions_added.size(); i++) { + NativeExtensionManager::LoadStatus st = NativeExtensionManager::get_singleton()->load_extension(extensions_added[i]); + if (st == NativeExtensionManager::LOAD_STATUS_FAILED) { + EditorNode::get_singleton()->add_io_error("Error loading extension: " + extensions_added[i]); + } else if (st == NativeExtensionManager::LOAD_STATUS_NEEDS_RESTART) { + needs_restart = true; + } + } + for (int i = 0; i < extensions_removed.size(); i++) { + NativeExtensionManager::LoadStatus st = NativeExtensionManager::get_singleton()->unload_extension(extensions_removed[i]); + if (st == NativeExtensionManager::LOAD_STATUS_FAILED) { + EditorNode::get_singleton()->add_io_error("Error removing extension: " + extensions_added[i]); + } else if (st == NativeExtensionManager::LOAD_STATUS_NEEDS_RESTART) { + needs_restart = true; + } + } + + return needs_restart; +} + void EditorFileSystem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_filesystem"), &EditorFileSystem::get_filesystem); ClassDB::bind_method(D_METHOD("is_scanning"), &EditorFileSystem::is_scanning); diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 9dce29d09c..b47cf5523a 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -255,6 +255,8 @@ class EditorFileSystem : public Node { static ResourceUID::ID _resource_saver_get_resource_id_for_path(const String &p_path, bool p_generate); + bool _scan_extensions(); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp index 1e3db1a7b0..e5d6315ef7 100644 --- a/editor/editor_fonts.cpp +++ b/editor/editor_fonts.cpp @@ -67,46 +67,113 @@ m_name->add_data(FontJapanese); \ m_name->add_data(FontFallback); -// the custom spacings might only work with Noto Sans -#define MAKE_DEFAULT_FONT(m_name) \ - Ref<Font> m_name; \ - m_name.instantiate(); \ - if (CustomFont.is_valid()) { \ - m_name->add_data(CustomFont); \ - m_name->add_data(DefaultFont); \ - } else { \ - m_name->add_data(DefaultFont); \ - } \ - m_name->set_spacing(Font::SPACING_TOP, -EDSCALE); \ - m_name->set_spacing(Font::SPACING_BOTTOM, -EDSCALE); \ +#define MAKE_DEFAULT_FONT(m_name, m_variations, m_base_size) \ + Ref<Font> m_name; \ + m_name.instantiate(); \ + if (CustomFont.is_valid()) { \ + m_name->add_data(CustomFont); \ + m_name->add_data(DefaultFont); \ + } else { \ + m_name->add_data(DefaultFont); \ + } \ + { \ + Dictionary variations; \ + if (m_variations != String()) { \ + Vector<String> variation_tags = m_variations.split(","); \ + for (int i = 0; i < variation_tags.size(); i++) { \ + Vector<String> tokens = variation_tags[i].split("="); \ + if (tokens.size() == 2) { \ + variations[tokens[0]] = tokens[1].to_float(); \ + } \ + } \ + } \ + m_name->set_variation_coordinates(variations); \ + } \ + m_name->set_base_size(m_base_size); \ + m_name->set_spacing(TextServer::SPACING_TOP, -EDSCALE); \ + m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \ MAKE_FALLBACKS(m_name); -#define MAKE_BOLD_FONT(m_name) \ - Ref<Font> m_name; \ - m_name.instantiate(); \ - if (CustomFontBold.is_valid()) { \ - m_name->add_data(CustomFontBold); \ - m_name->add_data(DefaultFontBold); \ - } else { \ - m_name->add_data(DefaultFontBold); \ - } \ - m_name->set_spacing(Font::SPACING_TOP, -EDSCALE); \ - m_name->set_spacing(Font::SPACING_BOTTOM, -EDSCALE); \ +#define MAKE_BOLD_FONT(m_name, m_variations, m_base_size) \ + Ref<Font> m_name; \ + m_name.instantiate(); \ + if (CustomFontBold.is_valid()) { \ + m_name->add_data(CustomFontBold); \ + m_name->add_data(DefaultFontBold); \ + } else { \ + m_name->add_data(DefaultFontBold); \ + } \ + { \ + Dictionary variations; \ + if (m_variations != String()) { \ + Vector<String> variation_tags = m_variations.split(","); \ + for (int i = 0; i < variation_tags.size(); i++) { \ + Vector<String> tokens = variation_tags[i].split("="); \ + if (tokens.size() == 2) { \ + variations[tokens[0]] = tokens[1].to_float(); \ + } \ + } \ + } \ + m_name->set_variation_coordinates(variations); \ + } \ + m_name->set_base_size(m_base_size); \ + m_name->set_spacing(TextServer::SPACING_TOP, -EDSCALE); \ + m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \ MAKE_FALLBACKS_BOLD(m_name); -#define MAKE_SOURCE_FONT(m_name) \ - Ref<Font> m_name; \ - m_name.instantiate(); \ - if (CustomFontSource.is_valid()) { \ - m_name->add_data(CustomFontSource); \ - m_name->add_data(dfmono); \ - } else { \ - m_name->add_data(dfmono); \ - } \ - m_name->set_spacing(Font::SPACING_TOP, -EDSCALE); \ - m_name->set_spacing(Font::SPACING_BOTTOM, -EDSCALE); \ +#define MAKE_SOURCE_FONT(m_name, m_variations, m_base_size) \ + Ref<Font> m_name; \ + m_name.instantiate(); \ + if (CustomFontSource.is_valid()) { \ + m_name->add_data(CustomFontSource); \ + m_name->add_data(dfmono); \ + } else { \ + m_name->add_data(dfmono); \ + } \ + { \ + Dictionary variations; \ + if (m_variations != String()) { \ + Vector<String> variation_tags = m_variations.split(","); \ + for (int i = 0; i < variation_tags.size(); i++) { \ + Vector<String> tokens = variation_tags[i].split("="); \ + if (tokens.size() == 2) { \ + variations[tokens[0]] = tokens[1].to_float(); \ + } \ + } \ + } \ + m_name->set_variation_coordinates(variations); \ + } \ + m_name->set_base_size(m_base_size); \ + m_name->set_spacing(TextServer::SPACING_TOP, -EDSCALE); \ + m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \ MAKE_FALLBACKS(m_name); +Ref<FontData> load_cached_external_font(const String &p_path, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint) { + Ref<FontData> font; + font.instantiate(); + + Vector<uint8_t> data = FileAccess::get_file_as_array(p_path); + + font->set_data(data); + font->set_antialiased(p_aa); + font->set_hinting(p_hinting); + font->set_force_autohinter(p_autohint); + + return font; +} + +Ref<FontData> load_cached_internal_font(const uint8_t *p_data, size_t p_size, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint) { + Ref<FontData> font; + font.instantiate(); + + font->set_data_ptr(p_data, p_size); + font->set_antialiased(p_aa); + font->set_hinting(p_hinting); + font->set_force_autohinter(p_autohint); + + return font; +} + void editor_register_fonts(Ref<Theme> p_theme) { DirAccess *dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); @@ -144,11 +211,7 @@ void editor_register_fonts(Ref<Theme> p_theme) { String custom_font_path = EditorSettings::get_singleton()->get("interface/editor/main_font"); Ref<FontData> CustomFont; if (custom_font_path.length() > 0 && dir->file_exists(custom_font_path)) { - CustomFont.instantiate(); - CustomFont->load_resource(custom_font_path, default_font_size); - CustomFont->set_antialiased(font_antialiased); - CustomFont->set_hinting(font_hinting); - CustomFont->set_force_autohinter(true); //just looks better..i think? + CustomFont = load_cached_external_font(custom_font_path, font_hinting, font_antialiased, true); } else { EditorSettings::get_singleton()->set_manually("interface/editor/main_font", ""); } @@ -158,11 +221,7 @@ void editor_register_fonts(Ref<Theme> p_theme) { String custom_font_path_bold = EditorSettings::get_singleton()->get("interface/editor/main_font_bold"); Ref<FontData> CustomFontBold; if (custom_font_path_bold.length() > 0 && dir->file_exists(custom_font_path_bold)) { - CustomFontBold.instantiate(); - CustomFontBold->load_resource(custom_font_path_bold, default_font_size); - CustomFontBold->set_antialiased(font_antialiased); - CustomFontBold->set_hinting(font_hinting); - CustomFontBold->set_force_autohinter(true); //just looks better..i think? + CustomFontBold = load_cached_external_font(custom_font_path_bold, font_hinting, font_antialiased, true); } else { EditorSettings::get_singleton()->set_manually("interface/editor/main_font_bold", ""); } @@ -172,231 +231,51 @@ void editor_register_fonts(Ref<Theme> p_theme) { String custom_font_path_source = EditorSettings::get_singleton()->get("interface/editor/code_font"); Ref<FontData> CustomFontSource; if (custom_font_path_source.length() > 0 && dir->file_exists(custom_font_path_source)) { - CustomFontSource.instantiate(); - CustomFontSource->load_resource(custom_font_path_source, default_font_size); - CustomFontSource->set_antialiased(font_antialiased); - CustomFontSource->set_hinting(font_hinting); - - Vector<String> subtag = String(EditorSettings::get_singleton()->get("interface/editor/code_font_custom_variations")).split(","); - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - if (subtag_a.size() == 2) { - CustomFontSource->set_variation(subtag_a[0], subtag_a[1].to_float()); - } - } + CustomFontSource = load_cached_external_font(custom_font_path_source, font_hinting, font_antialiased, true); } else { EditorSettings::get_singleton()->set_manually("interface/editor/code_font", ""); } memdelete(dir); - /* Noto Sans UI */ - - Ref<FontData> DefaultFont; - DefaultFont.instantiate(); - DefaultFont->load_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf", default_font_size); - DefaultFont->set_antialiased(font_antialiased); - DefaultFont->set_hinting(font_hinting); - DefaultFont->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> DefaultFontBold; - DefaultFontBold.instantiate(); - DefaultFontBold->load_memory(_font_NotoSans_Bold, _font_NotoSans_Bold_size, "ttf", default_font_size); - DefaultFontBold->set_antialiased(font_antialiased); - DefaultFontBold->set_hinting(font_hinting); - DefaultFontBold->set_force_autohinter(true); // just looks better..i think? - - Ref<FontData> FontArabic; - FontArabic.instantiate(); - FontArabic->load_memory(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, "ttf", default_font_size); - FontArabic->set_antialiased(font_antialiased); - FontArabic->set_hinting(font_hinting); - FontArabic->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontArabicBold; - FontArabicBold.instantiate(); - FontArabicBold->load_memory(_font_NotoNaskhArabicUI_Bold, _font_NotoNaskhArabicUI_Bold_size, "ttf", default_font_size); - FontArabicBold->set_antialiased(font_antialiased); - FontArabicBold->set_hinting(font_hinting); - FontArabicBold->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontBengali; - FontBengali.instantiate(); - FontBengali->load_memory(_font_NotoSansBengaliUI_Regular, _font_NotoSansBengaliUI_Regular_size, "ttf", default_font_size); - FontBengali->set_antialiased(font_antialiased); - FontBengali->set_hinting(font_hinting); - FontBengali->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontBengaliBold; - FontBengaliBold.instantiate(); - FontBengaliBold->load_memory(_font_NotoSansBengaliUI_Bold, _font_NotoSansBengaliUI_Bold_size, "ttf", default_font_size); - FontBengaliBold->set_antialiased(font_antialiased); - FontBengaliBold->set_hinting(font_hinting); - FontBengaliBold->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontDevanagari; - FontDevanagari.instantiate(); - FontDevanagari->load_memory(_font_NotoSansDevanagariUI_Regular, _font_NotoSansDevanagariUI_Regular_size, "ttf", default_font_size); - FontDevanagari->set_antialiased(font_antialiased); - FontDevanagari->set_hinting(font_hinting); - FontDevanagari->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontDevanagariBold; - FontDevanagariBold.instantiate(); - FontDevanagariBold->load_memory(_font_NotoSansDevanagariUI_Bold, _font_NotoSansDevanagariUI_Bold_size, "ttf", default_font_size); - FontDevanagariBold->set_antialiased(font_antialiased); - FontDevanagariBold->set_hinting(font_hinting); - FontDevanagariBold->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontGeorgian; - FontGeorgian.instantiate(); - FontGeorgian->load_memory(_font_NotoSansGeorgian_Regular, _font_NotoSansGeorgian_Regular_size, "ttf", default_font_size); - FontGeorgian->set_antialiased(font_antialiased); - FontGeorgian->set_hinting(font_hinting); - FontGeorgian->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontGeorgianBold; - FontGeorgianBold.instantiate(); - FontGeorgianBold->load_memory(_font_NotoSansGeorgian_Bold, _font_NotoSansGeorgian_Bold_size, "ttf", default_font_size); - FontGeorgianBold->set_antialiased(font_antialiased); - FontGeorgianBold->set_hinting(font_hinting); - FontGeorgianBold->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontHebrew; - FontHebrew.instantiate(); - FontHebrew->load_memory(_font_NotoSansHebrew_Regular, _font_NotoSansHebrew_Regular_size, "ttf", default_font_size); - FontHebrew->set_antialiased(font_antialiased); - FontHebrew->set_hinting(font_hinting); - FontHebrew->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontHebrewBold; - FontHebrewBold.instantiate(); - FontHebrewBold->load_memory(_font_NotoSansHebrew_Bold, _font_NotoSansHebrew_Bold_size, "ttf", default_font_size); - FontHebrewBold->set_antialiased(font_antialiased); - FontHebrewBold->set_hinting(font_hinting); - FontHebrewBold->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontMalayalam; - FontMalayalam.instantiate(); - FontMalayalam->load_memory(_font_NotoSansMalayalamUI_Regular, _font_NotoSansMalayalamUI_Regular_size, "ttf", default_font_size); - FontMalayalam->set_antialiased(font_antialiased); - FontMalayalam->set_hinting(font_hinting); - FontMalayalam->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontMalayalamBold; - FontMalayalamBold.instantiate(); - FontMalayalamBold->load_memory(_font_NotoSansMalayalamUI_Bold, _font_NotoSansMalayalamUI_Bold_size, "ttf", default_font_size); - FontMalayalamBold->set_antialiased(font_antialiased); - FontMalayalamBold->set_hinting(font_hinting); - FontMalayalamBold->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontOriya; - FontOriya.instantiate(); - FontOriya->load_memory(_font_NotoSansOriyaUI_Regular, _font_NotoSansOriyaUI_Regular_size, "ttf", default_font_size); - FontOriya->set_antialiased(font_antialiased); - FontOriya->set_hinting(font_hinting); - FontOriya->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontOriyaBold; - FontOriyaBold.instantiate(); - FontOriyaBold->load_memory(_font_NotoSansOriyaUI_Bold, _font_NotoSansOriyaUI_Bold_size, "ttf", default_font_size); - FontOriyaBold->set_antialiased(font_antialiased); - FontOriyaBold->set_hinting(font_hinting); - FontOriyaBold->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontSinhala; - FontSinhala.instantiate(); - FontSinhala->load_memory(_font_NotoSansSinhalaUI_Regular, _font_NotoSansSinhalaUI_Regular_size, "ttf", default_font_size); - FontSinhala->set_antialiased(font_antialiased); - FontSinhala->set_hinting(font_hinting); - FontSinhala->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontSinhalaBold; - FontSinhalaBold.instantiate(); - FontSinhalaBold->load_memory(_font_NotoSansSinhalaUI_Bold, _font_NotoSansSinhalaUI_Bold_size, "ttf", default_font_size); - FontSinhalaBold->set_antialiased(font_antialiased); - FontSinhalaBold->set_hinting(font_hinting); - FontSinhalaBold->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontTamil; - FontTamil.instantiate(); - FontTamil->load_memory(_font_NotoSansTamilUI_Regular, _font_NotoSansTamilUI_Regular_size, "ttf", default_font_size); - FontTamil->set_antialiased(font_antialiased); - FontTamil->set_hinting(font_hinting); - FontTamil->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontTamilBold; - FontTamilBold.instantiate(); - FontTamilBold->load_memory(_font_NotoSansTamilUI_Bold, _font_NotoSansTamilUI_Bold_size, "ttf", default_font_size); - FontTamilBold->set_antialiased(font_antialiased); - FontTamilBold->set_hinting(font_hinting); - FontTamilBold->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontTelugu; - FontTelugu.instantiate(); - FontTelugu->load_memory(_font_NotoSansTeluguUI_Regular, _font_NotoSansTeluguUI_Regular_size, "ttf", default_font_size); - FontTelugu->set_antialiased(font_antialiased); - FontTelugu->set_hinting(font_hinting); - FontTelugu->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontTeluguBold; - FontTeluguBold.instantiate(); - FontTeluguBold->load_memory(_font_NotoSansTeluguUI_Bold, _font_NotoSansTeluguUI_Bold_size, "ttf", default_font_size); - FontTeluguBold->set_antialiased(font_antialiased); - FontTeluguBold->set_hinting(font_hinting); - FontTeluguBold->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontThai; - FontThai.instantiate(); - FontThai->load_memory(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, "ttf", default_font_size); - FontThai->set_antialiased(font_antialiased); - FontThai->set_hinting(font_hinting); - FontThai->set_force_autohinter(true); //just looks better..i think? - - Ref<FontData> FontThaiBold; - FontThaiBold.instantiate(); - FontThaiBold->load_memory(_font_NotoSansThaiUI_Bold, _font_NotoSansThaiUI_Bold_size, "ttf", default_font_size); - FontThaiBold->set_antialiased(font_antialiased); - FontThaiBold->set_hinting(font_hinting); - FontThaiBold->set_force_autohinter(true); //just looks better..i think? - - /* Droid Sans Fallback */ - - Ref<FontData> FontFallback; - FontFallback.instantiate(); - FontFallback->load_memory(_font_DroidSansFallback, _font_DroidSansFallback_size, "ttf", default_font_size); - FontFallback->set_antialiased(font_antialiased); - FontFallback->set_hinting(font_hinting); - FontFallback->set_force_autohinter(true); //just looks better..i think? - - /* Droid Sans Japanese */ - - Ref<FontData> FontJapanese; - FontJapanese.instantiate(); - FontJapanese->load_memory(_font_DroidSansJapanese, _font_DroidSansJapanese_size, "ttf", default_font_size); - FontJapanese->set_antialiased(font_antialiased); - FontJapanese->set_hinting(font_hinting); - FontJapanese->set_force_autohinter(true); //just looks better..i think? + /* Noto Sans */ + + Ref<FontData> DefaultFont = load_cached_internal_font(_font_NotoSans_Regular, _font_NotoSans_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> DefaultFontBold = load_cached_internal_font(_font_NotoSans_Bold, _font_NotoSans_Bold_size, font_hinting, font_antialiased, true); + Ref<FontData> FontArabic = load_cached_internal_font(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> FontArabicBold = load_cached_internal_font(_font_NotoNaskhArabicUI_Bold, _font_NotoNaskhArabicUI_Bold_size, font_hinting, font_antialiased, true); + Ref<FontData> FontBengali = load_cached_internal_font(_font_NotoSansBengaliUI_Regular, _font_NotoSansBengaliUI_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> FontBengaliBold = load_cached_internal_font(_font_NotoSansBengaliUI_Bold, _font_NotoSansBengaliUI_Bold_size, font_hinting, font_antialiased, true); + Ref<FontData> FontDevanagari = load_cached_internal_font(_font_NotoSansDevanagariUI_Regular, _font_NotoSansDevanagariUI_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> FontDevanagariBold = load_cached_internal_font(_font_NotoSansDevanagariUI_Bold, _font_NotoSansDevanagariUI_Bold_size, font_hinting, font_antialiased, true); + Ref<FontData> FontGeorgian = load_cached_internal_font(_font_NotoSansGeorgian_Regular, _font_NotoSansGeorgian_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> FontGeorgianBold = load_cached_internal_font(_font_NotoSansGeorgian_Bold, _font_NotoSansGeorgian_Bold_size, font_hinting, font_antialiased, true); + Ref<FontData> FontHebrew = load_cached_internal_font(_font_NotoSansHebrew_Regular, _font_NotoSansHebrew_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> FontHebrewBold = load_cached_internal_font(_font_NotoSansHebrew_Bold, _font_NotoSansHebrew_Bold_size, font_hinting, font_antialiased, true); + Ref<FontData> FontMalayalam = load_cached_internal_font(_font_NotoSansMalayalamUI_Regular, _font_NotoSansMalayalamUI_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> FontMalayalamBold = load_cached_internal_font(_font_NotoSansMalayalamUI_Bold, _font_NotoSansMalayalamUI_Bold_size, font_hinting, font_antialiased, true); + Ref<FontData> FontOriya = load_cached_internal_font(_font_NotoSansOriyaUI_Regular, _font_NotoSansOriyaUI_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> FontOriyaBold = load_cached_internal_font(_font_NotoSansOriyaUI_Bold, _font_NotoSansOriyaUI_Bold_size, font_hinting, font_antialiased, true); + Ref<FontData> FontSinhala = load_cached_internal_font(_font_NotoSansSinhalaUI_Regular, _font_NotoSansSinhalaUI_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> FontSinhalaBold = load_cached_internal_font(_font_NotoSansSinhalaUI_Bold, _font_NotoSansSinhalaUI_Bold_size, font_hinting, font_antialiased, true); + Ref<FontData> FontTamil = load_cached_internal_font(_font_NotoSansTamilUI_Regular, _font_NotoSansTamilUI_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> FontTamilBold = load_cached_internal_font(_font_NotoSansTamilUI_Bold, _font_NotoSansTamilUI_Bold_size, font_hinting, font_antialiased, true); + Ref<FontData> FontTelugu = load_cached_internal_font(_font_NotoSansTeluguUI_Regular, _font_NotoSansTeluguUI_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> FontTeluguBold = load_cached_internal_font(_font_NotoSansTeluguUI_Bold, _font_NotoSansTeluguUI_Bold_size, font_hinting, font_antialiased, true); + Ref<FontData> FontThai = load_cached_internal_font(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, font_hinting, font_antialiased, true); + Ref<FontData> FontThaiBold = load_cached_internal_font(_font_NotoSansThaiUI_Bold, _font_NotoSansThaiUI_Bold_size, font_hinting, font_antialiased, true); + + /* Droid Sans */ + + Ref<FontData> FontFallback = load_cached_internal_font(_font_DroidSansFallback, _font_DroidSansFallback_size, font_hinting, font_antialiased, true); + Ref<FontData> FontJapanese = load_cached_internal_font(_font_DroidSansJapanese, _font_DroidSansJapanese_size, font_hinting, font_antialiased, true); /* Hack */ - Ref<FontData> dfmono; - dfmono.instantiate(); - dfmono->load_memory(_font_Hack_Regular, _font_Hack_Regular_size, "ttf", default_font_size); - dfmono->set_antialiased(font_antialiased); - dfmono->set_hinting(font_hinting); - - Vector<String> subtag = String(EditorSettings::get_singleton()->get("interface/editor/code_font_custom_variations")).split(","); - Dictionary ftrs; - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - if (subtag_a.size() == 2) { - dfmono->set_variation(subtag_a[0], subtag_a[1].to_float()); - } - } + Ref<FontData> dfmono = load_cached_internal_font(_font_Hack_Regular, _font_Hack_Regular_size, font_hinting, font_antialiased, true); // Default font - MAKE_DEFAULT_FONT(df); + MAKE_DEFAULT_FONT(df, String(), default_font_size); p_theme->set_default_theme_font(df); // Default theme font p_theme->set_default_theme_font_size(default_font_size); @@ -404,7 +283,7 @@ void editor_register_fonts(Ref<Theme> p_theme) { p_theme->set_font("main", "EditorFonts", df); // Bold font - MAKE_BOLD_FONT(df_bold); + MAKE_BOLD_FONT(df_bold, String(), default_font_size); p_theme->set_font_size("bold_size", "EditorFonts", default_font_size); p_theme->set_font("bold", "EditorFonts", df_bold); @@ -430,7 +309,8 @@ void editor_register_fonts(Ref<Theme> p_theme) { p_theme->set_font_size("font_size", "HeaderLarge", default_font_size + 3 * EDSCALE); // Documentation fonts - MAKE_SOURCE_FONT(df_code); + String code_font_custom_variations = EditorSettings::get_singleton()->get("interface/editor/code_font_custom_variations"); + MAKE_SOURCE_FONT(df_code, code_font_custom_variations, default_font_size); p_theme->set_font_size("doc_size", "EditorFonts", int(EDITOR_GET("text_editor/help/help_font_size")) * EDSCALE); p_theme->set_font("doc", "EditorFonts", df); p_theme->set_font_size("doc_bold_size", "EditorFonts", int(EDITOR_GET("text_editor/help/help_font_size")) * EDSCALE); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index f92b9ac8ba..490c8f287f 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -30,6 +30,7 @@ #include "editor_help.h" +#include "core/core_constants.h" #include "core/input/input.h" #include "core/os/keyboard.h" #include "doc_data_compressed.gen.h" @@ -170,7 +171,7 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum) { if (t.is_empty()) { t = "void"; } - bool can_ref = (t != "void") || !p_enum.is_empty(); + bool can_ref = (t != "void" && t.find("*") == -1) || !p_enum.is_empty(); if (!p_enum.is_empty()) { if (p_enum.get_slice_count(".") > 1) { @@ -632,8 +633,8 @@ void EditorHelp::_update_doc() { continue; } } - // Ignore undocumented private. - if (cd.methods[i].name.begins_with("_") && cd.methods[i].description.is_empty()) { + // Ignore undocumented non virtual private. + if (cd.methods[i].name.begins_with("_") && cd.methods[i].description.is_empty() && cd.methods[i].qualifiers.find("virtual") == -1) { continue; } methods.push_back(cd.methods[i]); @@ -695,7 +696,7 @@ void EditorHelp::_update_doc() { class_desc->pop(); //cell } - if (m[i].description != "") { + if (m[i].description != "" || m[i].errors_returned.size() > 0) { method_descr = true; } @@ -1227,6 +1228,31 @@ void EditorHelp::_update_doc() { class_desc->push_color(text_color); class_desc->push_font(doc_font); class_desc->push_indent(1); + if (methods_filtered[i].errors_returned.size()) { + class_desc->append_bbcode(TTR("Error codes returned:")); + class_desc->add_newline(); + class_desc->push_list(0, RichTextLabel::LIST_DOTS, false); + for (int j = 0; j < methods_filtered[i].errors_returned.size(); j++) { + if (j > 0) { + class_desc->add_newline(); + } + int val = methods_filtered[i].errors_returned[j]; + String text = itos(val); + for (int k = 0; k < CoreConstants::get_global_constant_count(); k++) { + if (CoreConstants::get_global_constant_value(k) == val && CoreConstants::get_global_constant_enum(k) == SNAME("Error")) { + text = CoreConstants::get_global_constant_name(k); + break; + } + } + + class_desc->push_bold(); + class_desc->append_bbcode(text); + class_desc->pop(); + } + class_desc->pop(); + class_desc->add_newline(); + class_desc->add_newline(); + } if (!methods_filtered[i].description.strip_edges().is_empty()) { _add_text(DTR(methods_filtered[i].description)); } else { @@ -1302,6 +1328,8 @@ void EditorHelp::_help_callback(const String &p_topic) { } else if (what == "class_global") { if (constant_line.has(name)) { line = constant_line[name]; + } else if (method_line.has(name)) { + line = method_line[name]; } else { Map<String, Map<String, int>>::Element *iter = enum_values_line.front(); while (true) { @@ -1825,8 +1853,6 @@ void FindBar::_notification(int p_what) { } void FindBar::_bind_methods() { - ClassDB::bind_method("_unhandled_input", &FindBar::_unhandled_input); - ADD_SIGNAL(MethodInfo("search")); } @@ -1902,7 +1928,7 @@ void FindBar::_hide_bar() { hide(); } -void FindBar::_unhandled_input(const Ref<InputEvent> &p_event) { +void FindBar::unhandled_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventKey> k = p_event; diff --git a/editor/editor_help.h b/editor/editor_help.h index 69bb72c52d..0b0821a7f4 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -70,7 +70,7 @@ class FindBar : public HBoxContainer { protected: void _notification(int p_what); - void _unhandled_input(const Ref<InputEvent> &p_event); + virtual void unhandled_input(const Ref<InputEvent> &p_event) override; bool _search(bool p_search_previous = false); diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index 2b5eee4c1f..e56b10720d 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -71,7 +71,7 @@ void EditorHelpSearch::_search_box_gui_input(const Ref<InputEvent> &p_event) { case KEY_DOWN: case KEY_PAGEUP: case KEY_PAGEDOWN: { - results_tree->call("_gui_input", key); + results_tree->gui_input(key); search_box->accept_event(); } break; default: diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 8d2edd3000..d3841ad6c0 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -31,11 +31,13 @@ #include "editor_inspector.h" #include "array_property_edit.h" +#include "core/os/keyboard.h" #include "dictionary_property_edit.h" #include "editor/doc_tools.h" #include "editor_feature_profile.h" #include "editor_node.h" #include "editor_scale.h" +#include "editor_settings.h" #include "multi_node_edit.h" #include "scene/resources/packed_scene.h" @@ -244,9 +246,9 @@ void EditorProperty::_notification(int p_what) { Color color; if (draw_red) { - color = get_theme_color(SNAME("error_color")); + color = get_theme_color(is_read_only() ? SNAME("readonly_error_color") : SNAME("error_color")); } else { - color = get_theme_color(SNAME("property_color")); + color = get_theme_color(is_read_only() ? SNAME("readonly_color") : SNAME("property_color")); } if (label.find(".") != -1) { color.a = 0.5; //this should be un-hacked honestly, as it's used for editor overrides @@ -281,7 +283,7 @@ void EditorProperty::_notification(int p_what) { check_rect = Rect2(); } - if (can_revert) { + if (can_revert && !is_read_only()) { Ref<Texture2D> reload_icon = get_theme_icon(SNAME("ReloadSmall"), SNAME("EditorIcons")); text_limit -= reload_icon->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")) * 2; revert_rect = Rect2(text_limit + get_theme_constant(SNAME("hseparator"), SNAME("Tree")), (size.height - reload_icon->get_height()) / 2, reload_icon->get_width(), reload_icon->get_height()); @@ -382,8 +384,12 @@ void EditorProperty::update_property() { GDVIRTUAL_CALL(_update_property); } +void EditorProperty::_set_read_only(bool p_read_only) { +} + void EditorProperty::set_read_only(bool p_read_only) { read_only = p_read_only; + _set_read_only(p_read_only); } bool EditorProperty::is_read_only() const { @@ -692,7 +698,7 @@ bool EditorProperty::is_selected() const { return selected; } -void EditorProperty::_gui_input(const Ref<InputEvent> &p_event) { +void EditorProperty::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (property == StringName()) { @@ -782,6 +788,30 @@ void EditorProperty::_gui_input(const Ref<InputEvent> &p_event) { update(); emit_signal(SNAME("property_checked"), property, checked); } + } else if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + _ensure_popup(); + menu->set_position(get_screen_position() + get_local_mouse_position()); + menu->set_size(Vector2(1, 1)); + menu->popup(); + select(); + return; + } +} + +void EditorProperty::unhandled_key_input(const Ref<InputEvent> &p_event) { + if (!selected) { + return; + } + + if (ED_IS_SHORTCUT("property_editor/copy_property", p_event)) { + menu_option(MENU_COPY_PROPERTY); + accept_event(); + } else if (ED_IS_SHORTCUT("property_editor/paste_property", p_event) && !is_read_only()) { + menu_option(MENU_PASTE_PROPERTY); + accept_event(); + } else if (ED_IS_SHORTCUT("property_editor/copy_property_path", p_event)) { + menu_option(MENU_COPY_PROPERTY_PATH); + accept_event(); } } @@ -895,6 +925,20 @@ String EditorProperty::get_tooltip_text() const { return tooltip_text; } +void EditorProperty::menu_option(int p_option) { + switch (p_option) { + case MENU_COPY_PROPERTY: { + EditorNode::get_singleton()->get_inspector()->set_property_clipboard(object->get(property)); + } break; + case MENU_PASTE_PROPERTY: { + emit_changed(property, EditorNode::get_singleton()->get_inspector()->get_property_clipboard()); + } break; + case MENU_COPY_PROPERTY_PATH: { + DisplayServer::get_singleton()->clipboard_set(property); + } break; + } +} + void EditorProperty::_bind_methods() { ClassDB::bind_method(D_METHOD("set_label", "text"), &EditorProperty::set_label); ClassDB::bind_method(D_METHOD("get_label"), &EditorProperty::get_label); @@ -920,8 +964,6 @@ void EditorProperty::_bind_methods() { ClassDB::bind_method(D_METHOD("get_edited_property"), &EditorProperty::get_edited_property); ClassDB::bind_method(D_METHOD("get_edited_object"), &EditorProperty::get_edited_object); - ClassDB::bind_method(D_METHOD("_gui_input"), &EditorProperty::_gui_input); - ClassDB::bind_method(D_METHOD("get_tooltip_text"), &EditorProperty::get_tooltip_text); ClassDB::bind_method(D_METHOD("update_property"), &EditorProperty::update_property); @@ -973,6 +1015,21 @@ EditorProperty::EditorProperty() { label_reference = nullptr; bottom_editor = nullptr; delete_hover = false; + menu = nullptr; + set_process_unhandled_key_input(true); +} + +void EditorProperty::_ensure_popup() { + if (menu) { + return; + } + menu = memnew(PopupMenu); + menu->add_shortcut(ED_GET_SHORTCUT("property_editor/copy_property"), MENU_COPY_PROPERTY); + menu->add_shortcut(ED_GET_SHORTCUT("property_editor/paste_property"), MENU_PASTE_PROPERTY); + menu->add_shortcut(ED_GET_SHORTCUT("property_editor/copy_property_path"), MENU_COPY_PROPERTY_PATH); + menu->connect("id_pressed", callable_mp(this, &EditorProperty::menu_option)); + menu->set_item_disabled(MENU_PASTE_PROPERTY, is_read_only()); + add_child(menu); } //////////////////////////////////////////////// @@ -1127,147 +1184,144 @@ EditorInspectorCategory::EditorInspectorCategory() { void EditorInspectorSection::_test_unfold() { if (!vbox_added) { add_child(vbox); + move_child(vbox, 0); vbox_added = true; } } void EditorInspectorSection::_notification(int p_what) { - if (p_what == NOTIFICATION_SORT_CHILDREN) { - Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); - int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); - - Ref<Texture2D> arrow; - - if (foldable) { - if (object->editor_is_section_unfolded(section)) { - arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); - } else { - if (is_layout_rtl()) { - arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + minimum_size_changed(); + } break; + case NOTIFICATION_SORT_CHILDREN: { + if (!vbox_added) { + return; + } + // Get the section header font. + Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + + // Get the right direction arrow texture, if the section is foldable. + Ref<Texture2D> arrow; + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); } else { - arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + if (is_layout_rtl()) { + arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + } else { + arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + } } } - } - - Size2 size = get_size(); - Point2 offset; - Rect2 rect; - offset.y = font->get_height(font_size); - if (arrow.is_valid()) { - offset.y = MAX(offset.y, arrow->get_height()); - } - - offset.y += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - if (is_layout_rtl()) { - rect = Rect2(offset, size - offset - Vector2(get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")), 0)); - } else { - offset.x += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); - rect = Rect2(offset, size - offset); - } - //set children - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c) { - continue; - } - if (c->is_set_as_top_level()) { - continue; - } - if (!c->is_visible_in_tree()) { - continue; + // Compute the height of the section header. + int header_height = font->get_height(font_size); + if (arrow.is_valid()) { + header_height = MAX(header_height, arrow->get_height()); } + header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - fit_child_in_rect(c, rect); - } - - update(); //need to redraw text - } + int inspector_margin = get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); + Size2 size = get_size() - Vector2(inspector_margin, 0); + Vector2 offset = Vector2(is_layout_rtl() ? 0 : inspector_margin, header_height); + for (int i = 0; i < get_child_count(); i++) { + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) { + continue; + } + if (c->is_set_as_top_level()) { + continue; + } - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> arrow; - bool rtl = is_layout_rtl(); + fit_child_in_rect(c, Rect2(offset, size)); + } + } break; + case NOTIFICATION_DRAW: { + // Get the section header font. + Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); - if (foldable) { - if (object->editor_is_section_unfolded(section)) { - arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); - } else { - if (is_layout_rtl()) { - arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + // Get the right direction arrow texture, if the section is foldable. + Ref<Texture2D> arrow; + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); } else { - arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + if (is_layout_rtl()) { + arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + } else { + arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + } } } - } - - Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); - int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); - - int h = font->get_height(font_size); - if (arrow.is_valid()) { - h = MAX(h, arrow->get_height()); - } - h += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - - Color c = bg_color; - c.a *= 0.4; - draw_rect(Rect2(Vector2(), Vector2(get_size().width, h)), c); - const int arrow_margin = 2; - const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0; - Color color = get_theme_color(SNAME("font_color")); - float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE); - draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (h - font->get_height(font_size)) / 2).floor(), label, rtl ? HALIGN_RIGHT : HALIGN_LEFT, text_width, font_size, color); + bool rtl = is_layout_rtl(); - if (arrow.is_valid()) { - if (rtl) { - draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (h - arrow->get_height()) / 2).floor()); - } else { - draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (h - arrow->get_height()) / 2).floor()); + // Compute the height of the section header. + int header_height = font->get_height(font_size); + if (arrow.is_valid()) { + header_height = MAX(header_height, arrow->get_height()); } - } + header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - if (dropping && !vbox->is_visible_in_tree()) { - Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - draw_rect(Rect2(Point2(), get_size()), accent_color, false); - } - } + Color c = bg_color; + c.a *= 0.4; + draw_rect(Rect2(Vector2(), Vector2(get_size().width, header_height)), c); - if (p_what == NOTIFICATION_DRAG_BEGIN) { - Dictionary dd = get_viewport()->gui_get_drag_data(); + const int arrow_margin = 2; + const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0; + Color color = get_theme_color(SNAME("font_color")); + float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE); + draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2).floor(), label, rtl ? HALIGN_RIGHT : HALIGN_LEFT, text_width, font_size, color); - // Only allow dropping if the section contains properties which can take the dragged data. - bool children_can_drop = false; - for (int child_idx = 0; child_idx < vbox->get_child_count(); child_idx++) { - Control *editor_property = Object::cast_to<Control>(vbox->get_child(child_idx)); + if (arrow.is_valid()) { + if (rtl) { + draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor()); + } else { + draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor()); + } + } - // Test can_drop_data and can_drop_data_fw, since can_drop_data only works if set up with forwarding or if script attached. - if (editor_property && (editor_property->can_drop_data(Point2(), dd) || editor_property->call("_can_drop_data_fw", Point2(), dd, this))) { - children_can_drop = true; - break; + if (dropping && !vbox->is_visible_in_tree()) { + Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + draw_rect(Rect2(Point2(), get_size()), accent_color, false); } - } + } break; + case NOTIFICATION_DRAG_BEGIN: { + Dictionary dd = get_viewport()->gui_get_drag_data(); - dropping = children_can_drop; - update(); - } + // Only allow dropping if the section contains properties which can take the dragged data. + bool children_can_drop = false; + for (int child_idx = 0; child_idx < vbox->get_child_count(); child_idx++) { + Control *editor_property = Object::cast_to<Control>(vbox->get_child(child_idx)); - if (p_what == NOTIFICATION_DRAG_END) { - dropping = false; - update(); - } + // Test can_drop_data and can_drop_data_fw, since can_drop_data only works if set up with forwarding or if script attached. + if (editor_property && (editor_property->can_drop_data(Point2(), dd) || editor_property->call("_can_drop_data_fw", Point2(), dd, this))) { + children_can_drop = true; + break; + } + } - if (p_what == NOTIFICATION_MOUSE_ENTER) { - if (dropping) { - dropping_unfold_timer->start(); - } - } + dropping = children_can_drop; + update(); + } break; + case NOTIFICATION_DRAG_END: { + dropping = false; + update(); + } break; + case NOTIFICATION_MOUSE_ENTER: { + if (dropping) { + dropping_unfold_timer->start(); + } + } break; - if (p_what == NOTIFICATION_MOUSE_EXIT) { - if (dropping) { - dropping_unfold_timer->stop(); - } + case NOTIFICATION_MOUSE_EXIT: { + if (dropping) { + dropping_unfold_timer->stop(); + } + } break; } } @@ -1306,6 +1360,7 @@ void EditorInspectorSection::setup(const String &p_section, const String &p_labe if (!foldable && !vbox_added) { add_child(vbox); + move_child(vbox, 0); vbox_added = true; } @@ -1319,7 +1374,7 @@ void EditorInspectorSection::setup(const String &p_section, const String &p_labe } } -void EditorInspectorSection::_gui_input(const Ref<InputEvent> &p_event) { +void EditorInspectorSection::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!foldable) { @@ -1365,7 +1420,7 @@ void EditorInspectorSection::fold() { } if (!vbox_added) { - return; //kinda pointless + return; } object->editor_set_section_unfold(section, false); @@ -1378,7 +1433,6 @@ void EditorInspectorSection::_bind_methods() { ClassDB::bind_method(D_METHOD("get_vbox"), &EditorInspectorSection::get_vbox); ClassDB::bind_method(D_METHOD("unfold"), &EditorInspectorSection::unfold); ClassDB::bind_method(D_METHOD("fold"), &EditorInspectorSection::fold); - ClassDB::bind_method(D_METHOD("_gui_input"), &EditorInspectorSection::_gui_input); } EditorInspectorSection::EditorInspectorSection() { @@ -1403,6 +1457,732 @@ EditorInspectorSection::~EditorInspectorSection() { //////////////////////////////////////////////// //////////////////////////////////////////////// +int EditorInspectorArray::_get_array_count() { + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + return _extract_properties_as_array(object_property_list).size(); + } else if (mode == MODE_USE_COUNT_PROPERTY) { + bool valid; + int count = object->get(count_property, &valid); + ERR_FAIL_COND_V_MSG(!valid, 0, vformat("%s is not a valid property to be used as array count.", count_property)); + return count; + } + return 0; +} + +void EditorInspectorArray::_add_button_pressed() { + _move_element(-1, -1); +} + +void EditorInspectorArray::_first_page_button_pressed() { + emit_signal("page_change_request", 0); +} + +void EditorInspectorArray::_prev_page_button_pressed() { + emit_signal("page_change_request", MAX(0, page - 1)); +} + +void EditorInspectorArray::_page_line_edit_text_submitted(String p_text) { + if (p_text.is_valid_int()) { + int new_page = p_text.to_int() - 1; + new_page = MIN(MAX(0, new_page), max_page); + page_line_edit->set_text(Variant(new_page)); + emit_signal("page_change_request", new_page); + } else { + page_line_edit->set_text(Variant(page)); + } +} + +void EditorInspectorArray::_next_page_button_pressed() { + emit_signal("page_change_request", MIN(max_page, page + 1)); +} + +void EditorInspectorArray::_last_page_button_pressed() { + emit_signal("page_change_request", max_page); +} + +void EditorInspectorArray::_rmb_popup_id_pressed(int p_id) { + switch (p_id) { + case OPTION_MOVE_UP: + if (popup_array_index_pressed > 0) { + _move_element(popup_array_index_pressed, popup_array_index_pressed - 1); + } + break; + case OPTION_MOVE_DOWN: + if (popup_array_index_pressed < count - 1) { + _move_element(popup_array_index_pressed, popup_array_index_pressed + 2); + } + break; + case OPTION_NEW_BEFORE: + _move_element(-1, popup_array_index_pressed); + break; + case OPTION_NEW_AFTER: + _move_element(-1, popup_array_index_pressed + 1); + break; + case OPTION_REMOVE: + _move_element(popup_array_index_pressed, -1); + break; + case OPTION_CLEAR_ARRAY: + _clear_array(); + break; + case OPTION_RESIZE_ARRAY: + new_size = count; + new_size_line_edit->set_text(Variant(new_size)); + resize_dialog->get_ok_button()->set_disabled(true); + resize_dialog->popup_centered(); + new_size_line_edit->grab_focus(); + new_size_line_edit->select_all(); + break; + default: + break; + } +} + +void EditorInspectorArray::_control_dropping_draw() { + int drop_position = _drop_position(); + + if (dropping && drop_position >= 0) { + Vector2 from; + Vector2 to; + if (drop_position < elements_vbox->get_child_count()) { + Transform2D xform = Object::cast_to<Control>(elements_vbox->get_child(drop_position))->get_transform(); + from = xform.xform(Vector2()); + to = xform.xform(Vector2(elements_vbox->get_size().x, 0)); + } else { + Control *child = Object::cast_to<Control>(elements_vbox->get_child(drop_position - 1)); + Transform2D xform = child->get_transform(); + from = xform.xform(Vector2(0, child->get_size().y)); + to = xform.xform(Vector2(elements_vbox->get_size().x, child->get_size().y)); + } + Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + control_dropping->draw_line(from, to, color, 2); + } +} + +void EditorInspectorArray::_vbox_visibility_changed() { + control_dropping->set_visible(vbox->is_visible_in_tree()); +} + +void EditorInspectorArray::_panel_draw(int p_index) { + ERR_FAIL_INDEX(p_index, (int)array_elements.size()); + + Ref<StyleBox> style = get_theme_stylebox("Focus", "EditorStyles"); + if (!style.is_valid()) { + return; + } + if (array_elements[p_index].panel->has_focus()) { + array_elements[p_index].panel->draw_style_box(style, Rect2(Vector2(), array_elements[p_index].panel->get_size())); + } +} + +void EditorInspectorArray::_panel_gui_input(Ref<InputEvent> p_event, int p_index) { + ERR_FAIL_INDEX(p_index, (int)array_elements.size()); + + Ref<InputEventKey> key_ref = p_event; + if (key_ref.is_valid()) { + const InputEventKey &key = **key_ref; + + if (array_elements[p_index].panel->has_focus() && key.is_pressed() && key.get_keycode() == KEY_DELETE) { + _move_element(begin_array_index + p_index, -1); + array_elements[p_index].panel->accept_event(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + popup_array_index_pressed = begin_array_index + p_index; + rmb_popup->set_item_disabled(OPTION_MOVE_UP, popup_array_index_pressed == 0); + rmb_popup->set_item_disabled(OPTION_MOVE_DOWN, popup_array_index_pressed == count - 1); + rmb_popup->set_position(mb->get_global_position()); + rmb_popup->set_size(Vector2()); + rmb_popup->popup(); + } + } +} + +void EditorInspectorArray::_move_element(int p_element_index, int p_to_pos) { + String action_name; + if (p_element_index < 0) { + action_name = vformat("Add element to property array with prefix %s.", array_element_prefix); + } else if (p_to_pos < 0) { + action_name = vformat("Remove element %d from property array with prefix %s.", p_element_index, array_element_prefix); + } else { + action_name = vformat("Move element %d to position %d in property array with prefix %s.", p_element_index, p_to_pos, array_element_prefix); + } + undo_redo->create_action(action_name); + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, p_element_index, p_to_pos }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + ERR_FAIL_COND(p_to_pos < -1 || p_to_pos > count); + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + + Array properties_as_array = _extract_properties_as_array(object_property_list); + properties_as_array.resize(count); + + // For undoing things + undo_redo->add_undo_property(object, count_property, properties_as_array.size()); + for (int i = 0; i < (int)properties_as_array.size(); i++) { + Dictionary d = Dictionary(properties_as_array[i]); + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_undo_property(object, vformat(key, i), d[key]); + } + } + + if (p_element_index < 0) { + // Add an element. + properties_as_array.insert(p_to_pos < 0 ? properties_as_array.size() : p_to_pos, Dictionary()); + } else if (p_to_pos < 0) { + // Delete the element. + properties_as_array.remove(p_element_index); + } else { + // Move the element. + properties_as_array.insert(p_to_pos, properties_as_array[p_element_index].duplicate()); + properties_as_array.remove(p_to_pos < p_element_index ? p_element_index + 1 : p_element_index); + } + + // Change the array size then set the properties. + undo_redo->add_do_property(object, count_property, properties_as_array.size()); + for (int i = 0; i < (int)properties_as_array.size(); i++) { + Dictionary d = properties_as_array[i]; + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_do_property(object, vformat(key, i), d[key]); + } + } + } + undo_redo->commit_action(); + + // Handle page change and update counts. + if (p_element_index < 0) { + int added_index = p_to_pos < 0 ? count : p_to_pos; + emit_signal("page_change_request", added_index / page_lenght); + count += 1; + } else if (p_to_pos < 0) { + count -= 1; + if (page == max_page && (MAX(0, count - 1) / page_lenght != max_page)) { + emit_signal("page_change_request", max_page - 1); + } + } + begin_array_index = page * page_lenght; + end_array_index = MIN(count, (page + 1) * page_lenght); + max_page = MAX(0, count - 1) / page_lenght; +} + +void EditorInspectorArray::_clear_array() { + undo_redo->create_action(vformat("Clear property array with prefix %s.", array_element_prefix)); + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + for (int i = count - 1; i >= 0; i--) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + + Array properties_as_array = _extract_properties_as_array(object_property_list); + properties_as_array.resize(count); + + // For undoing things + undo_redo->add_undo_property(object, count_property, count); + for (int i = 0; i < (int)properties_as_array.size(); i++) { + Dictionary d = Dictionary(properties_as_array[i]); + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_undo_property(object, vformat(key, i), d[key]); + } + } + + // Change the array size then set the properties. + undo_redo->add_do_property(object, count_property, 0); + } + undo_redo->commit_action(); + + // Handle page change and update counts. + emit_signal("page_change_request", 0); + count = 0; + begin_array_index = 0; + end_array_index = 0; + max_page = 0; +} + +void EditorInspectorArray::_resize_array(int p_size) { + ERR_FAIL_COND(p_size < 0); + if (p_size == count) { + return; + } + + undo_redo->create_action(vformat("Resize property array with prefix %s.", array_element_prefix)); + if (p_size > count) { + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + for (int i = count; i < p_size; i++) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, -1, -1 }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + undo_redo->add_undo_property(object, count_property, count); + undo_redo->add_do_property(object, count_property, p_size); + } + } else { + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + for (int i = count - 1; i > p_size - 1; i--) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + + Array properties_as_array = _extract_properties_as_array(object_property_list); + properties_as_array.resize(count); + + // For undoing things + undo_redo->add_undo_property(object, count_property, count); + for (int i = count - 1; i > p_size - 1; i--) { + Dictionary d = Dictionary(properties_as_array[i]); + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_undo_property(object, vformat(key, i), d[key]); + } + } + + // Change the array size then set the properties. + undo_redo->add_do_property(object, count_property, p_size); + } + } + undo_redo->commit_action(); + + // Handle page change and update counts. + emit_signal("page_change_request", 0); + /* + count = 0; + begin_array_index = 0; + end_array_index = 0; + max_page = 0; + */ +} + +Array EditorInspectorArray::_extract_properties_as_array(const List<PropertyInfo> &p_list) { + Array output; + + for (const PropertyInfo &pi : p_list) { + if (pi.name.begins_with(array_element_prefix)) { + String str = pi.name.trim_prefix(array_element_prefix); + + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + Error error = OK; + if (array_index >= output.size()) { + error = output.resize(array_index + 1); + } + if (error == OK) { + String format_string = String(array_element_prefix) + "%d" + str.substr(to_char_index); + Dictionary dict = output[array_index]; + dict[format_string] = object->get(pi.name); + output[array_index] = dict; + } else { + WARN_PRINT(vformat("Array element %s has an index too high. Array allocaiton failed.", pi.name)); + } + } + } + } + return output; +} + +int EditorInspectorArray::_drop_position() const { + for (int i = 0; i < (int)array_elements.size(); i++) { + const ArrayElement &ae = array_elements[i]; + + Size2 size = ae.panel->get_size(); + Vector2 mp = ae.panel->get_local_mouse_position(); + + if (Rect2(Vector2(), size).has_point(mp)) { + if (mp.y < size.y / 2) { + return i; + } else { + return i + 1; + } + } + } + return -1; +} + +void EditorInspectorArray::_new_size_line_edit_text_changed(String p_text) { + bool valid = false; + ; + if (p_text.is_valid_int()) { + int val = p_text.to_int(); + if (val > 0 && val != count) { + valid = true; + } + } + resize_dialog->get_ok_button()->set_disabled(!valid); +} + +void EditorInspectorArray::_new_size_line_edit_text_submitted(String p_text) { + bool valid = false; + ; + if (p_text.is_valid_int()) { + int val = p_text.to_int(); + if (val > 0 && val != count) { + new_size = val; + valid = true; + } + } + if (valid) { + resize_dialog->hide(); + _resize_array(new_size); + } else { + new_size_line_edit->set_text(Variant(new_size)); + } +} + +void EditorInspectorArray::_resize_dialog_confirmed() { + _new_size_line_edit_text_submitted(new_size_line_edit->get_text()); +} + +void EditorInspectorArray::_setup() { + // Setup counts. + count = _get_array_count(); + begin_array_index = page * page_lenght; + end_array_index = MIN(count, (page + 1) * page_lenght); + max_page = MAX(0, count - 1) / page_lenght; + array_elements.resize(MAX(0, end_array_index - begin_array_index)); + if (page < 0 || page > max_page) { + WARN_PRINT(vformat("Invalid page number %d", page)); + page = CLAMP(page, 0, max_page); + } + + for (int i = 0; i < (int)array_elements.size(); i++) { + ArrayElement &ae = array_elements[i]; + + // Panel and its hbox. + ae.panel = memnew(PanelContainer); + ae.panel->set_focus_mode(FOCUS_ALL); + ae.panel->set_mouse_filter(MOUSE_FILTER_PASS); + ae.panel->set_drag_forwarding(this); + ae.panel->set_meta("index", begin_array_index + i); + ae.panel->set_tooltip(vformat(TTR("Element %d: %s%d*"), i, array_element_prefix, i)); + ae.panel->connect("focus_entered", callable_mp((CanvasItem *)ae.panel, &PanelContainer::update)); + ae.panel->connect("focus_exited", callable_mp((CanvasItem *)ae.panel, &PanelContainer::update)); + ae.panel->connect("draw", callable_bind(callable_mp(this, &EditorInspectorArray::_panel_draw), i)); + ae.panel->connect("gui_input", callable_bind(callable_mp(this, &EditorInspectorArray::_panel_gui_input), i)); + ae.panel->add_theme_style_override(SNAME("panel"), i % 2 ? odd_style : even_style); + elements_vbox->add_child(ae.panel); + + ae.margin = memnew(MarginContainer); + ae.margin->set_mouse_filter(MOUSE_FILTER_PASS); + if (is_inside_tree()) { + Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size(); + ae.margin->add_theme_constant_override("margin_left", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_top", min_size.y / 2); + ae.margin->add_theme_constant_override("margin_right", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2); + } + ae.panel->add_child(ae.margin); + + ae.hbox = memnew(HBoxContainer); + ae.hbox->set_h_size_flags(SIZE_EXPAND_FILL); + ae.hbox->set_v_size_flags(SIZE_EXPAND_FILL); + ae.margin->add_child(ae.hbox); + + // Move button. + ae.move_texture_rect = memnew(TextureRect); + ae.move_texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + if (is_inside_tree()) { + ae.move_texture_rect->set_texture(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons"))); + } + ae.hbox->add_child(ae.move_texture_rect); + + // Right vbox. + ae.vbox = memnew(VBoxContainer); + ae.vbox->set_h_size_flags(SIZE_EXPAND_FILL); + ae.vbox->set_v_size_flags(SIZE_EXPAND_FILL); + ae.hbox->add_child(ae.vbox); + } + + // Hide/show the add button. + add_button->set_visible(page == max_page); + + if (max_page == 0) { + hbox_pagination->hide(); + } else { + // Update buttons. + first_page_button->set_disabled(page == 0); + prev_page_button->set_disabled(page == 0); + next_page_button->set_disabled(page == max_page); + last_page_button->set_disabled(page == max_page); + + // Update page number and page count. + page_line_edit->set_text(vformat("%d", page + 1)); + page_count_label->set_text(vformat("/ %d", max_page + 1)); + } +} + +Variant EditorInspectorArray::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + int index = p_from->get_meta("index"); + Dictionary dict; + dict["type"] = "property_array_element"; + dict["property_array_prefix"] = array_element_prefix; + dict["index"] = index; + + return dict; +} + +void EditorInspectorArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + Dictionary dict = p_data; + + int to_drop = dict["index"]; + int drop_position = _drop_position(); + if (drop_position < 0) { + return; + } + _move_element(to_drop, begin_array_index + drop_position); +} + +bool EditorInspectorArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + // First, update drawing. + control_dropping->update(); + + if (p_data.get_type() != Variant::DICTIONARY) { + return false; + } + Dictionary dict = p_data; + int drop_position = _drop_position(); + if (!dict.has("type") || dict["type"] != "property_array_element" || String(dict["property_array_prefix"]) != array_element_prefix || drop_position < 0) { + return false; + } + + // Check in dropping at the given index does indeed move the item. + int moved_array_index = (int)dict["index"]; + int drop_array_index = begin_array_index + drop_position; + + return drop_array_index != moved_array_index && drop_array_index - 1 != moved_array_index; +} + +void EditorInspectorArray::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + Color color = get_theme_color(SNAME("dark_color_1"), SNAME("Editor")); + odd_style->set_bg_color(color.lightened(0.15)); + even_style->set_bg_color(color.darkened(0.15)); + + for (int i = 0; i < (int)array_elements.size(); i++) { + ArrayElement &ae = array_elements[i]; + ae.move_texture_rect->set_texture(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons"))); + + Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size(); + ae.margin->add_theme_constant_override("margin_left", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_top", min_size.y / 2); + ae.margin->add_theme_constant_override("margin_right", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2); + } + + add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + first_page_button->set_icon(get_theme_icon(SNAME("PageFirst"), SNAME("EditorIcons"))); + prev_page_button->set_icon(get_theme_icon(SNAME("PagePrevious"), SNAME("EditorIcons"))); + next_page_button->set_icon(get_theme_icon(SNAME("PageNext"), SNAME("EditorIcons"))); + last_page_button->set_icon(get_theme_icon(SNAME("PageLast"), SNAME("EditorIcons"))); + minimum_size_changed(); + } break; + case NOTIFICATION_DRAG_BEGIN: { + Dictionary dict = get_viewport()->gui_get_drag_data(); + if (dict.has("type") && dict["type"] == "property_array_element" && String(dict["property_array_prefix"]) == array_element_prefix) { + dropping = true; + control_dropping->update(); + } + } break; + case NOTIFICATION_DRAG_END: { + if (dropping) { + dropping = false; + control_dropping->update(); + } + } break; + } +} + +void EditorInspectorArray::_bind_methods() { + ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &EditorInspectorArray::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &EditorInspectorArray::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &EditorInspectorArray::drop_data_fw); + + ADD_SIGNAL(MethodInfo("page_change_request")); +} + +void EditorInspectorArray::set_undo_redo(UndoRedo *p_undo_redo) { + undo_redo = p_undo_redo; +} + +void EditorInspectorArray::setup_with_move_element_function(Object *p_object, String p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable) { + count_property = ""; + mode = MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION; + array_element_prefix = p_array_element_prefix; + page = p_page; + + EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable); + + _setup(); +} + +void EditorInspectorArray::setup_with_count_property(Object *p_object, String p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable) { + count_property = p_count_property; + mode = MODE_USE_COUNT_PROPERTY; + array_element_prefix = p_array_element_prefix; + page = p_page; + + EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable); + + _setup(); +} + +VBoxContainer *EditorInspectorArray::get_vbox(int p_index) { + if (p_index >= begin_array_index && p_index < end_array_index) { + return array_elements[p_index - begin_array_index].vbox; + } else if (p_index < 0) { + return vbox; + } else { + return nullptr; + } +} + +EditorInspectorArray::EditorInspectorArray() { + set_mouse_filter(Control::MOUSE_FILTER_STOP); + + odd_style.instantiate(); + even_style.instantiate(); + + rmb_popup = memnew(PopupMenu); + rmb_popup->add_item(TTR("Move Up"), OPTION_MOVE_UP); + rmb_popup->add_item(TTR("Move Down"), OPTION_MOVE_DOWN); + rmb_popup->add_separator(); + rmb_popup->add_item(TTR("Insert New Before"), OPTION_NEW_BEFORE); + rmb_popup->add_item(TTR("Insert New After"), OPTION_NEW_AFTER); + rmb_popup->add_separator(); + rmb_popup->add_item(TTR("Remove"), OPTION_REMOVE); + rmb_popup->add_separator(); + rmb_popup->add_item(TTR("Clear Array"), OPTION_CLEAR_ARRAY); + rmb_popup->add_item(TTR("Resize Array..."), OPTION_RESIZE_ARRAY); + rmb_popup->connect("id_pressed", callable_mp(this, &EditorInspectorArray::_rmb_popup_id_pressed)); + add_child(rmb_popup); + + elements_vbox = memnew(VBoxContainer); + elements_vbox->add_theme_constant_override("separation", 0); + vbox->add_child(elements_vbox); + + add_button = memnew(Button); + add_button->set_text(TTR("Add Element")); + add_button->set_text_align(Button::ALIGN_CENTER); + add_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_add_button_pressed)); + vbox->add_child(add_button); + + hbox_pagination = memnew(HBoxContainer); + hbox_pagination->set_h_size_flags(SIZE_EXPAND_FILL); + hbox_pagination->set_alignment(HBoxContainer::ALIGN_CENTER); + vbox->add_child(hbox_pagination); + + first_page_button = memnew(Button); + first_page_button->set_flat(true); + first_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_first_page_button_pressed)); + hbox_pagination->add_child(first_page_button); + + prev_page_button = memnew(Button); + prev_page_button->set_flat(true); + prev_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_prev_page_button_pressed)); + hbox_pagination->add_child(prev_page_button); + + page_line_edit = memnew(LineEdit); + page_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_page_line_edit_text_submitted)); + page_line_edit->add_theme_constant_override("minimum_character_width", 2); + hbox_pagination->add_child(page_line_edit); + + page_count_label = memnew(Label); + hbox_pagination->add_child(page_count_label); + next_page_button = memnew(Button); + next_page_button->set_flat(true); + next_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_next_page_button_pressed)); + hbox_pagination->add_child(next_page_button); + + last_page_button = memnew(Button); + last_page_button->set_flat(true); + last_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_last_page_button_pressed)); + hbox_pagination->add_child(last_page_button); + + control_dropping = memnew(Control); + control_dropping->connect("draw", callable_mp(this, &EditorInspectorArray::_control_dropping_draw)); + control_dropping->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + add_child(control_dropping); + + resize_dialog = memnew(AcceptDialog); + resize_dialog->set_title(TTRC("Resize Array")); + resize_dialog->add_cancel_button(); + resize_dialog->connect("confirmed", callable_mp(this, &EditorInspectorArray::_resize_dialog_confirmed)); + add_child(resize_dialog); + + VBoxContainer *resize_dialog_vbox = memnew(VBoxContainer); + resize_dialog->add_child(resize_dialog_vbox); + + new_size_line_edit = memnew(LineEdit); + new_size_line_edit->connect("text_changed", callable_mp(this, &EditorInspectorArray::_new_size_line_edit_text_changed)); + new_size_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_new_size_line_edit_text_submitted)); + resize_dialog_vbox->add_margin_child(TTRC("New Size:"), new_size_line_edit); + + vbox->connect("visibility_changed", callable_mp(this, &EditorInspectorArray::_vbox_visibility_changed)); +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS]; int EditorInspector::inspector_plugin_count = 0; @@ -1588,18 +2368,17 @@ void EditorInspector::update_tree() { valid_plugins.push_back(inspector_plugins[i]); } + // Decide if properties should be drawn in red. bool draw_red = false; - if (is_inside_tree()) { Node *nod = Object::cast_to<Node>(object); Node *es = EditorNode::get_singleton()->get_edited_scene(); if (nod && es != nod && nod->get_owner() != es) { + // Draw in red edited nodes that are not in the currently edited scene. draw_red = true; } } - // TreeItem *current_category = nullptr; - String filter = search_box ? search_box->get_text() : ""; String group; String group_base; @@ -1611,30 +2390,30 @@ void EditorInspector::update_tree() { object->get_property_list(&plist, true); _update_script_class_properties(*object, plist); - HashMap<String, VBoxContainer *> item_path; - Map<VBoxContainer *, EditorInspectorSection *> section_map; - - item_path[""] = main_vbox; + Map<VBoxContainer *, HashMap<String, VBoxContainer *>> vbox_per_path; + Map<String, EditorInspectorArray *> editor_inspector_array_per_prefix; Color sscolor = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); + // Get the lists of editors to add the beginning. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { ped->parse_begin(object); _parse_added_editors(main_vbox, ped); } - for (List<PropertyInfo>::Element *I = plist.front(); I; I = I->next()) { - PropertyInfo &p = I->get(); - - //make sure the property can be edited + // Get the lists of editors for properties. + for (List<PropertyInfo>::Element *E_property = plist.front(); E_property; E_property = E_property->next()) { + PropertyInfo &p = E_property->get(); if (p.usage & PROPERTY_USAGE_SUBGROUP) { + // Setup a property sub-group. subgroup = p.name; subgroup_base = p.hint_string; continue; } else if (p.usage & PROPERTY_USAGE_GROUP) { + // Setup a property group. group = p.name; group_base = p.hint_string; subgroup = ""; @@ -1643,6 +2422,7 @@ void EditorInspector::update_tree() { continue; } else if (p.usage & PROPERTY_USAGE_CATEGORY) { + // Setup a property category. group = ""; group_base = ""; subgroup = ""; @@ -1652,9 +2432,9 @@ void EditorInspector::update_tree() { continue; } - List<PropertyInfo>::Element *N = I->next(); + // Iterate over remaining properties. If no properties in category, skip the category. + List<PropertyInfo>::Element *N = E_property->next(); bool valid = true; - //if no properties in category, skip while (N) { if (N->get().usage & PROPERTY_USAGE_EDITOR && (!restrict_to_basic || (N->get().usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { break; @@ -1666,28 +2446,32 @@ void EditorInspector::update_tree() { N = N->next(); } if (!valid) { - continue; //empty, ignore + continue; // Empty, ignore it. } + // Create an EditorInspectorCategory and add it to the inspector. EditorInspectorCategory *category = memnew(EditorInspectorCategory); main_vbox->add_child(category); category_vbox = nullptr; //reset String type = p.name; + + // Set the category icon. if (!ClassDB::class_exists(type) && !ScriptServer::is_global_class(type) && p.hint_string.length() && FileAccess::exists(p.hint_string)) { - Ref<Script> s = ResourceLoader::load(p.hint_string, "Script"); + // If we have a category inside a script, search for the first script with a valid icon. + Ref<Script> script = ResourceLoader::load(p.hint_string, "Script"); String base_type; - if (s.is_valid()) { - base_type = s->get_instance_base_type(); + if (script.is_valid()) { + base_type = script->get_instance_base_type(); } - while (s.is_valid()) { - StringName name = EditorNode::get_editor_data().script_class_get_name(s->get_path()); + while (script.is_valid()) { + StringName name = EditorNode::get_editor_data().script_class_get_name(script->get_path()); String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name); if (name != StringName() && icon_path.length()) { category->icon = ResourceLoader::load(icon_path, "Texture"); break; } - s = s->get_base_script(); + script = script->get_base_script(); } if (category->icon.is_null() && has_theme_icon(base_type, SNAME("EditorIcons"))) { category->icon = get_theme_icon(base_type, SNAME("EditorIcons")); @@ -1698,9 +2482,12 @@ void EditorInspector::update_tree() { category->icon = EditorNode::get_singleton()->get_class_icon(type, "Object"); } } + + // Set the category label. category->label = type; if (use_doc_hints) { + // Sets the category tooltip to show documentation. StringName type2 = p.name; if (!class_descr_cache.has(type2)) { String descr; @@ -1715,6 +2502,7 @@ void EditorInspector::update_tree() { category->set_tooltip(p.name + "::" + (class_descr_cache[type2] == "" ? "" : class_descr_cache[type2])); } + // Add editors at the start of a category. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { ped->parse_category(object, p.name); _parse_added_editors(main_vbox, ped); @@ -1723,134 +2511,215 @@ void EditorInspector::update_tree() { continue; } else if (!(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) || (restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { + // Ignore properties that are not supposed to be in the inspector. continue; } if (p.name == "script") { - category_vbox = nullptr; // script should go into its own category + // Script should go into its own category. + category_vbox = nullptr; } if (p.usage & PROPERTY_USAGE_HIGH_END_GFX && RS::get_singleton()->is_low_end()) { - continue; //do not show this property in low end gfx + // Do not show this property in low end gfx. + continue; } if (p.name == "script" && (hide_script || bool(object->call("_hide_script_from_inspector")))) { + // Hide script variables from inspector if required. continue; } - String basename = p.name; + // Get the path for property. + String path = p.name; - if (subgroup != "") { - if (subgroup_base != "") { - if (basename.begins_with(subgroup_base)) { - basename = basename.replace_first(subgroup_base, ""); - } else if (subgroup_base.begins_with(basename)) { - //keep it, this is used pretty often - } else { - subgroup = ""; //no longer using subgroup base, clear + // First check if we have an array that fits the prefix. + String array_prefix = ""; + int array_index = -1; + for (Map<String, EditorInspectorArray *>::Element *E = editor_inspector_array_per_prefix.front(); E; E = E->next()) { + if (p.name.begins_with(E->key()) && E->key().length() > array_prefix.length()) { + array_prefix = E->key(); + } + } + + if (!array_prefix.is_empty()) { + // If we have an array element, find the according index in array. + String str = p.name.trim_prefix(array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; } + to_char_index++; + } + if (to_char_index > 0) { + array_index = str.left(to_char_index).to_int(); + } else { + array_prefix = ""; } } - if (group != "") { - if (group_base != "" && subgroup == "") { - if (basename.begins_with(group_base)) { - basename = basename.replace_first(group_base, ""); - } else if (group_base.begins_with(basename)) { - //keep it, this is used pretty often + + if (!array_prefix.is_empty()) { + path = path.trim_prefix(array_prefix); + int char_index = path.find("/"); + if (char_index >= 0) { + path = path.right(-char_index - 1); + } else { + path = vformat(TTR("Element %s"), array_index); + } + } else { + // Check if we exit or not a subgroup. If there is a prefix, remove it from the property label string. + if (subgroup != "" && subgroup_base != "") { + if (path.begins_with(subgroup_base)) { + path = path.trim_prefix(subgroup_base); + } else if (subgroup_base.begins_with(path)) { + // Keep it, this is used pretty often. + } else { + subgroup = ""; // The prefix changed, we are no longer in the subgroup. + } + } + + // Check if we exit or not a group. If there is a prefix, remove it from the property label string. + if (group != "" && group_base != "" && subgroup == "") { + if (path.begins_with(group_base)) { + path = path.trim_prefix(group_base); + } else if (group_base.begins_with(path)) { + // Keep it, this is used pretty often. } else { - group = ""; //no longer using group base, clear + group = ""; // The prefix changed, we are no longer in the group. subgroup = ""; } } - } - if (subgroup != "") { - basename = subgroup + "/" + basename; - } - if (group != "") { - basename = group + "/" + basename; - } - String name = (basename.find("/") != -1) ? basename.substr(basename.rfind("/") + 1) : basename; + // Add the group and subgroup to the path. + if (subgroup != "") { + path = subgroup + "/" + path; + } + if (group != "") { + path = group + "/" + path; + } + } + // Get the property label's string. + String property_label_string = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path; if (capitalize_paths) { - int dot = name.find("."); + // Capitalize paths. + int dot = property_label_string.find("."); if (dot != -1) { - String ov = name.substr(dot); - name = name.substr(0, dot); - name = name.capitalize(); - name += ov; - + String ov = property_label_string.substr(dot); + property_label_string = property_label_string.substr(0, dot); + property_label_string = property_label_string.capitalize(); + property_label_string += ov; } else { - name = name.capitalize(); + property_label_string = property_label_string.capitalize(); } } - String path; - { - int idx = basename.rfind("/"); - if (idx > -1) { - path = basename.left(idx); - } + // Remove the property from the path. + int idx = path.rfind("/"); + if (idx > -1) { + path = path.left(idx); + } else { + path = ""; } + // Ignore properties that do not fit the filter. if (use_filter && filter != "") { - String cat = path; - - if (capitalize_paths) { - cat = cat.capitalize(); - } - - if (!filter.is_subsequence_ofi(cat) && !filter.is_subsequence_ofi(name) && property_prefix.to_lower().find(filter.to_lower()) == -1) { + if (!filter.is_subsequence_ofi(path) && !filter.is_subsequence_ofi(property_label_string) && property_prefix.to_lower().find(filter.to_lower()) == -1) { continue; } } + // Recreate the category vbox if it was reset. if (category_vbox == nullptr) { category_vbox = memnew(VBoxContainer); main_vbox->add_child(category_vbox); } - VBoxContainer *current_vbox = main_vbox; + // Find the correct section/vbox to add the property editor to. + VBoxContainer *root_vbox = array_prefix.is_empty() ? main_vbox : editor_inspector_array_per_prefix[array_prefix]->get_vbox(array_index); + if (!root_vbox) { + continue; + } - { - String acc_path = ""; - int level = 1; - for (int i = 0; i < path.get_slice_count("/"); i++) { - String path_name = path.get_slice("/", i); - if (i > 0) { - acc_path += "/"; - } - acc_path += path_name; - if (!item_path.has(acc_path)) { - EditorInspectorSection *section = memnew(EditorInspectorSection); - current_vbox->add_child(section); - sections.push_back(section); - - if (capitalize_paths) { - path_name = path_name.capitalize(); - } + if (!vbox_per_path.has(root_vbox)) { + vbox_per_path[root_vbox] = HashMap<String, VBoxContainer *>(); + vbox_per_path[root_vbox][""] = root_vbox; + } + + VBoxContainer *current_vbox = root_vbox; + String acc_path = ""; + int level = 1; - Color c = sscolor; - c.a /= level; - section->setup(acc_path, path_name, object, c, use_folding); + Vector<String> components = path.split("/"); + for (int i = 0; i < components.size(); i++) { + String component = components[i]; + acc_path += (i > 0) ? "/" + component : component; - VBoxContainer *vb = section->get_vbox(); - item_path[acc_path] = vb; - section_map[vb] = section; + if (!vbox_per_path[root_vbox].has(acc_path)) { + // If the section does not exists, create it. + EditorInspectorSection *section = memnew(EditorInspectorSection); + current_vbox->add_child(section); + sections.push_back(section); + + if (capitalize_paths) { + component = component.capitalize(); } - current_vbox = item_path[acc_path]; - level = (MIN(level + 1, 4)); - } - if (current_vbox == main_vbox) { - //do not add directly to the main vbox, given it has no spacing - if (category_vbox == nullptr) { - category_vbox = memnew(VBoxContainer); + Color c = sscolor; + c.a /= level; + section->setup(acc_path, component, object, c, use_folding); + + vbox_per_path[root_vbox][acc_path] = section->get_vbox(); + } + + current_vbox = vbox_per_path[root_vbox][acc_path]; + level = (MIN(level + 1, 4)); + } + + // If we did not find a section to add the property to, add it to the category vbox instead (the category vbox handles margins correctly). + if (current_vbox == main_vbox) { + current_vbox = category_vbox; + } + + // Check if the property is an array counter, if so create a dedicated array editor for the array. + if (p.usage & PROPERTY_USAGE_ARRAY) { + EditorInspectorArray *editor_inspector_array = nullptr; + StringName array_element_prefix; + Color c = sscolor; + c.a /= level; + if (p.type == Variant::NIL) { + // Setup the array to use a method to create/move/delete elements. + array_element_prefix = p.class_name; + editor_inspector_array = memnew(EditorInspectorArray); + + String array_label = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path; + array_label = property_label_string.capitalize(); + int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; + editor_inspector_array->setup_with_move_element_function(object, array_label, array_element_prefix, page, c, use_folding); + editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request), varray(array_element_prefix)); + editor_inspector_array->set_undo_redo(undo_redo); + } else if (p.type == Variant::INT) { + // Setup the array to use the count property and built-in functions to create/move/delete elements. + Vector<String> class_name_components = String(p.class_name).split(","); + if (class_name_components.size() == 2) { + array_element_prefix = class_name_components[1]; + editor_inspector_array = memnew(EditorInspectorArray); + int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; + editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, use_folding); + editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request), varray(array_element_prefix)); + editor_inspector_array->set_undo_redo(undo_redo); } - current_vbox = category_vbox; } + + if (editor_inspector_array) { + current_vbox->add_child(editor_inspector_array); + editor_inspector_array_per_prefix[array_element_prefix] = editor_inspector_array; + } + continue; } + // Checkable and checked properties. bool checkable = false; bool checked = false; if (p.usage & PROPERTY_USAGE_CHECKABLE) { @@ -1858,6 +2727,9 @@ void EditorInspector::update_tree() { checked = p.usage & PROPERTY_USAGE_CHECKED; } + bool property_read_only = (p.usage & PROPERTY_USAGE_READ_ONLY) || read_only; + + // Mark properties that would require an editor restart (mostly when editing editor settings). if (p.usage & PROPERTY_USAGE_RESTART_IF_CHANGED) { restart_request_props.insert(p.name); } @@ -1865,14 +2737,19 @@ void EditorInspector::update_tree() { String doc_hint; if (use_doc_hints) { + // Build the doc hint, to use as tooltip. + + // Get the class name. StringName classname = object->get_class_name(); if (object_class != String()) { classname = object_class; } + StringName propname = property_prefix + p.name; String descr; bool found = false; + // Search for the property description in the cache. Map<StringName, Map<StringName, String>>::Element *E = descr_cache.find(classname); if (E) { Map<StringName, String>::Element *F = E->get().find(propname); @@ -1883,6 +2760,7 @@ void EditorInspector::update_tree() { } if (!found) { + // Build the property description String and add it to the cache. DocTools *dd = EditorHelp::get_doc_data(); Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(classname); while (F && descr == String()) { @@ -1915,17 +2793,18 @@ void EditorInspector::update_tree() { doc_hint = descr; } + // Seach for the inspector plugin that will handle the properties. Then add the correct property editor to it. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { bool exclusive = ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage, wide_editors); - List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; //make a copy, since plugins may be used again in a sub-inspector + List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; // Make a copy, since plugins may be used again in a sub-inspector. ped->added_editors.clear(); for (const EditorInspectorPlugin::AddedEditor &F : editors) { EditorProperty *ep = Object::cast_to<EditorProperty>(F.property_editor); if (ep) { - //set all this before the control gets the ENTER_TREE notification + // Set all this before the control gets the ENTER_TREE notification. ep->object = object; if (F.properties.size()) { @@ -1940,7 +2819,7 @@ void EditorInspector::update_tree() { ep->set_label(F.label); } else { // Use the existing one. - ep->set_label(name); + ep->set_label(property_label_string); } for (int i = 0; i < F.properties.size(); i++) { String prop = F.properties[i]; @@ -1956,18 +2835,16 @@ void EditorInspector::update_tree() { ep->set_checkable(checkable); ep->set_checked(checked); ep->set_keying(keying); - - ep->set_read_only(read_only); + ep->set_read_only(property_read_only); ep->set_deletable(deletable_properties); } current_vbox->add_child(F.property_editor); if (ep) { - ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed)); - if (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED) { - ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed_update_all), varray(), CONNECT_DEFERRED); - } + // Eventually, set other properties/signals after the property editor got added to the tree. + bool update_all = (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED); + ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed), varray(update_all)); ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed)); ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), varray(), CONNECT_DEFERRED); ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value)); @@ -1992,17 +2869,17 @@ void EditorInspector::update_tree() { } if (exclusive) { + // If we know the plugin is exclusive, we don't need to go through other plugins. break; } } } + // Get the lists of to add at the end. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { ped->parse_end(); _parse_added_editors(main_vbox, ped); } - - //see if this property exists and should be kept } void EditorInspector::update_property(const String &p_prop) { @@ -2041,6 +2918,7 @@ void EditorInspector::edit(Object *p_object) { _clear(); object->disconnect("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback)); } + per_array_page.clear(); object = p_object; @@ -2186,6 +3064,15 @@ void EditorInspector::set_use_deletable_properties(bool p_enabled) { deletable_properties = p_enabled; } +void EditorInspector::_page_change_request(int p_new_page, const StringName &p_array_prefix) { + int prev_page = per_array_page.has(p_array_prefix) ? per_array_page[p_array_prefix] : 0; + int new_page = MAX(0, p_new_page); + if (new_page != prev_page) { + per_array_page[p_array_prefix] = new_page; + update_tree_pending = true; + } +} + void EditorInspector::_edit_request_change(Object *p_object, const String &p_property) { if (object != p_object) { //may be undoing/redoing for a non edited object, so ignore return; @@ -2286,14 +3173,14 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo } } -void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) { +void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing, bool p_update_all) { // The "changing" variable must be true for properties that trigger events as typing occurs, // like "text_changed" signal. E.g. text property of Label, Button, RichTextLabel, etc. if (p_changing) { this->changing++; } - _edit_set(p_path, p_value, false, p_name); + _edit_set(p_path, p_value, p_update_all, p_name); if (p_changing) { this->changing--; @@ -2304,11 +3191,7 @@ void EditorInspector::_property_changed(const String &p_path, const Variant &p_v } } -void EditorInspector::_property_changed_update_all(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) { - update_tree(); -} - -void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values) { +void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing) { ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0); ERR_FAIL_COND(p_paths.size() != p_values.size()); String names; @@ -2325,9 +3208,13 @@ void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array emit_signal(SNAME("restart_requested")); } } - changing++; + if (p_changing) { + changing++; + } undo_redo->commit_action(); - changing--; + if (p_changing) { + changing--; + } } void EditorInspector::_property_keyed(const String &p_path, bool p_advance) { @@ -2604,13 +3491,15 @@ void EditorInspector::_update_script_class_properties(const Object &p_object, Li } // NodeC -> C props... -> NodeB..C.. - r_list.erase(script_variables); - List<PropertyInfo>::Element *to_delete = bottom->next(); - while (to_delete && !(to_delete->get().usage & PROPERTY_USAGE_CATEGORY)) { - r_list.erase(to_delete); - to_delete = bottom->next(); + if (script_variables) { + r_list.erase(script_variables); + List<PropertyInfo>::Element *to_delete = bottom->next(); + while (to_delete && !(to_delete->get().usage & PROPERTY_USAGE_CATEGORY)) { + r_list.erase(to_delete); + to_delete = bottom->next(); + } + r_list.erase(bottom); } - r_list.erase(bottom); } void EditorInspector::set_restrict_to_basic_settings(bool p_restrict) { @@ -2618,6 +3507,14 @@ void EditorInspector::set_restrict_to_basic_settings(bool p_restrict) { update_tree(); } +void EditorInspector::set_property_clipboard(const Variant &p_value) { + property_clipboard = p_value; +} + +Variant EditorInspector::get_property_clipboard() const { + return property_clipboard; +} + void EditorInspector::_bind_methods() { ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change); @@ -2660,6 +3557,7 @@ EditorInspector::EditorInspector() { property_focusable = -1; sub_inspector = false; deletable_properties = false; + property_clipboard = Variant(); get_v_scrollbar()->connect("value_changed", callable_mp(this, &EditorInspector::_vscroll_changed)); update_scroll_request = -1; @@ -2669,4 +3567,8 @@ EditorInspector::EditorInspector() { //used when class is created by the docgen to dump default values of everything bindable, editorsettings may not be created refresh_countdown = 0.33; } + + ED_SHORTCUT("property_editor/copy_property", TTR("Copy Property"), KEY_MASK_CMD | KEY_C); + ED_SHORTCUT("property_editor/paste_property", TTR("Paste Property"), KEY_MASK_CMD | KEY_V); + ED_SHORTCUT("property_editor/copy_property_path", TTR("Copy Property Path"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_C); } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 8cb4f1fbef..5992c23f8c 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -32,8 +32,12 @@ #define EDITOR_INSPECTOR_H #include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/dialogs.h" #include "scene/gui/line_edit.h" +#include "scene/gui/panel_container.h" #include "scene/gui/scroll_container.h" +#include "scene/gui/texture_rect.h" class UndoRedo; @@ -51,6 +55,13 @@ public: class EditorProperty : public Container { GDCLASS(EditorProperty, Container); +public: + enum MenuItems { + MENU_COPY_PROPERTY, + MENU_PASTE_PROPERTY, + MENU_COPY_PROPERTY_PATH, + }; + private: String label; int text_size; @@ -84,6 +95,7 @@ private: bool use_folding; bool draw_top_bg; + void _ensure_popup(); bool _is_property_different(const Variant &p_current, const Variant &p_orig); bool _get_instantiated_node_original_property(const StringName &p_prop, Variant &value); void _focusable_focused(int p_index); @@ -97,6 +109,7 @@ private: Vector<Control *> focusables; Control *label_reference; Control *bottom_editor; + PopupMenu *menu; mutable String tooltip_text; @@ -106,8 +119,10 @@ private: protected: void _notification(int p_what); static void _bind_methods(); + virtual void _set_read_only(bool p_read_only); - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; public: void emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field = StringName(), bool p_changing = false); @@ -175,6 +190,8 @@ public: bool can_revert_to_default() const { return can_revert; } + void menu_option(int p_option); + EditorProperty(); }; @@ -238,9 +255,7 @@ class EditorInspectorSection : public Container { String label; String section; - Object *object; - VBoxContainer *vbox; - bool vbox_added; //optimization + bool vbox_added; // Optimization. Color bg_color; bool foldable; @@ -250,9 +265,12 @@ class EditorInspectorSection : public Container { void _test_unfold(); protected: + Object *object; + VBoxContainer *vbox; + void _notification(int p_what); static void _bind_methods(); - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; public: virtual Size2 get_minimum_size() const override; @@ -268,6 +286,118 @@ public: ~EditorInspectorSection(); }; +class EditorInspectorArray : public EditorInspectorSection { + GDCLASS(EditorInspectorArray, EditorInspectorSection); + + UndoRedo *undo_redo; + + enum Mode { + MODE_NONE, + MODE_USE_COUNT_PROPERTY, + MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION, + } mode; + StringName count_property; + StringName array_element_prefix; + + int count = 0; + + VBoxContainer *elements_vbox; + + Control *control_dropping; + bool dropping = false; + + Button *add_button; + + AcceptDialog *resize_dialog; + int new_size = 0; + LineEdit *new_size_line_edit; + + // Pagination + int page_lenght = 5; + int page = 0; + int max_page = 0; + int begin_array_index = 0; + int end_array_index = 0; + HBoxContainer *hbox_pagination; + Button *first_page_button; + Button *prev_page_button; + LineEdit *page_line_edit; + Label *page_count_label; + Button *next_page_button; + Button *last_page_button; + + enum MenuOptions { + OPTION_MOVE_UP = 0, + OPTION_MOVE_DOWN, + OPTION_NEW_BEFORE, + OPTION_NEW_AFTER, + OPTION_REMOVE, + OPTION_CLEAR_ARRAY, + OPTION_RESIZE_ARRAY, + }; + int popup_array_index_pressed = -1; + PopupMenu *rmb_popup; + + struct ArrayElement { + PanelContainer *panel; + MarginContainer *margin; + HBoxContainer *hbox; + TextureRect *move_texture_rect; + VBoxContainer *vbox; + }; + LocalVector<ArrayElement> array_elements; + + Ref<StyleBoxFlat> odd_style; + Ref<StyleBoxFlat> even_style; + + int _get_array_count(); + void _add_button_pressed(); + + void _first_page_button_pressed(); + void _prev_page_button_pressed(); + void _page_line_edit_text_submitted(String p_text); + void _next_page_button_pressed(); + void _last_page_button_pressed(); + + void _rmb_popup_id_pressed(int p_id); + + void _control_dropping_draw(); + + void _vbox_visibility_changed(); + + void _panel_draw(int p_index); + void _panel_gui_input(Ref<InputEvent> p_event, int p_index); + void _move_element(int p_element_index, int p_to_pos); + void _clear_array(); + void _resize_array(int p_size); + Array _extract_properties_as_array(const List<PropertyInfo> &p_list); + int _drop_position() const; + + void _new_size_line_edit_text_changed(String p_text); + void _new_size_line_edit_text_submitted(String p_text); + void _resize_dialog_confirmed(); + + void _update_elements_visibility(); + void _setup(); + + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_undo_redo(UndoRedo *p_undo_redo); + + void setup_with_move_element_function(Object *p_object, String p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable); + void setup_with_count_property(Object *p_object, String p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable); + VBoxContainer *get_vbox(int p_index); + + EditorInspectorArray(); +}; + class EditorInspector : public ScrollContainer { GDCLASS(EditorInspector, ScrollContainer); @@ -321,14 +451,14 @@ class EditorInspector : public ScrollContainer { String property_prefix; //used for sectioned inspector String object_class; + Variant property_clipboard; bool restrict_to_basic = false; void _edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field); - void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false); - void _property_changed_update_all(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false); - void _multiple_properties_changed(Vector<String> p_paths, Array p_values); + void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false, bool p_update_all = false); + void _multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing = false); void _property_keyed(const String &p_path, bool p_advance); void _property_keyed_with_value(const String &p_path, const Variant &p_value, bool p_advance); void _property_deleted(const String &p_path); @@ -341,6 +471,9 @@ class EditorInspector : public ScrollContainer { void _node_removed(Node *p_node); + Map<StringName, int> per_array_page; + void _page_change_request(int p_new_page, const StringName &p_array_prefix); + void _changed_callback(); void _edit_request_change(Object *p_object, const String &p_prop); @@ -412,6 +545,8 @@ public: void set_use_deletable_properties(bool p_enabled); void set_restrict_to_basic_settings(bool p_restrict); + void set_property_clipboard(const Variant &p_value); + Variant get_property_clipboard() const; EditorInspector(); }; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 08585030de..cf4f8c0b7d 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -97,10 +97,14 @@ #include "editor/editor_translation_parser.h" #include "editor/export_template_manager.h" #include "editor/filesystem_dock.h" +#include "editor/import/dynamicfont_import_settings.h" #include "editor/import/editor_import_collada.h" #include "editor/import/resource_importer_bitmask.h" +#include "editor/import/resource_importer_bmfont.h" #include "editor/import/resource_importer_csv_translation.h" +#include "editor/import/resource_importer_dynamicfont.h" #include "editor/import/resource_importer_image.h" +#include "editor/import/resource_importer_imagefont.h" #include "editor/import/resource_importer_layered_texture.h" #include "editor/import/resource_importer_obj.h" #include "editor/import/resource_importer_scene.h" @@ -403,7 +407,7 @@ void EditorNode::_update_title() { DisplayServer::get_singleton()->window_set_title(title); } -void EditorNode::_unhandled_input(const Ref<InputEvent> &p_event) { +void EditorNode::unhandled_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventKey> k = p_event; @@ -1852,7 +1856,7 @@ void EditorNode::_dialog_action(String p_file) { ml = Ref<MeshLibrary>(memnew(MeshLibrary)); } - MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true); + MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true, file_export_lib_apply_xforms->is_pressed()); Error err = ResourceSaver::save(p_file, ml); if (err) { @@ -2088,7 +2092,6 @@ void EditorNode::_edit_current() { Object *prev_inspected_object = get_inspector()->get_edited_object(); - bool capitalize = bool(EDITOR_GET("interface/inspector/capitalize_properties")); bool disable_folding = bool(EDITOR_GET("interface/inspector/disable_folding")); bool is_resource = current_obj->is_class("Resource"); bool is_node = current_obj->is_class("Node"); @@ -2146,7 +2149,6 @@ void EditorNode::_edit_current() { if (current_obj->is_class("EditorDebuggerRemoteObject")) { editable_warning = TTR("This is a remote object, so changes to it won't be kept.\nPlease read the documentation relevant to debugging to better understand this workflow."); - capitalize = false; disable_folding = true; } else if (current_obj->is_class("MultiNodeEdit")) { Node *scene = get_edited_scene(); @@ -2183,10 +2185,6 @@ void EditorNode::_edit_current() { inspector_dock->set_warning(editable_warning); - if (get_inspector()->is_capitalize_paths_enabled() != capitalize) { - get_inspector()->set_enable_capitalize_paths(capitalize); - } - if (get_inspector()->is_using_folding() == disable_folding) { get_inspector()->set_use_folding(!disable_folding); } @@ -4766,6 +4764,10 @@ bool EditorNode::ensure_main_scene(bool p_from_native) { return true; } +Error EditorNode::run_play_native(int p_idx, int p_platform) { + return run_native->run_native(p_idx, p_platform); +} + void EditorNode::run_play() { _menu_option_confirm(RUN_STOP, true); _run(false); @@ -4800,6 +4802,32 @@ String EditorNode::get_run_playing_scene() const { return run_filename; } +void EditorNode::_immediate_dialog_confirmed() { + immediate_dialog_confirmed = true; +} +bool EditorNode::immediate_confirmation_dialog(const String &p_text, const String &p_ok_text, const String &p_cancel_text) { + ConfirmationDialog *cd = memnew(ConfirmationDialog); + cd->set_text(p_text); + cd->get_ok_button()->set_text(p_ok_text); + cd->get_cancel_button()->set_text(p_cancel_text); + cd->connect("confirmed", callable_mp(singleton, &EditorNode::_immediate_dialog_confirmed)); + singleton->gui_base->add_child(cd); + + cd->popup_centered(); + + while (true) { + OS::get_singleton()->delay_usec(1); + DisplayServer::get_singleton()->process_events(); + Main::iteration(); + if (singleton->immediate_dialog_confirmed || !cd->is_visible()) { + break; + } + } + + memdelete(cd); + return singleton->immediate_dialog_confirmed; +} + int EditorNode::get_current_tab() { return scene_tabs->get_current_tab(); } @@ -5588,7 +5616,6 @@ void EditorNode::_bind_methods() { ClassDB::bind_method("_editor_select", &EditorNode::_editor_select); ClassDB::bind_method("_node_renamed", &EditorNode::_node_renamed); ClassDB::bind_method("edit_node", &EditorNode::edit_node); - ClassDB::bind_method("_unhandled_input", &EditorNode::_unhandled_input); ClassDB::bind_method(D_METHOD("push_item", "object", "property", "inspector_only"), &EditorNode::push_item, DEFVAL(""), DEFVAL(false)); @@ -5828,6 +5855,18 @@ EditorNode::EditorNode() { import_texture_atlas.instantiate(); ResourceFormatImporter::get_singleton()->add_importer(import_texture_atlas); + Ref<ResourceImporterDynamicFont> import_font_data_dynamic; + import_font_data_dynamic.instantiate(); + ResourceFormatImporter::get_singleton()->add_importer(import_font_data_dynamic); + + Ref<ResourceImporterBMFont> import_font_data_bmfont; + import_font_data_bmfont.instantiate(); + ResourceFormatImporter::get_singleton()->add_importer(import_font_data_bmfont); + + Ref<ResourceImporterImageFont> import_font_data_image; + import_font_data_image.instantiate(); + ResourceFormatImporter::get_singleton()->add_importer(import_font_data_image); + Ref<ResourceImporterCSVTranslation> import_csv_translation; import_csv_translation.instantiate(); ResourceFormatImporter::get_singleton()->add_importer(import_csv_translation); @@ -6233,6 +6272,9 @@ EditorNode::EditorNode() { scene_import_settings = memnew(SceneImportSettings); gui_base->add_child(scene_import_settings); + fontdata_import_settings = memnew(DynamicFontImportSettings); + gui_base->add_child(fontdata_import_settings); + export_template_manager = memnew(ExportTemplateManager); gui_base->add_child(export_template_manager); @@ -6769,6 +6811,10 @@ EditorNode::EditorNode() { file_export_lib_merge->set_text(TTR("Merge With Existing")); file_export_lib_merge->set_pressed(true); file_export_lib->get_vbox()->add_child(file_export_lib_merge); + file_export_lib_apply_xforms = memnew(CheckBox); + file_export_lib_apply_xforms->set_text(TTR("Apply MeshInstance Transforms")); + file_export_lib_apply_xforms->set_pressed(false); + file_export_lib->get_vbox()->add_child(file_export_lib_apply_xforms); gui_base->add_child(file_export_lib); file_script = memnew(EditorFileDialog); @@ -6794,7 +6840,6 @@ EditorNode::EditorNode() { preview_gen = memnew(AudioStreamPreviewGenerator); add_child(preview_gen); - //plugin stuff add_editor_plugin(memnew(DebuggerEditorPlugin(this, debug_menu))); add_editor_plugin(memnew(DebugAdapterServer())); diff --git a/editor/editor_node.h b/editor/editor_node.h index 911139f470..488957b1df 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -92,6 +92,8 @@ class VSplitContainer; class Window; class SubViewport; class SceneImportSettings; +class EditorExtensionManager; +class DynamicFontImportSettings; class EditorNode : public Node { GDCLASS(EditorNode, Node); @@ -333,6 +335,7 @@ private: EditorFileDialog *file_script; EditorFileDialog *file_android_build_source; CheckBox *file_export_lib_merge; + CheckBox *file_export_lib_apply_xforms; String current_path; MenuButton *update_spinner; @@ -421,6 +424,7 @@ private: EditorResourcePreview *resource_preview; EditorFolding editor_folding; + DynamicFontImportSettings *fontdata_import_settings; SceneImportSettings *scene_import_settings; struct BottomPanelItem { String name; @@ -530,7 +534,7 @@ private: bool convert_old; - void _unhandled_input(const Ref<InputEvent> &p_event); + virtual void unhandled_input(const Ref<InputEvent> &p_event) override; static void _load_error_notify(void *p_ud, const String &p_text); @@ -675,6 +679,9 @@ private: void _pick_main_scene_custom_action(const String &p_custom_action_name); + bool immediate_dialog_confirmed = false; + void _immediate_dialog_confirmed(); + protected: void _notification(int p_what); @@ -892,12 +899,15 @@ public: bool ensure_main_scene(bool p_from_native); + Error run_play_native(int p_idx, int p_platform); void run_play(); void run_play_current(); void run_play_custom(const String &p_custom); void run_stop(); bool is_run_playing() const; String get_run_playing_scene() const; + + static bool immediate_confirmation_dialog(const String &p_text, const String &p_ok_text = TTR("Ok"), const String &p_cancel_text = TTR("Cancel")); }; struct EditorProgress { diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 99619cfc40..1729705be5 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -51,6 +51,10 @@ EditorPropertyNil::EditorPropertyNil() { ///////////////////// TEXT ///////////////////////// +void EditorPropertyText::_set_read_only(bool p_read_only) { + text->set_editable(!p_read_only); +}; + void EditorPropertyText::_text_submitted(const String &p_string) { if (updating) { return; @@ -68,9 +72,9 @@ void EditorPropertyText::_text_changed(const String &p_string) { } if (string_name) { - emit_changed(get_edited_property(), StringName(p_string), "", true); + emit_changed(get_edited_property(), StringName(p_string)); } else { - emit_changed(get_edited_property(), p_string, "", true); + emit_changed(get_edited_property(), p_string); } } @@ -108,6 +112,11 @@ EditorPropertyText::EditorPropertyText() { ///////////////////// MULTILINE TEXT ///////////////////////// +void EditorPropertyMultilineText::_set_read_only(bool p_read_only) { + text->set_editable(!p_read_only); + open_big_text->set_disabled(p_read_only); +}; + void EditorPropertyMultilineText::_big_text_changed() { text->set_text(big_text->get_text()); emit_changed(get_edited_property(), big_text->get_text(), "", true); @@ -180,6 +189,11 @@ EditorPropertyMultilineText::EditorPropertyMultilineText() { ///////////////////// TEXT ENUM ///////////////////////// +void EditorPropertyTextEnum::_set_read_only(bool p_read_only) { + option_button->set_disabled(p_read_only); + edit_button->set_disabled(p_read_only); +}; + void EditorPropertyTextEnum::_emit_changed_value(String p_string) { if (string_name) { emit_changed(get_edited_property(), StringName(p_string)); @@ -328,6 +342,11 @@ EditorPropertyTextEnum::EditorPropertyTextEnum() { ///////////////////// PATH ///////////////////////// +void EditorPropertyPath::_set_read_only(bool p_read_only) { + path->set_editable(!p_read_only); + path_edit->set_disabled(p_read_only); +}; + void EditorPropertyPath::_path_selected(const String &p_path) { emit_changed(get_edited_property(), p_path); update_property(); @@ -420,6 +439,10 @@ EditorPropertyPath::EditorPropertyPath() { ///////////////////// CLASS NAME ///////////////////////// +void EditorPropertyClassName::_set_read_only(bool p_read_only) { + property->set_disabled(p_read_only); +}; + void EditorPropertyClassName::setup(const String &p_base_type, const String &p_selected_type) { base_type = p_base_type; dialog->set_base_type(base_type); @@ -461,6 +484,10 @@ EditorPropertyClassName::EditorPropertyClassName() { ///////////////////// MEMBER ///////////////////////// +void EditorPropertyMember::_set_read_only(bool p_read_only) { + property->set_disabled(p_read_only); +}; + void EditorPropertyMember::_property_selected(const String &p_selected) { emit_changed(get_edited_property(), p_selected); update_property(); @@ -557,6 +584,11 @@ EditorPropertyMember::EditorPropertyMember() { } ///////////////////// CHECK ///////////////////////// + +void EditorPropertyCheck::_set_read_only(bool p_read_only) { + checkbox->set_disabled(p_read_only); +}; + void EditorPropertyCheck::_checkbox_pressed() { emit_changed(get_edited_property(), checkbox->is_pressed()); } @@ -580,6 +612,10 @@ EditorPropertyCheck::EditorPropertyCheck() { ///////////////////// ENUM ///////////////////////// +void EditorPropertyEnum::_set_read_only(bool p_read_only) { + options->set_disabled(p_read_only); +}; + void EditorPropertyEnum::_option_selected(int p_which) { int64_t val = options->get_item_metadata(p_which); emit_changed(get_edited_property(), val); @@ -628,6 +664,12 @@ EditorPropertyEnum::EditorPropertyEnum() { ///////////////////// FLAGS ///////////////////////// +void EditorPropertyFlags::_set_read_only(bool p_read_only) { + for (CheckBox *check : flags) { + check->set_disabled(p_read_only); + } +}; + void EditorPropertyFlags::_flag_toggled() { uint32_t value = 0; for (int i = 0; i < flags.size(); i++) { @@ -698,6 +740,7 @@ private: bool expanded = false; int expansion_rows = 0; int hovered_index = -1; + bool read_only = false; Size2 get_grid_size() const { Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); @@ -712,6 +755,10 @@ public: Vector<String> names; Vector<String> tooltips; + void set_read_only(bool p_read_only) { + read_only = p_read_only; + } + virtual Size2 get_minimum_size() const override { Size2 min_size = get_grid_size(); @@ -735,7 +782,10 @@ public: return String(); } - void _gui_input(const Ref<InputEvent> &p_ev) { + void gui_input(const Ref<InputEvent> &p_ev) override { + if (read_only) { + return; + } const Ref<InputEventMouseMotion> mm = p_ev; if (mm.is_valid()) { bool expand_was_hovered = expand_hovered; @@ -799,12 +849,12 @@ public: const int bsize = (grid_size.height * 80 / 100) / 2; const int h = bsize * 2 + 1; - Color color = get_theme_color(SNAME("highlight_color"), SNAME("Editor")); + Color color = get_theme_color(read_only ? SNAME("disabled_highlight_color") : SNAME("highlight_color"), SNAME("Editor")); - Color text_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); + Color text_color = get_theme_color(read_only ? SNAME("disabled_font_color") : SNAME("font_color"), SNAME("Editor")); text_color.a *= 0.5; - Color text_color_on = get_theme_color(SNAME("font_hover_color"), SNAME("Editor")); + Color text_color_on = get_theme_color(read_only ? SNAME("disabled_font_color") : SNAME("font_hover_color"), SNAME("Editor")); text_color_on.a *= 0.7; const int vofs = (grid_size.height - h) / 2; @@ -931,11 +981,15 @@ public: } static void _bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &EditorPropertyLayersGrid::_gui_input); ADD_SIGNAL(MethodInfo("flag_changed", PropertyInfo(Variant::INT, "flag"))); } }; +void EditorPropertyLayers::_set_read_only(bool p_read_only) { + button->set_disabled(p_read_only); + grid->set_read_only(p_read_only); +}; + void EditorPropertyLayers::_grid_changed(uint32_t p_grid) { emit_changed(get_edited_property(), p_grid); } @@ -1072,6 +1126,10 @@ EditorPropertyLayers::EditorPropertyLayers() { ///////////////////// INT ///////////////////////// +void EditorPropertyInteger::_set_read_only(bool p_read_only) { + spin->set_read_only(p_read_only); +}; + void EditorPropertyInteger::_value_changed(int64_t val) { if (setting) { return; @@ -1114,6 +1172,10 @@ EditorPropertyInteger::EditorPropertyInteger() { ///////////////////// OBJECT ID ///////////////////////// +void EditorPropertyObjectID::_set_read_only(bool p_read_only) { + edit->set_disabled(p_read_only); +}; + void EditorPropertyObjectID::_edit_pressed() { emit_signal(SNAME("object_id_selected"), get_edited_property(), get_edited_object()->get(get_edited_property())); } @@ -1152,6 +1214,10 @@ EditorPropertyObjectID::EditorPropertyObjectID() { ///////////////////// FLOAT ///////////////////////// +void EditorPropertyFloat::_set_read_only(bool p_read_only) { + spin->set_read_only(p_read_only); +}; + void EditorPropertyFloat::_value_changed(double val) { if (setting) { return; @@ -1198,7 +1264,14 @@ EditorPropertyFloat::EditorPropertyFloat() { ///////////////////// EASING ///////////////////////// +void EditorPropertyEasing::_set_read_only(bool p_read_only) { + spin->set_read_only(p_read_only); +}; + void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) { + if (is_read_only()) { + return; + } const Ref<InputEventMouseButton> mb = p_ev; if (mb.is_valid()) { if (mb->is_double_click() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { @@ -1272,12 +1345,12 @@ void EditorPropertyEasing::_draw_easing() { const Ref<Font> f = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); - const Color font_color = get_theme_color(SNAME("font_color"), SNAME("Label")); + const Color font_color = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit")); Color line_color; if (dragging) { line_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); } else { - line_color = get_theme_color(SNAME("font_color"), SNAME("Label")) * Color(1, 1, 1, 0.9); + line_color = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit")) * Color(1, 1, 1, 0.9); } Vector<Point2> points; @@ -1410,6 +1483,12 @@ EditorPropertyEasing::EditorPropertyEasing() { ///////////////////// VECTOR2 ///////////////////////// +void EditorPropertyVector2::_set_read_only(bool p_read_only) { + for (int i = 0; i < 2; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector2::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1493,6 +1572,12 @@ EditorPropertyVector2::EditorPropertyVector2(bool p_force_wide) { ///////////////////// RECT2 ///////////////////////// +void EditorPropertyRect2::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyRect2::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1590,6 +1675,12 @@ EditorPropertyRect2::EditorPropertyRect2(bool p_force_wide) { ///////////////////// VECTOR3 ///////////////////////// +void EditorPropertyVector3::_set_read_only(bool p_read_only) { + for (int i = 0; i < 3; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector3::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1702,6 +1793,12 @@ EditorPropertyVector3::EditorPropertyVector3(bool p_force_wide) { ///////////////////// VECTOR2i ///////////////////////// +void EditorPropertyVector2i::_set_read_only(bool p_read_only) { + for (int i = 0; i < 2; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector2i::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1785,6 +1882,12 @@ EditorPropertyVector2i::EditorPropertyVector2i(bool p_force_wide) { ///////////////////// RECT2i ///////////////////////// +void EditorPropertyRect2i::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyRect2i::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1882,6 +1985,12 @@ EditorPropertyRect2i::EditorPropertyRect2i(bool p_force_wide) { ///////////////////// VECTOR3i ///////////////////////// +void EditorPropertyVector3i::_set_read_only(bool p_read_only) { + for (int i = 0; i < 3; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector3i::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1966,6 +2075,12 @@ EditorPropertyVector3i::EditorPropertyVector3i(bool p_force_wide) { ///////////////////// PLANE ///////////////////////// +void EditorPropertyPlane::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyPlane::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2053,6 +2168,12 @@ EditorPropertyPlane::EditorPropertyPlane(bool p_force_wide) { ///////////////////// QUATERNION ///////////////////////// +void EditorPropertyQuaternion::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyQuaternion::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2137,6 +2258,12 @@ EditorPropertyQuaternion::EditorPropertyQuaternion() { ///////////////////// AABB ///////////////////////// +void EditorPropertyAABB::_set_read_only(bool p_read_only) { + for (int i = 0; i < 6; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyAABB::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2214,6 +2341,12 @@ EditorPropertyAABB::EditorPropertyAABB() { ///////////////////// TRANSFORM2D ///////////////////////// +void EditorPropertyTransform2D::_set_read_only(bool p_read_only) { + for (int i = 0; i < 6; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyTransform2D::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2290,6 +2423,12 @@ EditorPropertyTransform2D::EditorPropertyTransform2D() { ///////////////////// BASIS ///////////////////////// +void EditorPropertyBasis::_set_read_only(bool p_read_only) { + for (int i = 0; i < 9; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyBasis::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2372,6 +2511,12 @@ EditorPropertyBasis::EditorPropertyBasis() { ///////////////////// TRANSFORM ///////////////////////// +void EditorPropertyTransform3D::_set_read_only(bool p_read_only) { + for (int i = 0; i < 12; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyTransform3D::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2462,6 +2607,10 @@ EditorPropertyTransform3D::EditorPropertyTransform3D() { ////////////// COLOR PICKER ////////////////////// +void EditorPropertyColor::_set_read_only(bool p_read_only) { + picker->set_disabled(p_read_only); +}; + void EditorPropertyColor::_color_changed(const Color &p_color) { // Cancel the color change if the current color is identical to the new one. if (get_edited_object()->get(get_edited_property()) == p_color) { @@ -2534,6 +2683,11 @@ EditorPropertyColor::EditorPropertyColor() { ////////////// NODE PATH ////////////////////// +void EditorPropertyNodePath::_set_read_only(bool p_read_only) { + assign->set_disabled(p_read_only); + clear->set_disabled(p_read_only); +}; + void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { NodePath path = p_path; Node *base_node = nullptr; @@ -2679,6 +2833,10 @@ EditorPropertyRID::EditorPropertyRID() { ////////////// RESOURCE ////////////////////// +void EditorPropertyResource::_set_read_only(bool p_read_only) { + resource_picker->set_editable(!p_read_only); +}; + void EditorPropertyResource::_resource_selected(const RES &p_resource) { if (use_sub_inspector) { bool unfold = !get_edited_object()->editor_is_section_unfolded(get_edited_property()); diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 0cb21bb391..cee5ab96a7 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -59,6 +59,7 @@ class EditorPropertyText : public EditorProperty { void _text_submitted(const String &p_string); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -81,6 +82,7 @@ class EditorPropertyMultilineText : public EditorProperty { void _open_big_text(); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -115,6 +117,7 @@ class EditorPropertyTextEnum : public EditorProperty { void _custom_value_cancelled(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); @@ -139,6 +142,7 @@ class EditorPropertyPath : public EditorProperty { void _path_focus_exited(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); @@ -161,6 +165,7 @@ private: void _dialog_created(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -182,7 +187,6 @@ public: MEMBER_PROPERTY_OF_BASE_TYPE, ///< a property of a base type MEMBER_PROPERTY_OF_INSTANCE, ///< a property of an instance MEMBER_PROPERTY_OF_SCRIPT, ///< a property of a script & base - }; private: @@ -195,6 +199,7 @@ private: void _property_select(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -210,6 +215,7 @@ class EditorPropertyCheck : public EditorProperty { void _checkbox_pressed(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -224,6 +230,7 @@ class EditorPropertyEnum : public EditorProperty { void _option_selected(int p_which); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -242,6 +249,7 @@ class EditorPropertyFlags : public EditorProperty { void _flag_toggled(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -276,6 +284,7 @@ private: void _menu_pressed(int p_menu); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -291,6 +300,7 @@ class EditorPropertyInteger : public EditorProperty { void _value_changed(int64_t p_val); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -306,6 +316,7 @@ class EditorPropertyObjectID : public EditorProperty { void _edit_pressed(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -322,6 +333,7 @@ class EditorPropertyFloat : public EditorProperty { void _value_changed(double p_val); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -363,6 +375,7 @@ class EditorPropertyEasing : public EditorProperty { void _notification(int p_what); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -378,6 +391,7 @@ class EditorPropertyVector2 : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -394,6 +408,7 @@ class EditorPropertyRect2 : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -411,6 +426,7 @@ class EditorPropertyVector3 : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -429,6 +445,7 @@ class EditorPropertyVector2i : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -445,6 +462,7 @@ class EditorPropertyRect2i : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -461,6 +479,7 @@ class EditorPropertyVector3i : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -477,6 +496,7 @@ class EditorPropertyPlane : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -493,6 +513,7 @@ class EditorPropertyQuaternion : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -509,6 +530,7 @@ class EditorPropertyAABB : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -525,6 +547,7 @@ class EditorPropertyTransform2D : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -541,6 +564,7 @@ class EditorPropertyBasis : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -557,6 +581,7 @@ class EditorPropertyTransform3D : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -578,6 +603,7 @@ class EditorPropertyColor : public EditorProperty { Color last_color; protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -600,6 +626,7 @@ class EditorPropertyNodePath : public EditorProperty { void _node_clear(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); @@ -644,6 +671,7 @@ class EditorPropertyResource : public EditorProperty { void _update_property_bg(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp index e115cd77e1..5828549bdc 100644 --- a/editor/editor_run_native.cpp +++ b/editor/editor_run_native.cpp @@ -52,8 +52,8 @@ void EditorRunNative::_notification(int p_what) { small_icon.instantiate(); small_icon->create_from_image(im); MenuButton *mb = memnew(MenuButton); - mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::_run_native), varray(i)); - mb->connect("pressed", callable_mp(this, &EditorRunNative::_run_native), varray(-1, i)); + mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::run_native), varray(i)); + mb->connect("pressed", callable_mp(this, &EditorRunNative::run_native), varray(-1, i)); mb->set_icon(small_icon); add_child(mb); menus[i] = mb; @@ -93,22 +93,22 @@ void EditorRunNative::_notification(int p_what) { } } -void EditorRunNative::_run_native(int p_idx, int p_platform) { +Error EditorRunNative::run_native(int p_idx, int p_platform) { if (!EditorNode::get_singleton()->ensure_main_scene(true)) { resume_idx = p_idx; resume_platform = p_platform; - return; + return OK; } Ref<EditorExportPlatform> eep = EditorExport::get_singleton()->get_export_platform(p_platform); - ERR_FAIL_COND(eep.is_null()); + ERR_FAIL_COND_V(eep.is_null(), ERR_UNAVAILABLE); if (p_idx == -1) { if (eep->get_options_count() == 1) { menus[p_platform]->get_popup()->hide(); p_idx = 0; } else { - return; + return ERR_INVALID_PARAMETER; } } @@ -124,7 +124,7 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) { if (preset.is_null()) { EditorNode::get_singleton()->show_warning(TTR("No runnable export preset found for this platform.\nPlease add a runnable preset in the Export menu or define an existing preset as runnable.")); - return; + return ERR_UNAVAILABLE; } emit_signal(SNAME("native_run"), preset); @@ -149,11 +149,11 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) { flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION; } - eep->run(preset, p_idx, flags); + return eep->run(preset, p_idx, flags); } void EditorRunNative::resume_run_native() { - _run_native(resume_idx, resume_platform); + run_native(resume_idx, resume_platform); } void EditorRunNative::_bind_methods() { diff --git a/editor/editor_run_native.h b/editor/editor_run_native.h index 3516f668c6..97f6fc005a 100644 --- a/editor/editor_run_native.h +++ b/editor/editor_run_native.h @@ -43,13 +43,12 @@ class EditorRunNative : public HBoxContainer { int resume_idx; int resume_platform; - void _run_native(int p_idx, int p_platform); - protected: static void _bind_methods(); void _notification(int p_what); public: + Error run_native(int p_idx, int p_platform); bool is_deploy_debug_remote_enabled() const; void resume_run_native(); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 3fc010c701..8d579753c2 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -704,8 +704,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("editors/tiles_editor/grid_color", Color(1.0, 0.5, 0.2, 0.5)); // Polygon editor - _initial_set("editors/poly_editor/point_grab_radius", 8); - _initial_set("editors/poly_editor/show_previous_outline", true); + _initial_set("editors/polygon_editor/point_grab_radius", 8); + _initial_set("editors/polygon_editor/show_previous_outline", true); // Animation _initial_set("editors/animation/autorename_animation_tracks", true); diff --git a/editor/editor_settings.h b/editor/editor_settings.h index 6d28b26623..86e15f5ff5 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -31,13 +31,13 @@ #ifndef EDITOR_SETTINGS_H #define EDITOR_SETTINGS_H +#include "core/input/shortcut.h" #include "core/io/config_file.h" #include "core/io/resource.h" #include "core/object/class_db.h" #include "core/os/thread_safe.h" #include "core/string/translation.h" #include "editor/editor_paths.h" -#include "scene/gui/shortcut.h" class EditorPlugin; diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index 91f00deeaa..8cd636ddf3 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -52,7 +52,7 @@ String EditorSpinSlider::get_text_value() const { return TS->format_number(String::num(get_value(), Math::range_step_decimals(get_step()))); } -void EditorSpinSlider::_gui_input(const Ref<InputEvent> &p_event) { +void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (read_only) { @@ -221,7 +221,7 @@ void EditorSpinSlider::_draw_spin_slider() { bool rtl = is_layout_rtl(); Vector2 size = get_size(); - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); + Ref<StyleBox> sb = get_theme_stylebox(is_read_only() ? SNAME("read_only") : SNAME("normal"), SNAME("LineEdit")); if (!flat) { draw_style_box(sb, Rect2(Vector2(), size)); } @@ -233,7 +233,7 @@ void EditorSpinSlider::_draw_spin_slider() { int label_width = font->get_string_size(label, font_size).width; int number_width = size.width - sb->get_minimum_size().width - label_width - sep; - Ref<Texture2D> updown = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); + Ref<Texture2D> updown = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox")); if (get_step() == 1) { number_width -= updown->get_width(); @@ -243,7 +243,7 @@ void EditorSpinSlider::_draw_spin_slider() { int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size); - Color fc = get_theme_color(SNAME("font_color"), SNAME("LineEdit")); + Color fc = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit")); Color lc; if (use_custom_label_color) { lc = custom_label_color; @@ -299,7 +299,7 @@ void EditorSpinSlider::_draw_spin_slider() { TS->free(num_rid); if (get_step() == 1) { - Ref<Texture2D> updown2 = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); + Ref<Texture2D> updown2 = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox")); int updown_vofs = (size.height - updown2->get_height()) / 2; if (rtl) { updown_offset = sb->get_margin(SIDE_LEFT); @@ -564,8 +564,6 @@ void EditorSpinSlider::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flat", "flat"), &EditorSpinSlider::set_flat); ClassDB::bind_method(D_METHOD("is_flat"), &EditorSpinSlider::is_flat); - ClassDB::bind_method(D_METHOD("_gui_input"), &EditorSpinSlider::_gui_input); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "suffix"), "set_suffix", "get_suffix"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only"); diff --git a/editor/editor_spin_slider.h b/editor/editor_spin_slider.h index c09d084e88..1bf8e8eef9 100644 --- a/editor/editor_spin_slider.h +++ b/editor/editor_spin_slider.h @@ -85,7 +85,7 @@ class EditorSpinSlider : public Range { protected: void _notification(int p_what); - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; static void _bind_methods(); void _grabber_mouse_entered(); void _grabber_mouse_exited(); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 4496180ddd..8a08f4e450 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -395,12 +395,14 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { const Color separator_color = Color(mono_color.r, mono_color.g, mono_color.b, 0.1); const Color highlight_color = Color(accent_color.r, accent_color.g, accent_color.b, 0.275); + const Color disabled_highlight_color = highlight_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5); float prev_icon_saturation = theme->has_color("icon_saturation", "Editor") ? theme->get_color("icon_saturation", "Editor").r : 1.0; theme->set_color("icon_saturation", "Editor", Color(icon_saturation, icon_saturation, icon_saturation)); //can't save single float in theme, so using color theme->set_color("accent_color", "Editor", accent_color); theme->set_color("highlight_color", "Editor", highlight_color); + theme->set_color("disabled_highlight_color", "Editor", disabled_highlight_color); theme->set_color("base_color", "Editor", base_color); theme->set_color("dark_color_1", "Editor", dark_color_1); theme->set_color("dark_color_2", "Editor", dark_color_2); @@ -424,6 +426,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Color warning_color = Color(1, 0.87, 0.4); Color error_color = Color(1, 0.47, 0.42); Color property_color = font_color.lerp(Color(0.5, 0.5, 0.5), 0.5); + Color readonly_color = property_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5); + Color readonly_error_color = error_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5); if (!dark_theme) { // Darken some colors to be readable on a light background @@ -436,6 +440,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("warning_color", "Editor", warning_color); theme->set_color("error_color", "Editor", error_color); theme->set_color("property_color", "Editor", property_color); + theme->set_color("readonly_color", "Editor", readonly_color); + theme->set_color("readonly_error_color", "EditorProperty", readonly_error_color); if (!dark_theme) { theme->set_color("vulkan_color", "Editor", Color::hex(0xad1128ff)); @@ -696,6 +702,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("unchecked", "CheckBox", theme->get_icon("GuiUnchecked", "EditorIcons")); theme->set_icon("radio_checked", "CheckBox", theme->get_icon("GuiRadioChecked", "EditorIcons")); theme->set_icon("radio_unchecked", "CheckBox", theme->get_icon("GuiRadioUnchecked", "EditorIcons")); + theme->set_icon("checked_disabled", "CheckBox", theme->get_icon("GuiCheckedDisabled", "EditorIcons")); + theme->set_icon("unchecked_disabled", "CheckBox", theme->get_icon("GuiUncheckedDisabled", "EditorIcons")); + theme->set_icon("radio_checked_disabled", "CheckBox", theme->get_icon("GuiRadioCheckedDisabled", "EditorIcons")); + theme->set_icon("radio_unchecked_disabled", "CheckBox", theme->get_icon("GuiRadioUncheckedDisabled", "EditorIcons")); theme->set_color("font_color", "CheckBox", font_color); theme->set_color("font_hover_color", "CheckBox", font_hover_color); @@ -742,6 +752,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("unchecked", "PopupMenu", theme->get_icon("GuiUnchecked", "EditorIcons")); theme->set_icon("radio_checked", "PopupMenu", theme->get_icon("GuiRadioChecked", "EditorIcons")); theme->set_icon("radio_unchecked", "PopupMenu", theme->get_icon("GuiRadioUnchecked", "EditorIcons")); + theme->set_icon("checked_disabled", "PopupMenu", theme->get_icon("GuiCheckedDisabled", "EditorIcons")); + theme->set_icon("unchecked_disabled", "PopupMenu", theme->get_icon("GuiUncheckedDisabled", "EditorIcons")); + theme->set_icon("radio_checked_disabled", "PopupMenu", theme->get_icon("GuiRadioCheckedDisabled", "EditorIcons")); + theme->set_icon("radio_unchecked_disabled", "PopupMenu", theme->get_icon("GuiRadioUncheckedDisabled", "EditorIcons")); theme->set_icon("submenu", "PopupMenu", theme->get_icon("ArrowRight", "EditorIcons")); theme->set_icon("submenu_mirrored", "PopupMenu", theme->get_icon("ArrowLeft", "EditorIcons")); theme->set_icon("visibility_hidden", "PopupMenu", theme->get_icon("GuiVisibilityHidden", "EditorIcons")); @@ -803,6 +817,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_constant("vseparation", "EditorProperty", (extra_spacing + default_margin_size) * EDSCALE); theme->set_color("error_color", "EditorProperty", error_color); theme->set_color("property_color", "EditorProperty", property_color); + theme->set_color("readonly_color", "EditorProperty", readonly_color); + theme->set_color("readonly_error_color", "EditorProperty", readonly_error_color); Color inspector_section_color = font_color.lerp(Color(0.5, 0.5, 0.5), 0.35); theme->set_color("font_color", "EditorInspectorSection", inspector_section_color); @@ -1052,7 +1068,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("tab", "TextEdit", theme->get_icon("GuiTab", "EditorIcons")); theme->set_icon("space", "TextEdit", theme->get_icon("GuiSpace", "EditorIcons")); theme->set_color("font_color", "TextEdit", font_color); - theme->set_color("font_readonly_color", "LineEdit", font_readonly_color); + theme->set_color("font_readonly_color", "TextEdit", font_readonly_color); theme->set_color("caret_color", "TextEdit", font_color); theme->set_color("selection_color", "TextEdit", selection_color); theme->set_constant("line_spacing", "TextEdit", 4 * EDSCALE); @@ -1201,11 +1217,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // TooltipPanel Ref<StyleBoxFlat> style_tooltip = style_popup->duplicate(); style_tooltip->set_shadow_size(0); - style_tooltip->set_default_margin(SIDE_LEFT, default_margin_size * EDSCALE); + style_tooltip->set_default_margin(SIDE_LEFT, default_margin_size * EDSCALE * 0.5); style_tooltip->set_default_margin(SIDE_TOP, default_margin_size * EDSCALE * 0.5); - style_tooltip->set_default_margin(SIDE_RIGHT, default_margin_size * EDSCALE); + style_tooltip->set_default_margin(SIDE_RIGHT, default_margin_size * EDSCALE * 0.5); style_tooltip->set_default_margin(SIDE_BOTTOM, default_margin_size * EDSCALE * 0.5); - style_tooltip->set_bg_color(mono_color.inverted() * Color(1, 1, 1, 0.9)); + style_tooltip->set_bg_color(dark_color_3 * Color(0.8, 0.8, 0.8, 0.9)); style_tooltip->set_border_width_all(0); theme->set_color("font_color", "TooltipLabel", font_hover_color); theme->set_color("font_color_shadow", "TooltipLabel", Color(0, 0, 0, 0)); @@ -1216,6 +1232,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // SpinBox theme->set_icon("updown", "SpinBox", theme->get_icon("GuiSpinboxUpdown", "EditorIcons")); + theme->set_icon("updown_disabled", "SpinBox", theme->get_icon("GuiSpinboxUpdownDisabled", "EditorIcons")); // ProgressBar theme->set_stylebox("bg", "ProgressBar", make_stylebox(theme->get_icon("GuiProgressBar", "EditorIcons"), 4, 4, 4, 4, 0, 0, 0, 0)); @@ -1361,7 +1378,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_constant("label_width", "ColorPicker", 10 * EDSCALE); theme->set_icon("screen_picker", "ColorPicker", theme->get_icon("ColorPick", "EditorIcons")); theme->set_icon("add_preset", "ColorPicker", theme->get_icon("Add", "EditorIcons")); - theme->set_icon("preset_bg", "ColorPicker", theme->get_icon("GuiMiniCheckerboard", "EditorIcons")); + theme->set_icon("sample_bg", "ColorPicker", theme->get_icon("GuiMiniCheckerboard", "EditorIcons")); theme->set_icon("overbright_indicator", "ColorPicker", theme->get_icon("OverbrightIndicator", "EditorIcons")); theme->set_icon("bar_arrow", "ColorPicker", theme->get_icon("ColorPickerBarArrow", "EditorIcons")); theme->set_icon("picker_cursor", "ColorPicker", theme->get_icon("PickerCursor", "EditorIcons")); @@ -1369,6 +1386,13 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // ColorPickerButton theme->set_icon("bg", "ColorPickerButton", theme->get_icon("GuiMiniCheckerboard", "EditorIcons")); + // ColorPresetButton + Ref<StyleBoxFlat> preset_sb = make_flat_stylebox(Color(1, 1, 1), 2, 2, 2, 2, 2); + preset_sb->set_anti_aliased(false); + theme->set_stylebox("preset_fg", "ColorPresetButton", preset_sb); + theme->set_icon("preset_bg", "ColorPresetButton", theme->get_icon("GuiMiniCheckerboard", "EditorIcons")); + theme->set_icon("overbright_indicator", "ColorPresetButton", theme->get_icon("OverbrightIndicator", "EditorIcons")); + // Information on 3D viewport Ref<StyleBoxFlat> style_info_3d_viewport = style_default->duplicate(); style_info_3d_viewport->set_bg_color(style_info_3d_viewport->get_bg_color() * Color(1, 1, 1, 0.5)); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index daee61c2dd..5dd5c050e0 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2158,6 +2158,14 @@ bool FileSystemDock::can_drop_data_fw(const Point2 &p_point, const Variant &p_da return true; } + if (drag_data.has("type") && String(drag_data["type"]) == "nodes") { + // Save branch as scene. + String to_dir; + bool favorite; + _get_drag_target_folder(to_dir, favorite, p_point, p_from); + return !favorite && Array(drag_data["nodes"]).size() == 1; + } + return false; } @@ -2296,6 +2304,13 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, _update_tree(_compute_uncollapsed_paths()); } } + + if (drag_data.has("type") && String(drag_data["type"]) == "nodes") { + String to_dir; + bool favorite; + _get_drag_target_folder(to_dir, favorite, p_point, p_from); + EditorNode::get_singleton()->get_scene_tree_dock()->save_branch_to_file(to_dir); + } } void FileSystemDock::_get_drag_target_folder(String &target, bool &target_favorites, const Point2 &p_point, Control *p_from) const { diff --git a/editor/icons/AnimatableBody2D.svg b/editor/icons/AnimatableBody2D.svg new file mode 100644 index 0000000000..f4fed813c9 --- /dev/null +++ b/editor/icons/AnimatableBody2D.svg @@ -0,0 +1 @@ +<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#8da5f3" fill-opacity=".99" stroke-width="1.08904"><path d="m10.86822576 4.28299076h1.99947744v1.99947744h-1.99947744z"/><path d="m10.86822576 10.84273776h1.99947744v1.99947744h-1.99947744z"/><path d="m4.71256576 10.84273776h1.99947744v1.99947744h-1.99947744z"/></g><g fill="none" stroke="#8da5f3"><path d="m1.635 8.161v4.848c0 .713.579 1.293 1.292 1.293h9.857c.713 0 1.291-.58 1.291-1.293v-9.854c0-.714-.578-1.293-1.291-1.293h-5.526" stroke-width="1.07" transform="matrix(.939225 0 0 .938055 1.27996 1.07595)"/><path d="m1.339 1.364 2.539 2.539" stroke-width=".74" transform="matrix(2.04823 .655864 .655864 2.04823 -1.51683 -1.5267)"/><path d="m1.436 1.461 1.168 1.168" stroke-width="1.18" transform="matrix(1.69185 0 0 1.69185 4.50755 -.792876)"/><path d="m1.385 1.41 1.219 1.219" stroke-width="1.22" transform="matrix(1.63859 0 0 1.63859 -.688679 4.82985)"/></g></svg> diff --git a/editor/icons/AnimatableBody3D.svg b/editor/icons/AnimatableBody3D.svg new file mode 100644 index 0000000000..2e472f0625 --- /dev/null +++ b/editor/icons/AnimatableBody3D.svg @@ -0,0 +1 @@ +<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f" fill-opacity=".99" stroke-width="1.08904"><path d="m10.86822576 4.28299076h1.99947744v1.99947744h-1.99947744z"/><path d="m10.86822576 10.84273776h1.99947744v1.99947744h-1.99947744z"/><path d="m4.71256576 10.84273776h1.99947744v1.99947744h-1.99947744z"/></g><g fill="none" stroke="#fc7f7f"><path d="m1.635 8.161v4.848c0 .713.579 1.293 1.292 1.293h9.857c.713 0 1.291-.58 1.291-1.293v-9.854c0-.714-.578-1.293-1.291-1.293h-5.526" stroke-width="1.07" transform="matrix(.939225 0 0 .938055 1.27996 1.07595)"/><path d="m1.339 1.364 2.539 2.539" stroke-width=".74" transform="matrix(2.04823 .655864 .655864 2.04823 -1.51683 -1.5267)"/><path d="m1.436 1.461 1.168 1.168" stroke-width="1.18" transform="matrix(1.69185 0 0 1.69185 4.50755 -.792876)"/><path d="m1.385 1.41 1.219 1.219" stroke-width="1.22" transform="matrix(1.63859 0 0 1.63859 -.688679 4.82985)"/></g></svg> diff --git a/editor/icons/GuiCheckedDisabled.svg b/editor/icons/GuiCheckedDisabled.svg new file mode 100644 index 0000000000..6252176241 --- /dev/null +++ b/editor/icons/GuiCheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.333 1c-1.289 0-2.333 1.045-2.333 2.333v9.333c0 1.289 1.044 2.334 2.333 2.334h9.333c1.289 0 2.334-1.045 2.334-2.334v-9.333c0-1.289-1.045-2.333-2.334-2.333z" fill="#808080"/><path d="m11.501 3.734-5.612 5.612-1.704-1.681-1.5 1.5 3.204 3.181 7.111-7.113z" fill="#b3b3b3"/></svg> diff --git a/editor/icons/GuiRadioCheckedDisabled.svg b/editor/icons/GuiRadioCheckedDisabled.svg new file mode 100644 index 0000000000..94a1fffa0b --- /dev/null +++ b/editor/icons/GuiRadioCheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m15 8c0 3.866-3.134 7-7 7s-7-3.134-7-7 3.134-7 7-7 7 3.134 7 7" fill="#808080"/><path d="m12 8c0 2.209-1.791 4-4 4s-4-1.791-4-4 1.791-4 4-4 4 1.791 4 4" fill="#b3b3b3"/></svg> diff --git a/editor/icons/GuiRadioUncheckedDisabled.svg b/editor/icons/GuiRadioUncheckedDisabled.svg new file mode 100644 index 0000000000..3a75797c4d --- /dev/null +++ b/editor/icons/GuiRadioUncheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m15 8c0 3.866-3.134 7-7 7s-7-3.134-7-7 3.134-7 7-7 7 3.134 7 7" fill="#808080" fill-opacity=".1882"/></svg> diff --git a/editor/icons/GuiSpinboxUpdownDisabled.svg b/editor/icons/GuiSpinboxUpdownDisabled.svg new file mode 100644 index 0000000000..332c5e7bf8 --- /dev/null +++ b/editor/icons/GuiSpinboxUpdownDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7.984 1.002c-.259.004-.507.108-.691.291l-4 4c-.398.383-.41 1.016-.027 1.414s1.016.41 1.414.027l.027-.027 3.293-3.293 3.293 3.293c.383.398 1.016.41 1.414.027s.41-1.016.027-1.414c-.01-.009-.018-.018-.027-.027l-4-4c-.191-.19-.452-.296-.723-.291zm4.006 7.984c-.264.006-.514.117-.697.307l-3.293 3.293-3.293-3.293c-.188-.193-.447-.303-.717-.303-.552 0-1 .448-1 1 0 .271.109.529.303.717l4 4c.391.391 1.023.391 1.414 0l4-4c.398-.383.41-1.016.027-1.414-.193-.202-.463-.313-.744-.307z" fill="#b3b3b3" fill-opacity=".7843"/></svg> diff --git a/editor/icons/GuiUncheckedDisabled.svg b/editor/icons/GuiUncheckedDisabled.svg new file mode 100644 index 0000000000..2b6515f9d7 --- /dev/null +++ b/editor/icons/GuiUncheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.333 1c-1.289 0-2.333 1.045-2.333 2.333v9.333c0 1.289 1.044 2.334 2.333 2.334h9.333c1.289 0 2.334-1.045 2.334-2.334v-9.333c0-1.289-1.045-2.333-2.334-2.333z" fill="#808080" fill-opacity=".1882"/></svg> diff --git a/editor/icons/Listener2D.svg b/editor/icons/Listener2D.svg new file mode 100644 index 0000000000..db84dcfed7 --- /dev/null +++ b/editor/icons/Listener2D.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m6 1a5 5 0 0 0 -5 5h2a3 3 0 0 1 3-3 3 3 0 0 1 3 3c0 1.75-.54175 2.3583-1.1406 2.8574-.29944.2495-.62954.44071-.97656.69141-.17351.1253-.35729.26529-.53711.49219-.17982.227-.3457.58398-.3457.95898 0 1.2778-.31632 1.5742-.63867 1.7676-.32236.1934-.86133.23242-1.3613.23242h-1v2h1c.5 0 1.461.038922 2.3887-.51758.87316-.5239 1.4826-1.6633 1.5566-3.2266.011365-.0098.027247-.024684.10938-.083984.21547-.1556.63537-.40194 1.0859-.77734.90112-.751 1.8594-2.1445 1.8594-4.3945a5 5 0 0 0 -5-5zm7.9277 1-1.7383 1.0039a6 6 0 0 1 .81055 2.9961 6 6 0 0 1 -.80859 2.998l1.7363 1.002a8 8 0 0 0 0-8z" fill="#a5b7f3"/></svg> diff --git a/editor/icons/PageFirst.svg b/editor/icons/PageFirst.svg new file mode 100644 index 0000000000..76078691ef --- /dev/null +++ b/editor/icons/PageFirst.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PageFirst.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="74.25" + inkscape:cx="18.053872" + inkscape:cy="6.5252525" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="M 6,9 3,6 6,3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> + <path + d="M 9,9 V 3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2211" + sodipodi:nodetypes="cc" /> +</svg> diff --git a/editor/icons/PageLast.svg b/editor/icons/PageLast.svg new file mode 100644 index 0000000000..17c874e8c9 --- /dev/null +++ b/editor/icons/PageLast.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PageLast.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="74.25" + inkscape:cx="18.053872" + inkscape:cy="6.5252525" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="m 6.0000414,9 3,-3 -3,-3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> + <path + d="M 3.0000414,9 V 3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2211" + sodipodi:nodetypes="cc" /> +</svg> diff --git a/editor/icons/PageNext.svg b/editor/icons/PageNext.svg new file mode 100644 index 0000000000..89ff6219bb --- /dev/null +++ b/editor/icons/PageNext.svg @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PageNext.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="105.00536" + inkscape:cx="4.5854803" + inkscape:cy="5.9377923" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="m 4.5000207,9 3,-3 -3,-3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> +</svg> diff --git a/editor/icons/PagePrevious.svg b/editor/icons/PagePrevious.svg new file mode 100644 index 0000000000..a2fa84da0c --- /dev/null +++ b/editor/icons/PagePrevious.svg @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PagePrevious.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="105.00536" + inkscape:cx="4.5854803" + inkscape:cy="5.9377923" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="m 7.4999793,9 -3,-3 3,-3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> +</svg> diff --git a/editor/icons/SeparationRayShape2D.svg b/editor/icons/SeparationRayShape2D.svg new file mode 100644 index 0000000000..aa8cee1210 --- /dev/null +++ b/editor/icons/SeparationRayShape2D.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 1a1 1 0 0 0 -1 1v9.5859l-1.293-1.293a1 1 0 0 0 -.7207-.29102 1 1 0 0 0 -.69336.29102 1 1 0 0 0 0 1.4141l3 3a1.0001 1.0001 0 0 0 .0039062.003907 1 1 0 0 0 .050781.044921 1.0001 1.0001 0 0 0 .03125.027344 1 1 0 0 0 .048828.035156 1.0001 1.0001 0 0 0 .023438.015625 1 1 0 0 0 .076172.044922 1.0001 1.0001 0 0 0 .0058593.003906 1 1 0 0 0 .013672.007813 1.0001 1.0001 0 0 0 .078125.035156 1 1 0 0 0 .074219.025391 1.0001 1.0001 0 0 0 .025391.009766 1 1 0 0 0 .039062.009765 1.0001 1.0001 0 0 0 .068359.013672 1.0001 1.0001 0 0 0 .097656.011719 1.0001 1.0001 0 0 0 .0078125 0 1 1 0 0 0 .0625.003906 1 1 0 0 0 .015625-.001953 1.0001 1.0001 0 0 0 .083984-.003906 1 1 0 0 0 .015625-.001953 1.0001 1.0001 0 0 0 .083984-.013672 1.0001 1.0001 0 0 0 .052734-.013672 1 1 0 0 0 .058594-.015625 1.0001 1.0001 0 0 0 .078125-.029297 1 1 0 0 0 .013672-.00586 1.0001 1.0001 0 0 0 .076172-.037109 1 1 0 0 0 .013672-.007812 1.0001 1.0001 0 0 0 .072266-.044922 1 1 0 0 0 .011719-.007813 1.0001 1.0001 0 0 0 .068359-.052734 1 1 0 0 0 .011719-.009766 1.0001 1.0001 0 0 0 .050781-.046875l.0097657-.011719 2.9902-2.9883a1 1 0 0 0 0-1.4141 1 1 0 0 0 -1.4141 0l-1.293 1.293v-9.5859a1 1 0 0 0 -1-1z" fill="#68b6ff" fill-rule="evenodd"/></svg> diff --git a/editor/icons/SeparationRayShape3D.svg b/editor/icons/SeparationRayShape3D.svg new file mode 100644 index 0000000000..44d32fe83b --- /dev/null +++ b/editor/icons/SeparationRayShape3D.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="m8 1-6 5 4 2.666v4.334l2 2v-5-2z" fill="#a2d2ff"/><path d="m8 1v7 2l-2-1.334v1.334l2 1.5v3.5l2-2v-4.334l4-2.666z" fill="#2998ff"/></g></svg> diff --git a/editor/import/collada.cpp b/editor/import/collada.cpp index 71930e1e59..b6d0927ce6 100644 --- a/editor/import/collada.cpp +++ b/editor/import/collada.cpp @@ -287,7 +287,7 @@ void Collada::_parse_image(XMLParser &parser) { if (state.version < State::Version(1, 4, 0)) { /* <1.4 */ String path = parser.get_attribute_value("source").strip_edges(); - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path image.path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().plus_file(path.uri_decode())); } @@ -300,7 +300,7 @@ void Collada::_parse_image(XMLParser &parser) { parser.read(); String path = parser.get_node_data().strip_edges().uri_decode(); - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().plus_file(path)); diff --git a/editor/import/dynamicfont_import_settings.cpp b/editor/import/dynamicfont_import_settings.cpp new file mode 100644 index 0000000000..37ca40287f --- /dev/null +++ b/editor/import/dynamicfont_import_settings.cpp @@ -0,0 +1,1889 @@ +/*************************************************************************/ +/* dynamicfont_import_settings.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 "dynamicfont_import_settings.h" + +#include "editor/editor_node.h" +#include "editor/editor_scale.h" + +/*************************************************************************/ +/* Settings data */ +/*************************************************************************/ + +class DynamicFontImportSettingsData : public RefCounted { + GDCLASS(DynamicFontImportSettingsData, RefCounted) + friend class DynamicFontImportSettings; + + Map<StringName, Variant> settings; + Map<StringName, Variant> defaults; + List<ResourceImporter::ImportOption> options; + DynamicFontImportSettings *owner = nullptr; + + bool _set(const StringName &p_name, const Variant &p_value) { + if (defaults.has(p_name) && defaults[p_name] == p_value) { + settings.erase(p_name); + } else { + settings[p_name] = p_value; + } + return true; + } + + bool _get(const StringName &p_name, Variant &r_ret) const { + if (settings.has(p_name)) { + r_ret = settings[p_name]; + return true; + } + if (defaults.has(p_name)) { + r_ret = defaults[p_name]; + return true; + } + return false; + } + + void _get_property_list(List<PropertyInfo> *p_list) const { + for (const List<ResourceImporter::ImportOption>::Element *E = options.front(); E; E = E->next()) { + if (owner && owner->import_settings_data.is_valid()) { + if (owner->import_settings_data->get("multichannel_signed_distance_field") && (E->get().option.name == "size" || E->get().option.name == "outline_size" || E->get().option.name == "oversampling")) { + continue; + } + if (!owner->import_settings_data->get("multichannel_signed_distance_field") && (E->get().option.name == "msdf_pixel_range" || E->get().option.name == "msdf_size")) { + continue; + } + } + p_list->push_back(E->get().option); + } + } +}; + +/*************************************************************************/ +/* Glyph ranges */ +/*************************************************************************/ + +struct UniRange { + int32_t start; + int32_t end; + String name; +}; + +static UniRange unicode_ranges[] = { + { 0x0000, 0x007F, U"Basic Latin" }, + { 0x0080, 0x00FF, U"Latin-1 Supplement" }, + { 0x0100, 0x017F, U"Latin Extended-A" }, + { 0x0180, 0x024F, U"Latin Extended-B" }, + { 0x0250, 0x02AF, U"IPA Extensions" }, + { 0x02B0, 0x02FF, U"Spacing Modifier Letters" }, + { 0x0300, 0x036F, U"Combining Diacritical Marks" }, + { 0x0370, 0x03FF, U"Greek and Coptic" }, + { 0x0400, 0x04FF, U"Cyrillic" }, + { 0x0500, 0x052F, U"Cyrillic Supplement" }, + { 0x0530, 0x058F, U"Armenian" }, + { 0x0590, 0x05FF, U"Hebrew" }, + { 0x0600, 0x06FF, U"Arabic" }, + { 0x0700, 0x074F, U"Syriac" }, + { 0x0750, 0x077F, U"Arabic Supplement" }, + { 0x0780, 0x07BF, U"Thaana" }, + { 0x07C0, 0x07FF, U"N'Ko" }, + { 0x0800, 0x083F, U"Samaritan" }, + { 0x0840, 0x085F, U"Mandaic" }, + { 0x0860, 0x086F, U"Syriac Supplement" }, + { 0x08A0, 0x08FF, U"Arabic Extended-A" }, + { 0x0900, 0x097F, U"Devanagari" }, + { 0x0980, 0x09FF, U"Bengali" }, + { 0x0A00, 0x0A7F, U"Gurmukhi" }, + { 0x0A80, 0x0AFF, U"Gujarati" }, + { 0x0B00, 0x0B7F, U"Oriya" }, + { 0x0B80, 0x0BFF, U"Tamil" }, + { 0x0C00, 0x0C7F, U"Telugu" }, + { 0x0C80, 0x0CFF, U"Kannada" }, + { 0x0D00, 0x0D7F, U"Malayalam" }, + { 0x0D80, 0x0DFF, U"Sinhala" }, + { 0x0E00, 0x0E7F, U"Thai" }, + { 0x0E80, 0x0EFF, U"Lao" }, + { 0x0F00, 0x0FFF, U"Tibetan" }, + { 0x1000, 0x109F, U"Myanmar" }, + { 0x10A0, 0x10FF, U"Georgian" }, + { 0x1100, 0x11FF, U"Hangul Jamo" }, + { 0x1200, 0x137F, U"Ethiopic" }, + { 0x1380, 0x139F, U"Ethiopic Supplement" }, + { 0x13A0, 0x13FF, U"Cherokee" }, + { 0x1400, 0x167F, U"Unified Canadian Aboriginal Syllabics" }, + { 0x1680, 0x169F, U"Ogham" }, + { 0x16A0, 0x16FF, U"Runic" }, + { 0x1700, 0x171F, U"Tagalog" }, + { 0x1720, 0x173F, U"Hanunoo" }, + { 0x1740, 0x175F, U"Buhid" }, + { 0x1760, 0x177F, U"Tagbanwa" }, + { 0x1780, 0x17FF, U"Khmer" }, + { 0x1800, 0x18AF, U"Mongolian" }, + { 0x18B0, 0x18FF, U"Unified Canadian Aboriginal Syllabics Extended" }, + { 0x1900, 0x194F, U"Limbu" }, + { 0x1950, 0x197F, U"Tai Le" }, + { 0x1980, 0x19DF, U"New Tai Lue" }, + { 0x19E0, 0x19FF, U"Khmer Symbols" }, + { 0x1A00, 0x1A1F, U"Buginese" }, + { 0x1A20, 0x1AAF, U"Tai Tham" }, + { 0x1AB0, 0x1AFF, U"Combining Diacritical Marks Extended" }, + { 0x1B00, 0x1B7F, U"Balinese" }, + { 0x1B80, 0x1BBF, U"Sundanese" }, + { 0x1BC0, 0x1BFF, U"Batak" }, + { 0x1C00, 0x1C4F, U"Lepcha" }, + { 0x1C50, 0x1C7F, U"Ol Chiki" }, + { 0x1C80, 0x1C8F, U"Cyrillic Extended-C" }, + { 0x1C90, 0x1CBF, U"Georgian Extended" }, + { 0x1CC0, 0x1CCF, U"Sundanese Supplement" }, + { 0x1CD0, 0x1CFF, U"Vedic Extensions" }, + { 0x1D00, 0x1D7F, U"Phonetic Extensions" }, + { 0x1D80, 0x1DBF, U"Phonetic Extensions Supplement" }, + { 0x1DC0, 0x1DFF, U"Combining Diacritical Marks Supplement" }, + { 0x1E00, 0x1EFF, U"Latin Extended Additional" }, + { 0x1F00, 0x1FFF, U"Greek Extended" }, + { 0x2000, 0x206F, U"General Punctuation" }, + { 0x2070, 0x209F, U"Superscripts and Subscripts" }, + { 0x20A0, 0x20CF, U"Currency Symbols" }, + { 0x20D0, 0x20FF, U"Combining Diacritical Marks for Symbols" }, + { 0x2100, 0x214F, U"Letterlike Symbols" }, + { 0x2150, 0x218F, U"Number Forms" }, + { 0x2190, 0x21FF, U"Arrows" }, + { 0x2200, 0x22FF, U"Mathematical Operators" }, + { 0x2300, 0x23FF, U"Miscellaneous Technical" }, + { 0x2400, 0x243F, U"Control Pictures" }, + { 0x2440, 0x245F, U"Optical Character Recognition" }, + { 0x2460, 0x24FF, U"Enclosed Alphanumerics" }, + { 0x2500, 0x257F, U"Box Drawing" }, + { 0x2580, 0x259F, U"Block Elements" }, + { 0x25A0, 0x25FF, U"Geometric Shapes" }, + { 0x2600, 0x26FF, U"Miscellaneous Symbols" }, + { 0x2700, 0x27BF, U"Dingbats" }, + { 0x27C0, 0x27EF, U"Miscellaneous Mathematical Symbols-A" }, + { 0x27F0, 0x27FF, U"Supplemental Arrows-A" }, + { 0x2800, 0x28FF, U"Braille Patterns" }, + { 0x2900, 0x297F, U"Supplemental Arrows-B" }, + { 0x2980, 0x29FF, U"Miscellaneous Mathematical Symbols-B" }, + { 0x2A00, 0x2AFF, U"Supplemental Mathematical Operators" }, + { 0x2B00, 0x2BFF, U"Miscellaneous Symbols and Arrows" }, + { 0x2C00, 0x2C5F, U"Glagolitic" }, + { 0x2C60, 0x2C7F, U"Latin Extended-C" }, + { 0x2C80, 0x2CFF, U"Coptic" }, + { 0x2D00, 0x2D2F, U"Georgian Supplement" }, + { 0x2D30, 0x2D7F, U"Tifinagh" }, + { 0x2D80, 0x2DDF, U"Ethiopic Extended" }, + { 0x2DE0, 0x2DFF, U"Cyrillic Extended-A" }, + { 0x2E00, 0x2E7F, U"Supplemental Punctuation" }, + { 0x2E80, 0x2EFF, U"CJK Radicals Supplement" }, + { 0x2F00, 0x2FDF, U"Kangxi Radicals" }, + { 0x2FF0, 0x2FFF, U"Ideographic Description Characters" }, + { 0x3000, 0x303F, U"CJK Symbols and Punctuation" }, + { 0x3040, 0x309F, U"Hiragana" }, + { 0x30A0, 0x30FF, U"Katakana" }, + { 0x3100, 0x312F, U"Bopomofo" }, + { 0x3130, 0x318F, U"Hangul Compatibility Jamo" }, + { 0x3190, 0x319F, U"Kanbun" }, + { 0x31A0, 0x31BF, U"Bopomofo Extended" }, + { 0x31C0, 0x31EF, U"CJK Strokes" }, + { 0x31F0, 0x31FF, U"Katakana Phonetic Extensions" }, + { 0x3200, 0x32FF, U"Enclosed CJK Letters and Months" }, + { 0x3300, 0x33FF, U"CJK Compatibility" }, + { 0x3400, 0x4DBF, U"CJK Unified Ideographs Extension A" }, + { 0x4DC0, 0x4DFF, U"Yijing Hexagram Symbols" }, + { 0x4E00, 0x9FFF, U"CJK Unified Ideographs" }, + { 0xA000, 0xA48F, U"Yi Syllables" }, + { 0xA490, 0xA4CF, U"Yi Radicals" }, + { 0xA4D0, 0xA4FF, U"Lisu" }, + { 0xA500, 0xA63F, U"Vai" }, + { 0xA640, 0xA69F, U"Cyrillic Extended-B" }, + { 0xA6A0, 0xA6FF, U"Bamum" }, + { 0xA700, 0xA71F, U"Modifier Tone Letters" }, + { 0xA720, 0xA7FF, U"Latin Extended-D" }, + { 0xA800, 0xA82F, U"Syloti Nagri" }, + { 0xA830, 0xA83F, U"Common Indic Number Forms" }, + { 0xA840, 0xA87F, U"Phags-pa" }, + { 0xA880, 0xA8DF, U"Saurashtra" }, + { 0xA8E0, 0xA8FF, U"Devanagari Extended" }, + { 0xA900, 0xA92F, U"Kayah Li" }, + { 0xA930, 0xA95F, U"Rejang" }, + { 0xA960, 0xA97F, U"Hangul Jamo Extended-A" }, + { 0xA980, 0xA9DF, U"Javanese" }, + { 0xA9E0, 0xA9FF, U"Myanmar Extended-B" }, + { 0xAA00, 0xAA5F, U"Cham" }, + { 0xAA60, 0xAA7F, U"Myanmar Extended-A" }, + { 0xAA80, 0xAADF, U"Tai Viet" }, + { 0xAAE0, 0xAAFF, U"Meetei Mayek Extensions" }, + { 0xAB00, 0xAB2F, U"Ethiopic Extended-A" }, + { 0xAB30, 0xAB6F, U"Latin Extended-E" }, + { 0xAB70, 0xABBF, U"Cherokee Supplement" }, + { 0xABC0, 0xABFF, U"Meetei Mayek" }, + { 0xD7B0, 0xD7FF, U"Hangul Jamo Extended-B" }, + //{ 0xF800, 0xDFFF, U"Surrogates" }, + { 0xE000, 0xE2FE, U"Private Use Area" }, + { 0xF900, 0xFAFF, U"CJK Compatibility Ideographs" }, + { 0xFB00, 0xFB4F, U"Alphabetic Presentation Forms" }, + { 0xFB50, 0xFDFF, U"Arabic Presentation Forms-A" }, + //{ 0xFE00, 0xFE0F, U"Variation Selectors" }, + { 0xFE10, 0xFE1F, U"Vertical Forms" }, + { 0xFE20, 0xFE2F, U"Combining Half Marks" }, + { 0xFE30, 0xFE4F, U"CJK Compatibility Forms" }, + { 0xFE50, 0xFE6F, U"Small Form Variants" }, + { 0xFE70, 0xFEFF, U"Arabic Presentation Forms-B" }, + { 0xFF00, 0xFFEF, U"Halfwidth and Fullwidth Forms" }, + //{ 0xFFF0, 0xFFFF, U"Specials" }, + { 0x10000, 0x1007F, U"Linear B Syllabary" }, + { 0x10080, 0x100FF, U"Linear B Ideograms" }, + { 0x10100, 0x1013F, U"Aegean Numbers" }, + { 0x10140, 0x1018F, U"Ancient Greek Numbers" }, + { 0x10190, 0x101CF, U"Ancient Symbols" }, + { 0x101D0, 0x101FF, U"Phaistos Disc" }, + { 0x10280, 0x1029F, U"Lycian" }, + { 0x102A0, 0x102DF, U"Carian" }, + { 0x102E0, 0x102FF, U"Coptic Epact Numbers" }, + { 0x10300, 0x1032F, U"Old Italic" }, + { 0x10330, 0x1034F, U"Gothic" }, + { 0x10350, 0x1037F, U"Old Permic" }, + { 0x10380, 0x1039F, U"Ugaritic" }, + { 0x103A0, 0x103DF, U"Old Persian" }, + { 0x10400, 0x1044F, U"Deseret" }, + { 0x10450, 0x1047F, U"Shavian" }, + { 0x10480, 0x104AF, U"Osmanya" }, + { 0x104B0, 0x104FF, U"Osage" }, + { 0x10500, 0x1052F, U"Elbasan" }, + { 0x10530, 0x1056F, U"Caucasian Albanian" }, + { 0x10600, 0x1077F, U"Linear A" }, + { 0x10800, 0x1083F, U"Cypriot Syllabary" }, + { 0x10840, 0x1085F, U"Imperial Aramaic" }, + { 0x10860, 0x1087F, U"Palmyrene" }, + { 0x10880, 0x108AF, U"Nabataean" }, + { 0x108E0, 0x108FF, U"Hatran" }, + { 0x10900, 0x1091F, U"Phoenician" }, + { 0x10920, 0x1093F, U"Lydian" }, + { 0x10980, 0x1099F, U"Meroitic Hieroglyphs" }, + { 0x109A0, 0x109FF, U"Meroitic Cursive" }, + { 0x10A00, 0x10A5F, U"Kharoshthi" }, + { 0x10A60, 0x10A7F, U"Old South Arabian" }, + { 0x10A80, 0x10A9F, U"Old North Arabian" }, + { 0x10AC0, 0x10AFF, U"Manichaean" }, + { 0x10B00, 0x10B3F, U"Avestan" }, + { 0x10B40, 0x10B5F, U"Inscriptional Parthian" }, + { 0x10B60, 0x10B7F, U"Inscriptional Pahlavi" }, + { 0x10B80, 0x10BAF, U"Psalter Pahlavi" }, + { 0x10C00, 0x10C4F, U"Old Turkic" }, + { 0x10C80, 0x10CFF, U"Old Hungarian" }, + { 0x10D00, 0x10D3F, U"Hanifi Rohingya" }, + { 0x10E60, 0x10E7F, U"Rumi Numeral Symbols" }, + { 0x10E80, 0x10EBF, U"Yezidi" }, + { 0x10F00, 0x10F2F, U"Old Sogdian" }, + { 0x10F30, 0x10F6F, U"Sogdian" }, + { 0x10FB0, 0x10FDF, U"Chorasmian" }, + { 0x10FE0, 0x10FFF, U"Elymaic" }, + { 0x11000, 0x1107F, U"Brahmi" }, + { 0x11080, 0x110CF, U"Kaithi" }, + { 0x110D0, 0x110FF, U"Sora Sompeng" }, + { 0x11100, 0x1114F, U"Chakma" }, + { 0x11150, 0x1117F, U"Mahajani" }, + { 0x11180, 0x111DF, U"Sharada" }, + { 0x111E0, 0x111FF, U"Sinhala Archaic Numbers" }, + { 0x11200, 0x1124F, U"Khojki" }, + { 0x11280, 0x112AF, U"Multani" }, + { 0x112B0, 0x112FF, U"Khudawadi" }, + { 0x11300, 0x1137F, U"Grantha" }, + { 0x11400, 0x1147F, U"Newa" }, + { 0x11480, 0x114DF, U"Tirhuta" }, + { 0x11580, 0x115FF, U"Siddham" }, + { 0x11600, 0x1165F, U"Modi" }, + { 0x11660, 0x1167F, U"Mongolian Supplement" }, + { 0x11680, 0x116CF, U"Takri" }, + { 0x11700, 0x1173F, U"Ahom" }, + { 0x11800, 0x1184F, U"Dogra" }, + { 0x118A0, 0x118FF, U"Warang Citi" }, + { 0x11900, 0x1195F, U"Dives Akuru" }, + { 0x119A0, 0x119FF, U"Nandinagari" }, + { 0x11A00, 0x11A4F, U"Zanabazar Square" }, + { 0x11A50, 0x11AAF, U"Soyombo" }, + { 0x11AC0, 0x11AFF, U"Pau Cin Hau" }, + { 0x11C00, 0x11C6F, U"Bhaiksuki" }, + { 0x11C70, 0x11CBF, U"Marchen" }, + { 0x11D00, 0x11D5F, U"Masaram Gondi" }, + { 0x11D60, 0x11DAF, U"Gunjala Gondi" }, + { 0x11EE0, 0x11EFF, U"Makasar" }, + { 0x11FB0, 0x11FBF, U"Lisu Supplement" }, + { 0x11FC0, 0x11FFF, U"Tamil Supplement" }, + { 0x12000, 0x123FF, U"Cuneiform" }, + { 0x12400, 0x1247F, U"Cuneiform Numbers and Punctuation" }, + { 0x12480, 0x1254F, U"Early Dynastic Cuneiform" }, + { 0x13000, 0x1342F, U"Egyptian Hieroglyphs" }, + { 0x13430, 0x1343F, U"Egyptian Hieroglyph Format Controls" }, + { 0x14400, 0x1467F, U"Anatolian Hieroglyphs" }, + { 0x16800, 0x16A3F, U"Bamum Supplement" }, + { 0x16A40, 0x16A6F, U"Mro" }, + { 0x16AD0, 0x16AFF, U"Bassa Vah" }, + { 0x16B00, 0x16B8F, U"Pahawh Hmong" }, + { 0x16E40, 0x16E9F, U"Medefaidrin" }, + { 0x16F00, 0x16F9F, U"Miao" }, + { 0x16FE0, 0x16FFF, U"Ideographic Symbols and Punctuation" }, + { 0x17000, 0x187FF, U"Tangut" }, + { 0x18800, 0x18AFF, U"Tangut Components" }, + { 0x18B00, 0x18CFF, U"Khitan Small Script" }, + { 0x18D00, 0x18D8F, U"Tangut Supplement" }, + { 0x1B000, 0x1B0FF, U"Kana Supplement" }, + { 0x1B100, 0x1B12F, U"Kana Extended-A" }, + { 0x1B130, 0x1B16F, U"Small Kana Extension" }, + { 0x1B170, 0x1B2FF, U"Nushu" }, + { 0x1BC00, 0x1BC9F, U"Duployan" }, + { 0x1BCA0, 0x1BCAF, U"Shorthand Format Controls" }, + { 0x1D000, 0x1D0FF, U"Byzantine Musical Symbols" }, + { 0x1D100, 0x1D1FF, U"Musical Symbols" }, + { 0x1D200, 0x1D24F, U"Ancient Greek Musical Notation" }, + { 0x1D2E0, 0x1D2FF, U"Mayan Numerals" }, + { 0x1D300, 0x1D35F, U"Tai Xuan Jing Symbols" }, + { 0x1D360, 0x1D37F, U"Counting Rod Numerals" }, + { 0x1D400, 0x1D7FF, U"Mathematical Alphanumeric Symbols" }, + { 0x1D800, 0x1DAAF, U"Sutton SignWriting" }, + { 0x1E000, 0x1E02F, U"Glagolitic Supplement" }, + { 0x1E100, 0x1E14F, U"Nyiakeng Puachue Hmong" }, + { 0x1E2C0, 0x1E2FF, U"Wancho" }, + { 0x1E800, 0x1E8DF, U"Mende Kikakui" }, + { 0x1E900, 0x1E95F, U"Adlam" }, + { 0x1EC70, 0x1ECBF, U"Indic Siyaq Numbers" }, + { 0x1ED00, 0x1ED4F, U"Ottoman Siyaq Numbers" }, + { 0x1EE00, 0x1EEFF, U"Arabic Mathematical Alphabetic Symbols" }, + { 0x1F000, 0x1F02F, U"Mahjong Tiles" }, + { 0x1F030, 0x1F09F, U"Domino Tiles" }, + { 0x1F0A0, 0x1F0FF, U"Playing Cards" }, + { 0x1F100, 0x1F1FF, U"Enclosed Alphanumeric Supplement" }, + { 0x1F200, 0x1F2FF, U"Enclosed Ideographic Supplement" }, + { 0x1F300, 0x1F5FF, U"Miscellaneous Symbols and Pictographs" }, + { 0x1F600, 0x1F64F, U"Emoticons" }, + { 0x1F650, 0x1F67F, U"Ornamental Dingbats" }, + { 0x1F680, 0x1F6FF, U"Transport and Map Symbols" }, + { 0x1F700, 0x1F77F, U"Alchemical Symbols" }, + { 0x1F780, 0x1F7FF, U"Geometric Shapes Extended" }, + { 0x1F800, 0x1F8FF, U"Supplemental Arrows-C" }, + { 0x1F900, 0x1F9FF, U"Supplemental Symbols and Pictographs" }, + { 0x1FA00, 0x1FA6F, U"Chess Symbols" }, + { 0x1FA70, 0x1FAFF, U"Symbols and Pictographs Extended-A" }, + { 0x1FB00, 0x1FBFF, U"Symbols for Legacy Computing" }, + { 0x20000, 0x2A6DF, U"CJK Unified Ideographs Extension B" }, + { 0x2A700, 0x2B73F, U"CJK Unified Ideographs Extension C" }, + { 0x2B740, 0x2B81F, U"CJK Unified Ideographs Extension D" }, + { 0x2B820, 0x2CEAF, U"CJK Unified Ideographs Extension E" }, + { 0x2CEB0, 0x2EBEF, U"CJK Unified Ideographs Extension F" }, + { 0x2F800, 0x2FA1F, U"CJK Compatibility Ideographs Supplement" }, + { 0x30000, 0x3134F, U"CJK Unified Ideographs Extension G" }, + //{ 0xE0000, 0xE007F, U"Tags" }, + //{ 0xE0100, 0xE01EF, U"Variation Selectors Supplement" }, + { 0xF0000, 0xFFFFD, U"Supplementary Private Use Area-A" }, + { 0x100000, 0x10FFFD, U"Supplementary Private Use Area-B" }, + { 0x10FFFF, 0x10FFFF, String() } +}; + +void DynamicFontImportSettings::_add_glyph_range_item(int32_t p_start, int32_t p_end, const String &p_name) { + const int page_size = 512; + int pages = (p_end - p_start) / page_size; + int remain = (p_end - p_start) % page_size; + + int32_t start = p_start; + for (int i = 0; i < pages; i++) { + TreeItem *item = glyph_tree->create_item(glyph_root); + ERR_FAIL_NULL(item); + item->set_text(0, _pad_zeros(String::num_int64(start, 16)) + " - " + _pad_zeros(String::num_int64(start + page_size, 16))); + item->set_text(1, p_name); + item->set_metadata(0, Vector2i(start, start + page_size)); + start += page_size; + } + if (remain > 0) { + TreeItem *item = glyph_tree->create_item(glyph_root); + ERR_FAIL_NULL(item); + item->set_text(0, _pad_zeros(String::num_int64(start, 16)) + " - " + _pad_zeros(String::num_int64(p_end, 16))); + item->set_text(1, p_name); + item->set_metadata(0, Vector2i(start, p_end)); + } +} + +/*************************************************************************/ +/* Languages and scripts */ +/*************************************************************************/ + +struct CodeInfo { + String name; + String code; +}; + +static CodeInfo langs[] = { + { U"Custom", U"xx" }, + { U"-", U"-" }, + { U"Abkhazian", U"ab" }, + { U"Afar", U"aa" }, + { U"Afrikaans", U"af" }, + { U"Akan", U"ak" }, + { U"Albanian", U"sq" }, + { U"Amharic", U"am" }, + { U"Arabic", U"ar" }, + { U"Aragonese", U"an" }, + { U"Armenian", U"hy" }, + { U"Assamese", U"as" }, + { U"Avaric", U"av" }, + { U"Avestan", U"ae" }, + { U"Aymara", U"ay" }, + { U"Azerbaijani", U"az" }, + { U"Bambara", U"bm" }, + { U"Bashkir", U"ba" }, + { U"Basque", U"eu" }, + { U"Belarusian", U"be" }, + { U"Bengali", U"bn" }, + { U"Bihari", U"bh" }, + { U"Bislama", U"bi" }, + { U"Bosnian", U"bs" }, + { U"Breton", U"br" }, + { U"Bulgarian", U"bg" }, + { U"Burmese", U"my" }, + { U"Catalan", U"ca" }, + { U"Chamorro", U"ch" }, + { U"Chechen", U"ce" }, + { U"Chichewa", U"ny" }, + { U"Chinese", U"zh" }, + { U"Chuvash", U"cv" }, + { U"Cornish", U"kw" }, + { U"Corsican", U"co" }, + { U"Cree", U"cr" }, + { U"Croatian", U"hr" }, + { U"Czech", U"cs" }, + { U"Danish", U"da" }, + { U"Divehi", U"dv" }, + { U"Dutch", U"nl" }, + { U"Dzongkha", U"dz" }, + { U"English", U"en" }, + { U"Esperanto", U"eo" }, + { U"Estonian", U"et" }, + { U"Ewe", U"ee" }, + { U"Faroese", U"fo" }, + { U"Fijian", U"fj" }, + { U"Finnish", U"fi" }, + { U"French", U"fr" }, + { U"Fulah", U"ff" }, + { U"Galician", U"gl" }, + { U"Georgian", U"ka" }, + { U"German", U"de" }, + { U"Greek", U"el" }, + { U"Guarani", U"gn" }, + { U"Gujarati", U"gu" }, + { U"Haitian", U"ht" }, + { U"Hausa", U"ha" }, + { U"Hebrew", U"he" }, + { U"Herero", U"hz" }, + { U"Hindi", U"hi" }, + { U"Hiri Motu", U"ho" }, + { U"Hungarian", U"hu" }, + { U"Interlingua", U"ia" }, + { U"Indonesian", U"id" }, + { U"Interlingue", U"ie" }, + { U"Irish", U"ga" }, + { U"Igbo", U"ig" }, + { U"Inupiaq", U"ik" }, + { U"Ido", U"io" }, + { U"Icelandic", U"is" }, + { U"Italian", U"it" }, + { U"Inuktitut", U"iu" }, + { U"Japanese", U"ja" }, + { U"Javanese", U"jv" }, + { U"Kalaallisut", U"kl" }, + { U"Kannada", U"kn" }, + { U"Kanuri", U"kr" }, + { U"Kashmiri", U"ks" }, + { U"Kazakh", U"kk" }, + { U"Central Khmer", U"km" }, + { U"Kikuyu", U"ki" }, + { U"Kinyarwanda", U"rw" }, + { U"Kirghiz", U"ky" }, + { U"Komi", U"kv" }, + { U"Kongo", U"kg" }, + { U"Korean", U"ko" }, + { U"Kurdish", U"ku" }, + { U"Kuanyama", U"kj" }, + { U"Latin", U"la" }, + { U"Luxembourgish", U"lb" }, + { U"Ganda", U"lg" }, + { U"Limburgan", U"li" }, + { U"Lingala", U"ln" }, + { U"Lao", U"lo" }, + { U"Lithuanian", U"lt" }, + { U"Luba-Katanga", U"lu" }, + { U"Latvian", U"lv" }, + { U"Man", U"gv" }, + { U"Macedonian", U"mk" }, + { U"Malagasy", U"mg" }, + { U"Malay", U"ms" }, + { U"Malayalam", U"ml" }, + { U"Maltese", U"mt" }, + { U"Maori", U"mi" }, + { U"Marathi", U"mr" }, + { U"Marshallese", U"mh" }, + { U"Mongolian", U"mn" }, + { U"Nauru", U"na" }, + { U"Navajo", U"nv" }, + { U"North Ndebele", U"nd" }, + { U"Nepali", U"ne" }, + { U"Ndonga", U"ng" }, + { U"Norwegian Bokmål", U"nb" }, + { U"Norwegian Nynorsk", U"nn" }, + { U"Norwegian", U"no" }, + { U"Sichuan Yi, Nuosu", U"ii" }, + { U"South Ndebele", U"nr" }, + { U"Occitan", U"oc" }, + { U"Ojibwa", U"oj" }, + { U"Church Slavic", U"cu" }, + { U"Oromo", U"om" }, + { U"Oriya", U"or" }, + { U"Ossetian", U"os" }, + { U"Punjabi", U"pa" }, + { U"Pali", U"pi" }, + { U"Persian", U"fa" }, + { U"Polish", U"pl" }, + { U"Pashto", U"ps" }, + { U"Portuguese", U"pt" }, + { U"Quechua", U"qu" }, + { U"Romansh", U"rm" }, + { U"Rundi", U"rn" }, + { U"Romanian", U"ro" }, + { U"Russian", U"ru" }, + { U"Sanskrit", U"sa" }, + { U"Sardinian", U"sc" }, + { U"Sindhi", U"sd" }, + { U"Northern Sami", U"se" }, + { U"Samoan", U"sm" }, + { U"Sango", U"sg" }, + { U"Serbian", U"sr" }, + { U"Gaelic", U"gd" }, + { U"Shona", U"sn" }, + { U"Sinhala", U"si" }, + { U"Slovak", U"sk" }, + { U"Slovenian", U"sl" }, + { U"Somali", U"so" }, + { U"Southern Sotho", U"st" }, + { U"Spanish", U"es" }, + { U"Sundanese", U"su" }, + { U"Swahili", U"sw" }, + { U"Swati", U"ss" }, + { U"Swedish", U"sv" }, + { U"Tamil", U"ta" }, + { U"Telugu", U"te" }, + { U"Tajik", U"tg" }, + { U"Thai", U"th" }, + { U"Tigrinya", U"ti" }, + { U"Tibetan", U"bo" }, + { U"Turkmen", U"tk" }, + { U"Tagalog", U"tl" }, + { U"Tswana", U"tn" }, + { U"Tonga", U"to" }, + { U"Turkish", U"tr" }, + { U"Tsonga", U"ts" }, + { U"Tatar", U"tt" }, + { U"Twi", U"tw" }, + { U"Tahitian", U"ty" }, + { U"Uighur", U"ug" }, + { U"Ukrainian", U"uk" }, + { U"Urdu", U"ur" }, + { U"Uzbek", U"uz" }, + { U"Venda", U"ve" }, + { U"Vietnamese", U"vi" }, + { U"Volapük", U"vo" }, + { U"Walloon", U"wa" }, + { U"Welsh", U"cy" }, + { U"Wolof", U"wo" }, + { U"Western Frisian", U"fy" }, + { U"Xhosa", U"xh" }, + { U"Yiddish", U"yi" }, + { U"Yoruba", U"yo" }, + { U"Zhuang", U"za" }, + { U"Zulu", U"zu" }, + { String(), String() } +}; + +static CodeInfo scripts[] = { + { U"Custom", U"Qaaa" }, + { U"-", U"-" }, + { U"Adlam", U"Adlm" }, + { U"Afaka", U"Afak" }, + { U"Caucasian Albanian", U"Aghb" }, + { U"Ahom", U"Ahom" }, + { U"Arabic", U"Arab" }, + { U"Imperial Aramaic", U"Armi" }, + { U"Armenian", U"Armn" }, + { U"Avestan", U"Avst" }, + { U"Balinese", U"Bali" }, + { U"Bamum", U"Bamu" }, + { U"Bassa Vah", U"Bass" }, + { U"Batak", U"Batk" }, + { U"Bengali", U"Beng" }, + { U"Bhaiksuki", U"Bhks" }, + { U"Blissymbols", U"Blis" }, + { U"Bopomofo", U"Bopo" }, + { U"Brahmi", U"Brah" }, + { U"Braille", U"Brai" }, + { U"Buginese", U"Bugi" }, + { U"Buhid", U"Buhd" }, + { U"Chakma", U"Cakm" }, + { U"Unified Canadian Aboriginal", U"Cans" }, + { U"Carian", U"Cari" }, + { U"Cham", U"Cham" }, + { U"Cherokee", U"Cher" }, + { U"Chorasmian", U"Chrs" }, + { U"Cirth", U"Cirt" }, + { U"Coptic", U"Copt" }, + { U"Cypro-Minoan", U"Cpmn" }, + { U"Cypriot", U"Cprt" }, + { U"Cyrillic", U"Cyrl" }, + { U"Devanagari", U"Deva" }, + { U"Dives Akuru", U"Diak" }, + { U"Dogra", U"Dogr" }, + { U"Deseret", U"Dsrt" }, + { U"Duployan", U"Dupl" }, + { U"Egyptian demotic", U"Egyd" }, + { U"Egyptian hieratic", U"Egyh" }, + { U"Egyptian hieroglyphs", U"Egyp" }, + { U"Elbasan", U"Elba" }, + { U"Elymaic", U"Elym" }, + { U"Ethiopic", U"Ethi" }, + { U"Khutsuri", U"Geok" }, + { U"Georgian", U"Geor" }, + { U"Glagolitic", U"Glag" }, + { U"Gunjala Gondi", U"Gong" }, + { U"Masaram Gondi", U"Gonm" }, + { U"Gothic", U"Goth" }, + { U"Grantha", U"Gran" }, + { U"Greek", U"Grek" }, + { U"Gujarati", U"Gujr" }, + { U"Gurmukhi", U"Guru" }, + { U"Hangul", U"Hang" }, + { U"Han", U"Hani" }, + { U"Hanunoo", U"Hano" }, + { U"Hatran", U"Hatr" }, + { U"Hebrew", U"Hebr" }, + { U"Hiragana", U"Hira" }, + { U"Anatolian Hieroglyphs", U"Hluw" }, + { U"Pahawh Hmong", U"Hmng" }, + { U"Nyiakeng Puachue Hmong", U"Hmnp" }, + { U"Old Hungarian", U"Hung" }, + { U"Indus", U"Inds" }, + { U"Old Italic", U"Ital" }, + { U"Javanese", U"Java" }, + { U"Jurchen", U"Jurc" }, + { U"Kayah Li", U"Kali" }, + { U"Katakana", U"Kana" }, + { U"Kharoshthi", U"Khar" }, + { U"Khmer", U"Khmr" }, + { U"Khojki", U"Khoj" }, + { U"Khitan large script", U"Kitl" }, + { U"Khitan small script", U"Kits" }, + { U"Kannada", U"Knda" }, + { U"Kpelle", U"Kpel" }, + { U"Kaithi", U"Kthi" }, + { U"Tai Tham", U"Lana" }, + { U"Lao", U"Laoo" }, + { U"Latin", U"Latn" }, + { U"Leke", U"Leke" }, + { U"Lepcha", U"Lepc" }, + { U"Limbu", U"Limb" }, + { U"Linear A", U"Lina" }, + { U"Linear B", U"Linb" }, + { U"Lisu", U"Lisu" }, + { U"Loma", U"Loma" }, + { U"Lycian", U"Lyci" }, + { U"Lydian", U"Lydi" }, + { U"Mahajani", U"Mahj" }, + { U"Makasar", U"Maka" }, + { U"Mandaic", U"Mand" }, + { U"Manichaean", U"Mani" }, + { U"Marchen", U"Marc" }, + { U"Mayan Hieroglyphs", U"Maya" }, + { U"Medefaidrin", U"Medf" }, + { U"Mende Kikakui", U"Mend" }, + { U"Meroitic Cursive", U"Merc" }, + { U"Meroitic Hieroglyphs", U"Mero" }, + { U"Malayalam", U"Mlym" }, + { U"Modi", U"Modi" }, + { U"Mongolian", U"Mong" }, + { U"Moon", U"Moon" }, + { U"Mro", U"Mroo" }, + { U"Meitei Mayek", U"Mtei" }, + { U"Multani", U"Mult" }, + { U"Myanmar (Burmese)", U"Mymr" }, + { U"Nandinagari", U"Nand" }, + { U"Old North Arabian", U"Narb" }, + { U"Nabataean", U"Nbat" }, + { U"Newa", U"Newa" }, + { U"Naxi Dongba", U"Nkdb" }, + { U"Nakhi Geba", U"Nkgb" }, + { U"N’Ko", U"Nkoo" }, + { U"Nüshu", U"Nshu" }, + { U"Ogham", U"Ogam" }, + { U"Ol Chiki", U"Olck" }, + { U"Old Turkic", U"Orkh" }, + { U"Oriya", U"Orya" }, + { U"Osage", U"Osge" }, + { U"Osmanya", U"Osma" }, + { U"Old Uyghur", U"Ougr" }, + { U"Palmyrene", U"Palm" }, + { U"Pau Cin Hau", U"Pauc" }, + { U"Proto-Cuneiform", U"Pcun" }, + { U"Proto-Elamite", U"Pelm" }, + { U"Old Permic", U"Perm" }, + { U"Phags-pa", U"Phag" }, + { U"Inscriptional Pahlavi", U"Phli" }, + { U"Psalter Pahlavi", U"Phlp" }, + { U"Book Pahlavi", U"Phlv" }, + { U"Phoenician", U"Phnx" }, + { U"Klingon", U"Piqd" }, + { U"Miao", U"Plrd" }, + { U"Inscriptional Parthian", U"Prti" }, + { U"Proto-Sinaitic", U"Psin" }, + { U"Ranjana", U"Ranj" }, + { U"Rejang", U"Rjng" }, + { U"Hanifi Rohingya", U"Rohg" }, + { U"Rongorongo", U"Roro" }, + { U"Runic", U"Runr" }, + { U"Samaritan", U"Samr" }, + { U"Sarati", U"Sara" }, + { U"Old South Arabian", U"Sarb" }, + { U"Saurashtra", U"Saur" }, + { U"SignWriting", U"Sgnw" }, + { U"Shavian", U"Shaw" }, + { U"Sharada", U"Shrd" }, + { U"Shuishu", U"Shui" }, + { U"Siddham", U"Sidd" }, + { U"Khudawadi", U"Sind" }, + { U"Sinhala", U"Sinh" }, + { U"Sogdian", U"Sogd" }, + { U"Old Sogdian", U"Sogo" }, + { U"Sora Sompeng", U"Sora" }, + { U"Soyombo", U"Soyo" }, + { U"Sundanese", U"Sund" }, + { U"Syloti Nagri", U"Sylo" }, + { U"Syriac", U"Syrc" }, + { U"Tagbanwa", U"Tagb" }, + { U"Takri", U"Takr" }, + { U"Tai Le", U"Tale" }, + { U"New Tai Lue", U"Talu" }, + { U"Tamil", U"Taml" }, + { U"Tangut", U"Tang" }, + { U"Tai Viet", U"Tavt" }, + { U"Telugu", U"Telu" }, + { U"Tengwar", U"Teng" }, + { U"Tifinagh", U"Tfng" }, + { U"Tagalog", U"Tglg" }, + { U"Thaana", U"Thaa" }, + { U"Thai", U"Thai" }, + { U"Tibetan", U"Tibt" }, + { U"Tirhuta", U"Tirh" }, + { U"Tangsa", U"Tnsa" }, + { U"Toto", U"Toto" }, + { U"Ugaritic", U"Ugar" }, + { U"Vai", U"Vaii" }, + { U"Visible Speech", U"Visp" }, + { U"Vithkuqi", U"Vith" }, + { U"Warang Citi", U"Wara" }, + { U"Wancho", U"Wcho" }, + { U"Woleai", U"Wole" }, + { U"Old Persian", U"Xpeo" }, + { U"Cuneiform", U"Xsux" }, + { U"Yezidi", U"Yezi" }, + { U"Yi", U"Yiii" }, + { U"Zanabazar Square", U"Zanb" }, + { String(), String() } +}; + +/*************************************************************************/ +/* Page 1 callbacks: Rendering Options */ +/*************************************************************************/ + +void DynamicFontImportSettings::_main_prop_changed(const String &p_edited_property) { + // Update font preview. + + if (p_edited_property == "antialiased") { + if (font_preview->get_data_count() > 0) { + font_preview->get_data(0)->set_antialiased(import_settings_data->get("antialiased")); + } + } else if (p_edited_property == "multichannel_signed_distance_field") { + if (font_preview->get_data_count() > 0) { + font_preview->get_data(0)->set_multichannel_signed_distance_field(import_settings_data->get("multichannel_signed_distance_field")); + } + _variation_selected(); + _variations_validate(); + } else if (p_edited_property == "msdf_pixel_range") { + if (font_preview->get_data_count() > 0) { + font_preview->get_data(0)->set_msdf_pixel_range(import_settings_data->get("msdf_pixel_range")); + } + } else if (p_edited_property == "msdf_size") { + if (font_preview->get_data_count() > 0) { + font_preview->get_data(0)->set_msdf_size(import_settings_data->get("msdf_size")); + } + } else if (p_edited_property == "force_autohinter") { + if (font_preview->get_data_count() > 0) { + font_preview->get_data(0)->set_force_autohinter(import_settings_data->get("force_autohinter")); + } + } else if (p_edited_property == "hinting") { + if (font_preview->get_data_count() > 0) { + font_preview->get_data(0)->set_hinting((TextServer::Hinting)import_settings_data->get("hinting").operator int()); + } + } else if (p_edited_property == "oversampling") { + if (font_preview->get_data_count() > 0) { + font_preview->get_data(0)->set_oversampling(import_settings_data->get("oversampling")); + } + } + font_preview_label->add_theme_font_override("font", font_preview); + font_preview_label->update(); +} + +/*************************************************************************/ +/* Page 2 callbacks: Configurations */ +/*************************************************************************/ + +void DynamicFontImportSettings::_variation_add() { + TreeItem *vars_item = vars_list->create_item(vars_list_root); + ERR_FAIL_NULL(vars_item); + + vars_item->set_text(0, TTR("New configuration")); + vars_item->set_editable(0, true); + vars_item->add_button(1, vars_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove Variation")); + vars_item->set_button_color(1, 0, Color(1, 1, 1, 0.75)); + + Ref<DynamicFontImportSettingsData> import_variation_data; + import_variation_data.instantiate(); + import_variation_data->owner = this; + ERR_FAIL_NULL(import_variation_data); + + for (List<ResourceImporter::ImportOption>::Element *E = options_variations.front(); E; E = E->next()) { + import_variation_data->defaults[E->get().option.name] = E->get().default_value; + } + + import_variation_data->options = options_variations; + inspector_vars->edit(import_variation_data.ptr()); + import_variation_data->notify_property_list_changed(); + + vars_item->set_metadata(0, import_variation_data); + + _variations_validate(); +} + +void DynamicFontImportSettings::_variation_selected() { + TreeItem *vars_item = vars_list->get_selected(); + if (vars_item) { + Ref<DynamicFontImportSettingsData> import_variation_data = vars_item->get_metadata(0); + ERR_FAIL_NULL(import_variation_data); + + inspector_vars->edit(import_variation_data.ptr()); + import_variation_data->notify_property_list_changed(); + } +} + +void DynamicFontImportSettings::_variation_remove(Object *p_item, int p_column, int p_id) { + TreeItem *vars_item = (TreeItem *)p_item; + ERR_FAIL_NULL(vars_item); + + inspector_vars->edit(nullptr); + + vars_list_root->remove_child(vars_item); + memdelete(vars_item); + + if (vars_list_root->get_first_child()) { + Ref<DynamicFontImportSettingsData> import_variation_data = vars_list_root->get_first_child()->get_metadata(0); + inspector_vars->edit(import_variation_data.ptr()); + import_variation_data->notify_property_list_changed(); + } + + _variations_validate(); +} + +void DynamicFontImportSettings::_variation_changed(const String &p_edited_property) { + _variations_validate(); +} + +void DynamicFontImportSettings::_variations_validate() { + String warn; + if (!vars_list_root->get_first_child()) { + warn = TTR("Warinig: There are no configurations specified, no glyphs will be pre-rendered."); + } + for (TreeItem *vars_item_a = vars_list_root->get_first_child(); vars_item_a; vars_item_a = vars_item_a->get_next()) { + Ref<DynamicFontImportSettingsData> import_variation_data_a = vars_item_a->get_metadata(0); + ERR_FAIL_NULL(import_variation_data_a); + + for (TreeItem *vars_item_b = vars_list_root->get_first_child(); vars_item_b; vars_item_b = vars_item_b->get_next()) { + if (vars_item_b != vars_item_a) { + bool match = true; + for (Map<StringName, Variant>::Element *E = import_variation_data_a->settings.front(); E; E = E->next()) { + Ref<DynamicFontImportSettingsData> import_variation_data_b = vars_item_b->get_metadata(0); + ERR_FAIL_NULL(import_variation_data_b); + match = match && (import_variation_data_b->settings[E->key()] == E->get()); + } + if (match) { + warn = TTR("Warinig: Multiple configurations have identical settings. Duplicates will be ignored."); + break; + } + } + } + } + if (warn.is_empty()) { + label_warn->set_text(""); + label_warn->hide(); + } else { + label_warn->set_text(warn); + label_warn->show(); + } +} + +/*************************************************************************/ +/* Page 3 callbacks: Text to select glyphs */ +/*************************************************************************/ + +void DynamicFontImportSettings::_change_text_opts() { + Vector<String> ftr = ftr_edit->get_text().split(","); + for (int i = 0; i < ftr.size(); i++) { + Vector<String> tokens = ftr[i].split("="); + if (tokens.size() == 2) { + text_edit->set_opentype_feature(tokens[0], tokens[1].to_int()); + } else if (tokens.size() == 1) { + text_edit->set_opentype_feature(tokens[0], 1); + } + } + text_edit->set_language(lang_edit->get_text()); +} + +void DynamicFontImportSettings::_glyph_clear() { + selected_glyphs.clear(); + label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size())); + _range_selected(); +} + +void DynamicFontImportSettings::_glyph_text_selected() { + Dictionary ftrs; + Vector<String> ftr = ftr_edit->get_text().split(","); + for (int i = 0; i < ftr.size(); i++) { + Vector<String> tokens = ftr[i].split("="); + if (tokens.size() == 2) { + ftrs[tokens[0]] = tokens[1].to_int(); + } else if (tokens.size() == 1) { + ftrs[tokens[0]] = 1; + } + } + + RID text_rid = TS->create_shaped_text(); + if (text_rid.is_valid()) { + TS->shaped_text_add_string(text_rid, text_edit->get_text(), font_main->get_rids(), 16, ftrs, text_edit->get_language()); + TS->shaped_text_shape(text_rid); + const Vector<TextServer::Glyph> &gl = TS->shaped_text_get_glyphs(text_rid); + + for (int i = 0; i < gl.size(); i++) { + if (gl[i].font_rid.is_valid() && gl[i].index != 0) { + selected_glyphs.insert(gl[i].index); + } + } + TS->free(text_rid); + label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size())); + } + _range_selected(); +} + +/*************************************************************************/ +/* Page 4 callbacks: Character map */ +/*************************************************************************/ + +void DynamicFontImportSettings::_glyph_selected() { + TreeItem *item = glyph_table->get_selected(); + ERR_FAIL_NULL(item); + + Color scol = glyph_table->get_theme_color("box_selection_fill_color", "Editor"); + Color fcol = glyph_table->get_theme_color("font_selected_color", "Editor"); + scol.a = 1.f; + + int32_t c = item->get_metadata(glyph_table->get_selected_column()); + if (font_main->has_char(c)) { + if (_char_update(c)) { + item->set_custom_color(glyph_table->get_selected_column(), fcol); + item->set_custom_bg_color(glyph_table->get_selected_column(), scol); + } else { + item->clear_custom_color(glyph_table->get_selected_column()); + item->clear_custom_bg_color(glyph_table->get_selected_column()); + } + } + label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size())); +} + +void DynamicFontImportSettings::_range_edited() { + TreeItem *item = glyph_tree->get_selected(); + ERR_FAIL_NULL(item); + Vector2i range = item->get_metadata(0); + _range_update(range.x, range.y); +} + +void DynamicFontImportSettings::_range_selected() { + TreeItem *item = glyph_tree->get_selected(); + if (item) { + Vector2i range = item->get_metadata(0); + _edit_range(range.x, range.y); + } +} + +void DynamicFontImportSettings::_edit_range(int32_t p_start, int32_t p_end) { + glyph_table->clear(); + + TreeItem *root = glyph_table->create_item(); + ERR_FAIL_NULL(root); + + Color scol = glyph_table->get_theme_color("box_selection_fill_color", "Editor"); + Color fcol = glyph_table->get_theme_color("font_selected_color", "Editor"); + scol.a = 1.f; + + TreeItem *item = nullptr; + int col = 0; + + for (int32_t c = p_start; c <= p_end; c++) { + if (col == 0) { + item = glyph_table->create_item(root); + ERR_FAIL_NULL(item); + item->set_text(0, _pad_zeros(String::num_int64(c, 16))); + item->set_text_align(0, TreeItem::ALIGN_LEFT); + item->set_selectable(0, false); + item->set_custom_bg_color(0, glyph_table->get_theme_color("dark_color_3", "Editor")); + } + if (font_main->has_char(c)) { + item->set_text(col + 1, String::chr(c)); + item->set_custom_color(col + 1, Color(1, 1, 1)); + if (selected_chars.has(c) || (font_main->get_data(0).is_valid() && selected_glyphs.has(font_main->get_data(0)->get_glyph_index(get_theme_font_size("font_size") * 2, c)))) { + item->set_custom_color(col + 1, fcol); + item->set_custom_bg_color(col + 1, scol); + } else { + item->clear_custom_color(col + 1); + item->clear_custom_bg_color(col + 1); + } + } else { + item->set_custom_bg_color(col + 1, glyph_table->get_theme_color("dark_color_2", "Editor")); + } + item->set_metadata(col + 1, c); + item->set_text_align(col + 1, TreeItem::ALIGN_CENTER); + item->set_selectable(col + 1, true); + item->set_custom_font(col + 1, font_main); + item->set_custom_font_size(col + 1, get_theme_font_size("font_size") * 2); + + col++; + if (col == 16) { + col = 0; + } + } + label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size())); +} + +bool DynamicFontImportSettings::_char_update(int32_t p_char) { + if (selected_chars.has(p_char)) { + selected_chars.erase(p_char); + return false; + } else if (font_main->get_data(0).is_valid() && selected_glyphs.has(font_main->get_data(0)->get_glyph_index(get_theme_font_size("font_size") * 2, p_char))) { + selected_glyphs.erase(font_main->get_data(0)->get_glyph_index(get_theme_font_size("font_size") * 2, p_char)); + return false; + } else { + selected_chars.insert(p_char); + return true; + } + label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size())); +} + +void DynamicFontImportSettings::_range_update(int32_t p_start, int32_t p_end) { + bool all_selected = true; + for (int32_t i = p_start; i <= p_end; i++) { + if (font_main->has_char(i)) { + if (font_main->get_data(0).is_valid()) { + all_selected = all_selected && (selected_chars.has(i) || (font_main->get_data(0).is_valid() && selected_glyphs.has(font_main->get_data(0)->get_glyph_index(get_theme_font_size("font_size") * 2, i)))); + } else { + all_selected = all_selected && selected_chars.has(i); + } + } + } + for (int32_t i = p_start; i <= p_end; i++) { + if (font_main->has_char(i)) { + if (!all_selected) { + selected_chars.insert(i); + } else { + selected_chars.erase(i); + if (font_main->get_data(0).is_valid()) { + selected_glyphs.erase(font_main->get_data(0)->get_glyph_index(get_theme_font_size("font_size") * 2, i)); + } + } + } + } + _edit_range(p_start, p_end); +} + +/*************************************************************************/ +/* Page 5 callbacks: CMetadata override */ +/*************************************************************************/ + +void DynamicFontImportSettings::_lang_add() { + menu_langs->set_position(lang_list->get_screen_transform().xform(lang_list->get_local_mouse_position())); + menu_langs->set_size(Vector2(1, 1)); + menu_langs->popup(); +} + +void DynamicFontImportSettings::_lang_add_item(int p_option) { + TreeItem *lang_item = lang_list->create_item(lang_list_root); + ERR_FAIL_NULL(lang_item); + + lang_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + lang_item->set_editable(0, true); + lang_item->set_checked(0, false); + lang_item->set_text(1, langs[p_option].code); + lang_item->set_editable(1, true); + lang_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove")); + lang_item->set_button_color(2, 0, Color(1, 1, 1, 0.75)); +} + +void DynamicFontImportSettings::_lang_remove(Object *p_item, int p_column, int p_id) { + TreeItem *lang_item = (TreeItem *)p_item; + ERR_FAIL_NULL(lang_item); + + lang_list_root->remove_child(lang_item); + memdelete(lang_item); +} + +void DynamicFontImportSettings::_script_add() { + menu_scripts->set_position(script_list->get_screen_transform().xform(script_list->get_local_mouse_position())); + menu_scripts->set_size(Vector2(1, 1)); + menu_scripts->popup(); +} + +void DynamicFontImportSettings::_script_add_item(int p_option) { + TreeItem *script_item = script_list->create_item(script_list_root); + ERR_FAIL_NULL(script_item); + + script_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + script_item->set_editable(0, true); + script_item->set_checked(0, false); + script_item->set_text(1, scripts[p_option].code); + script_item->set_editable(1, true); + script_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove")); + script_item->set_button_color(2, 0, Color(1, 1, 1, 0.75)); +} + +void DynamicFontImportSettings::_script_remove(Object *p_item, int p_column, int p_id) { + TreeItem *script_item = (TreeItem *)p_item; + ERR_FAIL_NULL(script_item); + + script_list_root->remove_child(script_item); + memdelete(script_item); +} + +/*************************************************************************/ +/* Common */ +/*************************************************************************/ + +DynamicFontImportSettings *DynamicFontImportSettings::singleton = nullptr; + +String DynamicFontImportSettings::_pad_zeros(const String &p_hex) const { + int len = CLAMP(5 - p_hex.length(), 0, 5); + return String("0").repeat(len) + p_hex; +} + +void DynamicFontImportSettings::_notification(int p_what) { + if (p_what == NOTIFICATION_READY) { + connect("confirmed", callable_mp(this, &DynamicFontImportSettings::_re_import)); + } else if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + add_lang->set_icon(add_var->get_theme_icon("Add", "EditorIcons")); + add_script->set_icon(add_var->get_theme_icon("Add", "EditorIcons")); + add_var->set_icon(add_var->get_theme_icon("Add", "EditorIcons")); + } +} + +void DynamicFontImportSettings::_re_import() { + Map<StringName, Variant> main_settings; + + main_settings["antialiased"] = import_settings_data->get("antialiased"); + main_settings["multichannel_signed_distance_field"] = import_settings_data->get("multichannel_signed_distance_field"); + main_settings["msdf_pixel_range"] = import_settings_data->get("msdf_pixel_range"); + main_settings["msdf_size"] = import_settings_data->get("msdf_size"); + main_settings["force_autohinter"] = import_settings_data->get("force_autohinter"); + main_settings["hinting"] = import_settings_data->get("hinting"); + main_settings["oversampling"] = import_settings_data->get("oversampling"); + main_settings["compress"] = import_settings_data->get("compress"); + + Vector<String> variations; + for (TreeItem *vars_item = vars_list_root->get_first_child(); vars_item; vars_item = vars_item->get_next()) { + String variation; + Ref<DynamicFontImportSettingsData> import_variation_data = vars_item->get_metadata(0); + ERR_FAIL_NULL(import_variation_data); + + String name = vars_item->get_text(0); + variation += ("name=" + name); + for (Map<StringName, Variant>::Element *E = import_variation_data->settings.front(); E; E = E->next()) { + if (!variation.is_empty()) { + variation += ","; + } + variation += (String(E->key()) + "=" + String(E->get())); + } + variations.push_back(variation); + } + main_settings["preload/configurations"] = variations; + + Vector<String> langs_enabled; + Vector<String> langs_disabled; + for (TreeItem *lang_item = lang_list_root->get_first_child(); lang_item; lang_item = lang_item->get_next()) { + bool selected = lang_item->is_checked(0); + String name = lang_item->get_text(1); + if (selected) { + langs_enabled.push_back(name); + } else { + langs_disabled.push_back(name); + } + } + main_settings["support_overrides/language_enabled"] = langs_enabled; + main_settings["support_overrides/language_disabled"] = langs_disabled; + + Vector<String> scripts_enabled; + Vector<String> scripts_disabled; + for (TreeItem *script_item = script_list_root->get_first_child(); script_item; script_item = script_item->get_next()) { + bool selected = script_item->is_checked(0); + String name = script_item->get_text(1); + if (selected) { + scripts_enabled.push_back(name); + } else { + scripts_disabled.push_back(name); + } + } + main_settings["support_overrides/script_enabled"] = scripts_enabled; + main_settings["support_overrides/script_disabled"] = scripts_disabled; + + if (!selected_chars.is_empty()) { + Vector<String> ranges; + char32_t start = selected_chars.front()->get(); + for (Set<char32_t>::Element *E = selected_chars.front()->next(); E; E = E->next()) { + if (E->prev() && ((E->prev()->get() + 1) != E->get())) { + ranges.push_back(String("0x") + String::num_int64(start, 16) + String("-0x") + String::num_int64(E->prev()->get(), 16)); + start = E->get(); + } + } + ranges.push_back(String("0x") + String::num_int64(start, 16) + String("-0x") + String::num_int64(selected_chars.back()->get(), 16)); + main_settings["preload/char_ranges"] = ranges; + } + + if (!selected_glyphs.is_empty()) { + Vector<String> ranges; + int32_t start = selected_glyphs.front()->get(); + for (Set<int32_t>::Element *E = selected_glyphs.front()->next(); E; E = E->next()) { + if (E->prev() && ((E->prev()->get() + 1) != E->get())) { + ranges.push_back(String("0x") + String::num_int64(start, 16) + String("-0x") + String::num_int64(E->prev()->get(), 16)); + start = E->get(); + } + } + ranges.push_back(String("0x") + String::num_int64(start, 16) + String("-0x") + String::num_int64(selected_glyphs.back()->get(), 16)); + main_settings["preload/glyph_ranges"] = ranges; + } + + if (OS::get_singleton()->is_stdout_verbose()) { + print_line("Import settings:"); + for (Map<StringName, Variant>::Element *E = main_settings.front(); E; E = E->next()) { + print_line(String(" ") + String(E->key()).utf8().get_data() + " == " + String(E->get()).utf8().get_data()); + } + } + + EditorFileSystem::get_singleton()->reimport_file_with_custom_parameters(base_path, "font_data_dynamic", main_settings); +} + +void DynamicFontImportSettings::open_settings(const String &p_path) { + // Load base font data. + Vector<uint8_t> data = FileAccess::get_file_as_array(p_path); + + // Load font for preview. + Ref<FontData> dfont_prev; + dfont_prev.instantiate(); + dfont_prev->set_data(data); + + font_preview.instantiate(); + font_preview->add_data(dfont_prev); + + String sample; + static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀"; + for (int i = 0; i < sample_base.length(); i++) { + if (dfont_prev->has_char(sample_base[i])) { + sample += sample_base[i]; + } + } + if (sample.is_empty()) { + sample = dfont_prev->get_supported_chars().substr(0, 6); + } + font_preview_label->set_text(sample); + + // Load second copy of font with MSDF disabled for the glyph table and metadata extraction. + Ref<FontData> dfont_main; + dfont_main.instantiate(); + dfont_main->set_data(data); + dfont_main->set_multichannel_signed_distance_field(false); + + font_main.instantiate(); + font_main->add_data(dfont_main); + text_edit->add_theme_font_override("font", font_main); + + base_path = p_path; + + inspector_vars->edit(nullptr); + inspector_general->edit(nullptr); + + int gww = get_theme_font("font")->get_string_size("00000", get_theme_font_size("font_size")).x + 50; + glyph_table->set_column_custom_minimum_width(0, gww); + + glyph_table->clear(); + vars_list->clear(); + lang_list->clear(); + script_list->clear(); + + selected_chars.clear(); + selected_glyphs.clear(); + text_edit->set_text(String()); + + vars_list_root = vars_list->create_item(); + lang_list_root = lang_list->create_item(); + script_list_root = script_list->create_item(); + + options_variations.clear(); + Dictionary var_list = dfont_main->get_supported_variation_list(); + for (int i = 0; i < var_list.size(); i++) { + int32_t tag = var_list.get_key_at_index(i); + Vector3i value = var_list.get_value_at_index(i); + options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, TS->tag_to_name(tag), PROPERTY_HINT_RANGE, itos(value.x) + "," + itos(value.y) + ",1"), value.z)); + } + options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "size", PROPERTY_HINT_RANGE, "0,127,1"), 16)); + options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "outline_size", PROPERTY_HINT_RANGE, "0,127,1"), 0)); + options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "extra_spacing_glyph"), 0)); + options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "extra_spacing_space"), 0)); + + import_settings_data->defaults.clear(); + for (List<ResourceImporter::ImportOption>::Element *E = options_general.front(); E; E = E->next()) { + import_settings_data->defaults[E->get().option.name] = E->get().default_value; + } + + Ref<ConfigFile> config; + config.instantiate(); + ERR_FAIL_NULL(config); + + Error err = config->load(p_path + ".import"); + print_verbose("Loading import settings:"); + if (err == OK) { + List<String> keys; + config->get_section_keys("params", &keys); + for (List<String>::Element *E = keys.front(); E; E = E->next()) { + String key = E->get(); + print_verbose(String(" ") + key + " == " + String(config->get_value("params", key))); + if (key == "preload/char_ranges") { + Vector<String> ranges = config->get_value("params", key); + for (int i = 0; i < ranges.size(); i++) { + int32_t start, end; + Vector<String> tokens = ranges[i].split("-"); + if (tokens.size() == 2) { + if (!ResourceImporterDynamicFont::_decode_range(tokens[0], start) || !ResourceImporterDynamicFont::_decode_range(tokens[1], end)) { + WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); + continue; + } + } else if (tokens.size() == 1) { + if (!ResourceImporterDynamicFont::_decode_range(tokens[0], start)) { + WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); + continue; + } + end = start; + } else { + WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); + continue; + } + for (int32_t j = start; j <= end; j++) { + selected_chars.insert(j); + } + } + } else if (key == "preload/glyph_ranges") { + Vector<String> ranges = config->get_value("params", key); + for (int i = 0; i < ranges.size(); i++) { + int32_t start, end; + Vector<String> tokens = ranges[i].split("-"); + if (tokens.size() == 2) { + if (!ResourceImporterDynamicFont::_decode_range(tokens[0], start) || !ResourceImporterDynamicFont::_decode_range(tokens[1], end)) { + WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); + continue; + } + } else if (tokens.size() == 1) { + if (!ResourceImporterDynamicFont::_decode_range(tokens[0], start)) { + WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); + continue; + } + end = start; + } else { + WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); + continue; + } + for (int32_t j = start; j <= end; j++) { + selected_glyphs.insert(j); + } + } + } else if (key == "preload/configurations") { + Vector<String> variations = config->get_value("params", key); + for (int i = 0; i < variations.size(); i++) { + TreeItem *vars_item = vars_list->create_item(vars_list_root); + ERR_FAIL_NULL(vars_item); + + vars_item->set_text(0, TTR("Configuration") + " " + itos(i)); + vars_item->set_editable(0, true); + vars_item->add_button(1, vars_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove Variation")); + vars_item->set_button_color(1, 0, Color(1, 1, 1, 0.75)); + + Ref<DynamicFontImportSettingsData> import_variation_data_custom; + import_variation_data_custom.instantiate(); + import_variation_data_custom->owner = this; + ERR_FAIL_NULL(import_variation_data_custom); + + for (List<ResourceImporter::ImportOption>::Element *F = options_variations.front(); F; F = F->next()) { + import_variation_data_custom->defaults[F->get().option.name] = F->get().default_value; + } + + import_variation_data_custom->options = options_variations; + + vars_item->set_metadata(0, import_variation_data_custom); + Vector<String> variation_tags = variations[i].split(","); + for (int j = 0; j < variation_tags.size(); j++) { + Vector<String> tokens = variation_tags[j].split("="); + if (tokens[0] == "name") { + vars_item->set_text(0, tokens[1]); + } else if (tokens[0] == "size" || tokens[0] == "outline_size" || tokens[0] == "extra_spacing_space" || tokens[0] == "extra_spacing_glyph") { + import_variation_data_custom->set(tokens[0], tokens[1].to_int()); + } else { + import_variation_data_custom->set(tokens[0], tokens[1].to_float()); + } + } + } + } else if (key == "support_overrides/language_enabled") { + PackedStringArray _langs = config->get_value("params", key); + for (int i = 0; i < _langs.size(); i++) { + TreeItem *lang_item = lang_list->create_item(lang_list_root); + ERR_FAIL_NULL(lang_item); + + lang_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + lang_item->set_editable(0, true); + lang_item->set_checked(0, true); + lang_item->set_text(1, _langs[i]); + lang_item->set_editable(1, true); + lang_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove")); + } + } else if (key == "support_overrides/language_disabled") { + PackedStringArray _langs = config->get_value("params", key); + for (int i = 0; i < _langs.size(); i++) { + TreeItem *lang_item = lang_list->create_item(lang_list_root); + ERR_FAIL_NULL(lang_item); + + lang_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + lang_item->set_editable(0, true); + lang_item->set_checked(0, false); + lang_item->set_text(1, _langs[i]); + lang_item->set_editable(1, true); + lang_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove")); + } + } else if (key == "support_overrides/script_enabled") { + PackedStringArray _scripts = config->get_value("params", key); + for (int i = 0; i < _scripts.size(); i++) { + TreeItem *script_item = script_list->create_item(script_list_root); + ERR_FAIL_NULL(script_item); + + script_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + script_item->set_editable(0, true); + script_item->set_checked(0, true); + script_item->set_text(1, _scripts[i]); + script_item->set_editable(1, true); + script_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove")); + } + } else if (key == "support_overrides/script_disabled") { + PackedStringArray _scripts = config->get_value("params", key); + for (int i = 0; i < _scripts.size(); i++) { + TreeItem *script_item = script_list->create_item(script_list_root); + ERR_FAIL_NULL(script_item); + + script_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + script_item->set_editable(0, true); + script_item->set_checked(0, false); + script_item->set_text(1, _scripts[i]); + script_item->set_editable(1, true); + script_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove")); + } + } else { + Variant value = config->get_value("params", key); + import_settings_data->defaults[key] = value; + } + } + } + label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size())); + + import_settings_data->options = options_general; + inspector_general->edit(import_settings_data.ptr()); + import_settings_data->notify_property_list_changed(); + + if (font_preview->get_data_count() > 0) { + font_preview->get_data(0)->set_antialiased(import_settings_data->get("antialiased")); + font_preview->get_data(0)->set_multichannel_signed_distance_field(import_settings_data->get("multichannel_signed_distance_field")); + font_preview->get_data(0)->set_msdf_pixel_range(import_settings_data->get("msdf_pixel_range")); + font_preview->get_data(0)->set_msdf_size(import_settings_data->get("msdf_size")); + font_preview->get_data(0)->set_force_autohinter(import_settings_data->get("force_autohinter")); + font_preview->get_data(0)->set_hinting((TextServer::Hinting)import_settings_data->get("hinting").operator int()); + font_preview->get_data(0)->set_oversampling(import_settings_data->get("oversampling")); + } + font_preview_label->add_theme_font_override("font", font_preview); + font_preview_label->update(); + + _variations_validate(); + + popup_centered_ratio(); + + set_title(vformat(TTR("Advanced Import Settings for '%s'"), base_path.get_file())); +} + +DynamicFontImportSettings *DynamicFontImportSettings::get_singleton() { + return singleton; +} + +DynamicFontImportSettings::DynamicFontImportSettings() { + singleton = this; + + options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "antialiased"), true)); + options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true)); + options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_RANGE, "1,100,1"), 8)); + options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_RANGE, "1,250,1"), 48)); + options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false)); + options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1)); + options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), 0.0)); + options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "compress", PROPERTY_HINT_NONE, ""), false)); + + // Popup menus + + menu_langs = memnew(PopupMenu); + menu_langs->set_name("Language"); + for (int i = 0; langs[i].name != String(); i++) { + if (langs[i].name == "-") { + menu_langs->add_separator(); + } else { + menu_langs->add_item(langs[i].name + " (" + langs[i].code + ")", i); + } + } + add_child(menu_langs); + menu_langs->connect("id_pressed", callable_mp(this, &DynamicFontImportSettings::_lang_add_item)); + + menu_scripts = memnew(PopupMenu); + menu_scripts->set_name("Script"); + for (int i = 0; scripts[i].name != String(); i++) { + if (scripts[i].name == "-") { + menu_scripts->add_separator(); + } else { + menu_scripts->add_item(scripts[i].name + " (" + scripts[i].code + ")", i); + } + } + add_child(menu_scripts); + menu_scripts->connect("id_pressed", callable_mp(this, &DynamicFontImportSettings::_script_add_item)); + + Color warn_color = (EditorNode::get_singleton()) ? EditorNode::get_singleton()->get_gui_base()->get_theme_color("warning_color", "Editor") : Color(1, 1, 0); + + // Root layout + + VBoxContainer *root_vb = memnew(VBoxContainer); + add_child(root_vb); + + main_pages = memnew(TabContainer); + main_pages->set_v_size_flags(Control::SIZE_EXPAND_FILL); + main_pages->set_h_size_flags(Control::SIZE_EXPAND_FILL); + root_vb->add_child(main_pages); + + label_warn = memnew(Label); + label_warn->set_align(Label::ALIGN_CENTER); + label_warn->set_text(""); + root_vb->add_child(label_warn); + label_warn->add_theme_color_override("font_color", warn_color); + label_warn->hide(); + + // Page 1 layout: Rendering Options + + VBoxContainer *page1_vb = memnew(VBoxContainer); + page1_vb->set_meta("_tab_name", TTR("Rendering options")); + main_pages->add_child(page1_vb); + + page1_description = memnew(Label); + page1_description->set_text(TTR("Select font rendering options:")); + page1_description->set_h_size_flags(Control::SIZE_EXPAND_FILL); + page1_vb->add_child(page1_description); + + HSplitContainer *page1_hb = memnew(HSplitContainer); + page1_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL); + page1_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + page1_vb->add_child(page1_hb); + + font_preview_label = memnew(Label); + font_preview_label->add_theme_font_size_override("font_size", 200 * EDSCALE); + font_preview_label->set_align(Label::ALIGN_CENTER); + font_preview_label->set_valign(Label::VALIGN_CENTER); + font_preview_label->set_autowrap_mode(Label::AUTOWRAP_ARBITRARY); + font_preview_label->set_clip_text(true); + font_preview_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); + font_preview_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + page1_hb->add_child(font_preview_label); + + inspector_general = memnew(EditorInspector); + inspector_general->set_v_size_flags(Control::SIZE_EXPAND_FILL); + inspector_general->set_custom_minimum_size(Size2(300 * EDSCALE, 250 * EDSCALE)); + inspector_general->connect("property_edited", callable_mp(this, &DynamicFontImportSettings::_main_prop_changed)); + page1_hb->add_child(inspector_general); + + // Page 2 layout: Configurations + VBoxContainer *page2_vb = memnew(VBoxContainer); + page2_vb->set_meta("_tab_name", TTR("Sizes and variations")); + main_pages->add_child(page2_vb); + + page2_description = memnew(Label); + page2_description->set_text(TTR("Add font size, variation coordinates, and extra spacing combinations to pre-render:")); + page2_description->set_h_size_flags(Control::SIZE_EXPAND_FILL); + page2_description->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART); + page2_vb->add_child(page2_description); + + HSplitContainer *page2_hb = memnew(HSplitContainer); + page2_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL); + page2_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + page2_vb->add_child(page2_hb); + + VBoxContainer *page2_side_vb = memnew(VBoxContainer); + page2_hb->add_child(page2_side_vb); + + HBoxContainer *page2_hb_vars = memnew(HBoxContainer); + page2_side_vb->add_child(page2_hb_vars); + + label_vars = memnew(Label); + page2_hb_vars->add_child(label_vars); + label_vars->set_align(Label::ALIGN_CENTER); + label_vars->set_h_size_flags(Control::SIZE_EXPAND_FILL); + label_vars->set_text(TTR("Configuration:")); + + add_var = memnew(Button); + page2_hb_vars->add_child(add_var); + add_var->set_tooltip(TTR("Add configuration")); + add_var->set_icon(add_var->get_theme_icon("Add", "EditorIcons")); + add_var->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_variation_add)); + + vars_list = memnew(Tree); + page2_side_vb->add_child(vars_list); + vars_list->set_custom_minimum_size(Size2(300 * EDSCALE, 0)); + vars_list->set_hide_root(true); + vars_list->set_columns(2); + vars_list->set_column_expand(0, true); + vars_list->set_column_custom_minimum_width(0, 80 * EDSCALE); + vars_list->set_column_expand(1, false); + vars_list->set_column_custom_minimum_width(1, 50 * EDSCALE); + vars_list->connect("item_selected", callable_mp(this, &DynamicFontImportSettings::_variation_selected)); + vars_list->connect("button_pressed", callable_mp(this, &DynamicFontImportSettings::_variation_remove)); + vars_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + inspector_vars = memnew(EditorInspector); + inspector_vars->set_v_size_flags(Control::SIZE_EXPAND_FILL); + inspector_vars->connect("property_edited", callable_mp(this, &DynamicFontImportSettings::_variation_changed)); + page2_hb->add_child(inspector_vars); + + // Page 3 layout: Text to select glyphs + VBoxContainer *page3_vb = memnew(VBoxContainer); + page3_vb->set_meta("_tab_name", TTR("Glyphs from the text")); + main_pages->add_child(page3_vb); + + page3_description = memnew(Label); + page3_description->set_text(TTR("Enter a text to shape and add all required glyphs to pre-render list:")); + page3_description->set_h_size_flags(Control::SIZE_EXPAND_FILL); + page3_description->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART); + page3_vb->add_child(page3_description); + + HBoxContainer *ot_hb = memnew(HBoxContainer); + page3_vb->add_child(ot_hb); + ot_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + Label *label_ed_ftr = memnew(Label); + ot_hb->add_child(label_ed_ftr); + label_ed_ftr->set_text(TTR("OpenType features:")); + + ftr_edit = memnew(LineEdit); + ot_hb->add_child(ftr_edit); + ftr_edit->connect("text_changed", callable_mp(this, &DynamicFontImportSettings::_change_text_opts)); + ftr_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + Label *label_ed_lang = memnew(Label); + ot_hb->add_child(label_ed_lang); + label_ed_lang->set_text(TTR("Text language:")); + + lang_edit = memnew(LineEdit); + ot_hb->add_child(lang_edit); + lang_edit->connect("text_changed", callable_mp(this, &DynamicFontImportSettings::_change_text_opts)); + lang_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + text_edit = memnew(TextEdit); + page3_vb->add_child(text_edit); + text_edit->set_v_size_flags(Control::SIZE_EXPAND_FILL); + text_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + HBoxContainer *text_hb = memnew(HBoxContainer); + page3_vb->add_child(text_hb); + text_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + label_glyphs = memnew(Label); + text_hb->add_child(label_glyphs); + label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(0)); + label_glyphs->set_custom_minimum_size(Size2(50 * EDSCALE, 0)); + + Button *btn_fill = memnew(Button); + text_hb->add_child(btn_fill); + btn_fill->set_text(TTR("Shape text and add glyphs")); + btn_fill->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_glyph_text_selected)); + + Button *btn_clear = memnew(Button); + text_hb->add_child(btn_clear); + btn_clear->set_text(TTR("Clear glyph list")); + btn_clear->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_glyph_clear)); + + // Page 4 layout: Character map + VBoxContainer *page4_vb = memnew(VBoxContainer); + page4_vb->set_meta("_tab_name", TTR("Glyphs from the character map")); + main_pages->add_child(page4_vb); + + page4_description = memnew(Label); + page4_description->set_text(TTR("Add or remove additional glyphs from the character map to pre-render list:\nNote: Some stylistic alternatives and glyph variants do not have one-to-one correspondence to character, and not shown in this map, use \"Glyphs from the text\" to add these.")); + page4_description->set_h_size_flags(Control::SIZE_EXPAND_FILL); + page4_description->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART); + page4_vb->add_child(page4_description); + + HSplitContainer *glyphs_split = memnew(HSplitContainer); + glyphs_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); + glyphs_split->set_h_size_flags(Control::SIZE_EXPAND_FILL); + page4_vb->add_child(glyphs_split); + + glyph_table = memnew(Tree); + glyphs_split->add_child(glyph_table); + glyph_table->set_custom_minimum_size(Size2((30 * 16 + 100) * EDSCALE, 0)); + glyph_table->set_columns(17); + glyph_table->set_column_expand(0, false); + glyph_table->set_hide_root(true); + glyph_table->set_allow_reselect(true); + glyph_table->set_select_mode(Tree::SELECT_SINGLE); + glyph_table->connect("item_activated", callable_mp(this, &DynamicFontImportSettings::_glyph_selected)); + glyph_table->set_column_titles_visible(true); + for (int i = 0; i < 16; i++) { + glyph_table->set_column_title(i + 1, String::num_int64(i, 16)); + } + glyph_table->add_theme_style_override("selected", glyph_table->get_theme_stylebox("bg")); + glyph_table->add_theme_style_override("selected_focus", glyph_table->get_theme_stylebox("bg")); + glyph_table->add_theme_constant_override("hseparation", 0); + glyph_table->set_h_size_flags(Control::SIZE_EXPAND_FILL); + glyph_table->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + glyph_tree = memnew(Tree); + glyphs_split->add_child(glyph_tree); + glyph_tree->set_custom_minimum_size(Size2(300 * EDSCALE, 0)); + glyph_tree->set_columns(3); + glyph_tree->set_hide_root(true); + glyph_tree->set_column_expand(0, false); + glyph_tree->set_column_expand(1, true); + glyph_tree->set_column_custom_minimum_width(0, 120 * EDSCALE); + glyph_tree->connect("item_activated", callable_mp(this, &DynamicFontImportSettings::_range_edited)); + glyph_tree->connect("item_selected", callable_mp(this, &DynamicFontImportSettings::_range_selected)); + glyph_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + glyph_root = glyph_tree->create_item(); + for (int i = 0; unicode_ranges[i].name != String(); i++) { + _add_glyph_range_item(unicode_ranges[i].start, unicode_ranges[i].end, unicode_ranges[i].name); + } + + // Page 4 layout: Metadata override + VBoxContainer *page5_vb = memnew(VBoxContainer); + page5_vb->set_meta("_tab_name", TTR("Metadata override")); + main_pages->add_child(page5_vb); + + page5_description = memnew(Label); + page5_description->set_text(TTR("Add or remove language and script support overrides, to control fallback font selection order:")); + page5_description->set_h_size_flags(Control::SIZE_EXPAND_FILL); + page5_description->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART); + page5_vb->add_child(page5_description); + + HBoxContainer *hb_lang = memnew(HBoxContainer); + page5_vb->add_child(hb_lang); + + label_langs = memnew(Label); + label_langs->set_align(Label::ALIGN_CENTER); + label_langs->set_h_size_flags(Control::SIZE_EXPAND_FILL); + label_langs->set_text(TTR("Language support overrides")); + hb_lang->add_child(label_langs); + + add_lang = memnew(Button); + hb_lang->add_child(add_lang); + add_lang->set_tooltip(TTR("Add language override")); + add_lang->set_icon(add_var->get_theme_icon("Add", "EditorIcons")); + add_lang->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_lang_add)); + + lang_list = memnew(Tree); + page5_vb->add_child(lang_list); + lang_list->set_hide_root(true); + lang_list->set_columns(3); + lang_list->set_column_expand(0, false); // Check + lang_list->set_column_custom_minimum_width(0, 50 * EDSCALE); + lang_list->set_column_expand(1, true); + lang_list->set_column_custom_minimum_width(1, 80 * EDSCALE); + lang_list->set_column_expand(2, false); + lang_list->set_column_custom_minimum_width(2, 50 * EDSCALE); + lang_list->connect("button_pressed", callable_mp(this, &DynamicFontImportSettings::_lang_remove)); + lang_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + HBoxContainer *hb_script = memnew(HBoxContainer); + page5_vb->add_child(hb_script); + + label_script = memnew(Label); + label_script->set_align(Label::ALIGN_CENTER); + label_script->set_h_size_flags(Control::SIZE_EXPAND_FILL); + label_script->set_text(TTR("Script support overrides")); + hb_script->add_child(label_script); + + add_script = memnew(Button); + hb_script->add_child(add_script); + add_script->set_tooltip(TTR("Add script override")); + add_script->set_icon(add_var->get_theme_icon("Add", "EditorIcons")); + add_script->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_script_add)); + + script_list = memnew(Tree); + page5_vb->add_child(script_list); + script_list->set_hide_root(true); + script_list->set_columns(3); + script_list->set_column_expand(0, false); + script_list->set_column_custom_minimum_width(0, 50 * EDSCALE); + script_list->set_column_expand(1, true); + script_list->set_column_custom_minimum_width(1, 80 * EDSCALE); + script_list->set_column_expand(2, false); + script_list->set_column_custom_minimum_width(2, 50 * EDSCALE); + script_list->connect("button_pressed", callable_mp(this, &DynamicFontImportSettings::_script_remove)); + script_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + // Common + + import_settings_data.instantiate(); + import_settings_data->owner = this; + + get_ok_button()->set_text(TTR("Reimport")); + get_cancel_button()->set_text(TTR("Close")); +} diff --git a/editor/import/dynamicfont_import_settings.h b/editor/import/dynamicfont_import_settings.h new file mode 100644 index 0000000000..05f5e8e00b --- /dev/null +++ b/editor/import/dynamicfont_import_settings.h @@ -0,0 +1,167 @@ +/*************************************************************************/ +/* dynamicfont_import_settings.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 FONTDATA_IMPORT_SETTINGS_H +#define FONTDATA_IMPORT_SETTINGS_H + +#include "editor/editor_file_dialog.h" +#include "editor/editor_inspector.h" + +#include "editor/import/resource_importer_dynamicfont.h" + +#include "scene/gui/dialogs.h" +#include "scene/gui/item_list.h" +#include "scene/gui/option_button.h" +#include "scene/gui/split_container.h" +#include "scene/gui/subviewport_container.h" +#include "scene/gui/tab_container.h" +#include "scene/gui/text_edit.h" +#include "scene/gui/tree.h" + +#include "scene/resources/font.h" +#include "servers/text_server.h" + +class DynamicFontImportSettingsData; + +class DynamicFontImportSettings : public ConfirmationDialog { + GDCLASS(DynamicFontImportSettings, ConfirmationDialog) + friend class DynamicFontImportSettingsData; + + enum ItemButton { + BUTTON_ADD_VAR, + BUTTON_REMOVE_VAR, + }; + + static DynamicFontImportSettings *singleton; + + String base_path; + + Ref<DynamicFontImportSettingsData> import_settings_data; + List<ResourceImporter::ImportOption> options_variations; + List<ResourceImporter::ImportOption> options_general; + + // Root layout + Label *label_warn = nullptr; + TabContainer *main_pages = nullptr; + + // Page 1 layout: Rendering Options + Label *page1_description = nullptr; + Label *font_preview_label = nullptr; + EditorInspector *inspector_general = nullptr; + + void _main_prop_changed(const String &p_edited_property); + + // Page 2 layout: Configurations + Label *page2_description = nullptr; + Label *label_vars = nullptr; + Button *add_var = nullptr; + Tree *vars_list = nullptr; + TreeItem *vars_list_root = nullptr; + EditorInspector *inspector_vars = nullptr; + + void _variation_add(); + void _variation_selected(); + void _variation_remove(Object *p_item, int p_column, int p_id); + void _variation_changed(const String &p_edited_property); + void _variations_validate(); + + // Page 3 layout: Text to select glyphs + Label *page3_description = nullptr; + Label *label_glyphs = nullptr; + TextEdit *text_edit = nullptr; + LineEdit *ftr_edit = nullptr; + LineEdit *lang_edit = nullptr; + + void _change_text_opts(); + void _glyph_text_selected(); + void _glyph_clear(); + + // Page 4 layout: Character map + Label *page4_description = nullptr; + Tree *glyph_table = nullptr; + Tree *glyph_tree = nullptr; + TreeItem *glyph_root = nullptr; + + void _glyph_selected(); + void _range_edited(); + void _range_selected(); + void _edit_range(int32_t p_start, int32_t p_end); + bool _char_update(int32_t p_char); + void _range_update(int32_t p_start, int32_t p_end); + + // Page 5 layout: Metadata override + Label *page5_description = nullptr; + Button *add_lang = nullptr; + Button *add_script = nullptr; + + PopupMenu *menu_langs = nullptr; + PopupMenu *menu_scripts = nullptr; + + Tree *lang_list = nullptr; + TreeItem *lang_list_root = nullptr; + + Tree *script_list = nullptr; + TreeItem *script_list_root = nullptr; + Label *label_langs = nullptr; + Label *label_script = nullptr; + + void _lang_add(); + void _lang_add_item(int p_option); + void _lang_remove(Object *p_item, int p_column, int p_id); + + void _script_add(); + void _script_add_item(int p_option); + void _script_remove(Object *p_item, int p_column, int p_id); + + // Common + + void _add_glyph_range_item(int32_t p_start, int32_t p_end, const String &p_name); + + Ref<Font> font_preview; + Ref<Font> font_main; + + Set<char32_t> selected_chars; + Set<int32_t> selected_glyphs; + + void _re_import(); + + String _pad_zeros(const String &p_hex) const; + +protected: + void _notification(int p_what); + +public: + void open_settings(const String &p_path); + static DynamicFontImportSettings *get_singleton(); + + DynamicFontImportSettings(); +}; + +#endif // FONTDATA_IMPORT_SETTINGS_H diff --git a/editor/import/resource_importer_bmfont.cpp b/editor/import/resource_importer_bmfont.cpp new file mode 100644 index 0000000000..2e7ef1402b --- /dev/null +++ b/editor/import/resource_importer_bmfont.cpp @@ -0,0 +1,797 @@ +/*************************************************************************/ +/* resource_importer_bmfont.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 "resource_importer_bmfont.h" + +#include "core/io/image_loader.h" +#include "core/io/resource_saver.h" + +String ResourceImporterBMFont::get_importer_name() const { + return "font_data_bmfont"; +} + +String ResourceImporterBMFont::get_visible_name() const { + return "Font Data (AngelCode BMFont)"; +} + +void ResourceImporterBMFont::get_recognized_extensions(List<String> *p_extensions) const { + if (p_extensions) { + p_extensions->push_back("font"); + p_extensions->push_back("fnt"); + } +} + +String ResourceImporterBMFont::get_save_extension() const { + return "fontdata"; +} + +String ResourceImporterBMFont::get_resource_type() const { + return "FontData"; +} + +bool ResourceImporterBMFont::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { + return true; +} + +void ResourceImporterBMFont::get_import_options(List<ImportOption> *r_options, int p_preset) const { + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true)); +} + +void _convert_packed_8bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_sz) { + int w = p_source->get_width(); + int h = p_source->get_height(); + + PackedByteArray imgdata = p_source->get_data(); + const uint8_t *r = imgdata.ptr(); + + PackedByteArray imgdata_r; + imgdata_r.resize(w * h * 2); + uint8_t *wr = imgdata_r.ptrw(); + + PackedByteArray imgdata_g; + imgdata_g.resize(w * h * 2); + uint8_t *wg = imgdata_g.ptrw(); + + PackedByteArray imgdata_b; + imgdata_b.resize(w * h * 2); + uint8_t *wb = imgdata_b.ptrw(); + + PackedByteArray imgdata_a; + imgdata_a.resize(w * h * 2); + uint8_t *wa = imgdata_a.ptrw(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + int ofs_src = (i * w + j) * 4; + int ofs_dst = (i * w + j) * 2; + wr[ofs_dst + 0] = 255; + wr[ofs_dst + 1] = r[ofs_src + 0]; + wg[ofs_dst + 0] = 255; + wg[ofs_dst + 1] = r[ofs_src + 1]; + wb[ofs_dst + 0] = 255; + wb[ofs_dst + 1] = r[ofs_src + 2]; + wa[ofs_dst + 0] = 255; + wa[ofs_dst + 1] = r[ofs_src + 3]; + } + } + Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r)); + r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r); + Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g)); + r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g); + Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b)); + r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b); + Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a)); + r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a); +} + +void _convert_packed_4bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_sz) { + int w = p_source->get_width(); + int h = p_source->get_height(); + + PackedByteArray imgdata = p_source->get_data(); + const uint8_t *r = imgdata.ptr(); + + PackedByteArray imgdata_r; + imgdata_r.resize(w * h * 2); + uint8_t *wr = imgdata_r.ptrw(); + + PackedByteArray imgdata_g; + imgdata_g.resize(w * h * 2); + uint8_t *wg = imgdata_g.ptrw(); + + PackedByteArray imgdata_b; + imgdata_b.resize(w * h * 2); + uint8_t *wb = imgdata_b.ptrw(); + + PackedByteArray imgdata_a; + imgdata_a.resize(w * h * 2); + uint8_t *wa = imgdata_a.ptrw(); + + PackedByteArray imgdata_ro; + imgdata_ro.resize(w * h * 2); + uint8_t *wro = imgdata_ro.ptrw(); + + PackedByteArray imgdata_go; + imgdata_go.resize(w * h * 2); + uint8_t *wgo = imgdata_go.ptrw(); + + PackedByteArray imgdata_bo; + imgdata_bo.resize(w * h * 2); + uint8_t *wbo = imgdata_bo.ptrw(); + + PackedByteArray imgdata_ao; + imgdata_ao.resize(w * h * 2); + uint8_t *wao = imgdata_ao.ptrw(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + int ofs_src = (i * w + j) * 4; + int ofs_dst = (i * w + j) * 2; + wr[ofs_dst + 0] = 255; + wro[ofs_dst + 0] = 255; + if (r[ofs_src + 0] > 0x0F) { + wr[ofs_dst + 1] = (r[ofs_src + 0] - 0x0F) * 2; + wro[ofs_dst + 1] = 0; + } else { + wr[ofs_dst + 1] = 0; + wro[ofs_dst + 1] = r[ofs_src + 0] * 2; + } + wg[ofs_dst + 0] = 255; + wgo[ofs_dst + 0] = 255; + if (r[ofs_src + 1] > 0x0F) { + wg[ofs_dst + 1] = (r[ofs_src + 1] - 0x0F) * 2; + wgo[ofs_dst + 1] = 0; + } else { + wg[ofs_dst + 1] = 0; + wgo[ofs_dst + 1] = r[ofs_src + 1] * 2; + } + wb[ofs_dst + 0] = 255; + wbo[ofs_dst + 0] = 255; + if (r[ofs_src + 2] > 0x0F) { + wb[ofs_dst + 1] = (r[ofs_src + 2] - 0x0F) * 2; + wbo[ofs_dst + 1] = 0; + } else { + wb[ofs_dst + 1] = 0; + wbo[ofs_dst + 1] = r[ofs_src + 2] * 2; + } + wa[ofs_dst + 0] = 255; + wao[ofs_dst + 0] = 255; + if (r[ofs_src + 3] > 0x0F) { + wa[ofs_dst + 1] = (r[ofs_src + 3] - 0x0F) * 2; + wao[ofs_dst + 1] = 0; + } else { + wa[ofs_dst + 1] = 0; + wao[ofs_dst + 1] = r[ofs_src + 3] * 2; + } + } + } + Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r)); + r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r); + Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g)); + r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g); + Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b)); + r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b); + Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a)); + r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a); + + Ref<Image> img_ro = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ro)); + r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 0, img_ro); + Ref<Image> img_go = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_go)); + r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 1, img_go); + Ref<Image> img_bo = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_bo)); + r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 2, img_bo); + Ref<Image> img_ao = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ao)); + r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 3, img_ao); +} + +void _convert_rgba_4bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_sz) { + int w = p_source->get_width(); + int h = p_source->get_height(); + + PackedByteArray imgdata = p_source->get_data(); + const uint8_t *r = imgdata.ptr(); + + PackedByteArray imgdata_g; + imgdata_g.resize(w * h * 4); + uint8_t *wg = imgdata_g.ptrw(); + + PackedByteArray imgdata_o; + imgdata_o.resize(w * h * 4); + uint8_t *wo = imgdata_o.ptrw(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + int ofs = (i * w + j) * 4; + + if (r[ofs + 0] > 0x7F) { + wg[ofs + 0] = r[ofs + 0]; + wo[ofs + 0] = 0; + } else { + wg[ofs + 0] = 0; + wo[ofs + 0] = r[ofs + 0] * 2; + } + if (r[ofs + 1] > 0x7F) { + wg[ofs + 1] = r[ofs + 1]; + wo[ofs + 1] = 0; + } else { + wg[ofs + 1] = 0; + wo[ofs + 1] = r[ofs + 1] * 2; + } + if (r[ofs + 2] > 0x7F) { + wg[ofs + 2] = r[ofs + 2]; + wo[ofs + 2] = 0; + } else { + wg[ofs + 2] = 0; + wo[ofs + 2] = r[ofs + 2] * 2; + } + if (r[ofs + 3] > 0x7F) { + wg[ofs + 3] = r[ofs + 3]; + wo[ofs + 3] = 0; + } else { + wg[ofs + 3] = 0; + wo[ofs + 3] = r[ofs + 3] * 2; + } + } + } + Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_g)); + r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g); + + Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_o)); + r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page, img_o); +} + +void _convert_mono_8bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) { + int w = p_source->get_width(); + int h = p_source->get_height(); + + PackedByteArray imgdata = p_source->get_data(); + const uint8_t *r = imgdata.ptr(); + + int size = 4; + if (p_source->get_format() == Image::FORMAT_L8) { + size = 1; + p_ch = 0; + } + + PackedByteArray imgdata_g; + imgdata_g.resize(w * h * 2); + uint8_t *wg = imgdata_g.ptrw(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + int ofs_src = (i * w + j) * size; + int ofs_dst = (i * w + j) * 2; + wg[ofs_dst + 0] = 255; + wg[ofs_dst + 1] = r[ofs_src + p_ch]; + } + } + Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g)); + r_font->set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_g); +} + +void _convert_mono_4bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) { + int w = p_source->get_width(); + int h = p_source->get_height(); + + PackedByteArray imgdata = p_source->get_data(); + const uint8_t *r = imgdata.ptr(); + + int size = 4; + if (p_source->get_format() == Image::FORMAT_L8) { + size = 1; + p_ch = 0; + } + + PackedByteArray imgdata_g; + imgdata_g.resize(w * h * 2); + uint8_t *wg = imgdata_g.ptrw(); + + PackedByteArray imgdata_o; + imgdata_o.resize(w * h * 2); + uint8_t *wo = imgdata_o.ptrw(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + int ofs_src = (i * w + j) * size; + int ofs_dst = (i * w + j) * 2; + wg[ofs_dst + 0] = 255; + wo[ofs_dst + 0] = 255; + if (r[ofs_src + p_ch] > 0x7F) { + wg[ofs_dst + 1] = r[ofs_src + p_ch]; + wo[ofs_dst + 1] = 0; + } else { + wg[ofs_dst + 1] = 0; + wo[ofs_dst + 1] = r[ofs_src + p_ch] * 2; + } + } + } + Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g)); + r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g); + + Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_o)); + r_font->set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_o); +} + +Error ResourceImporterBMFont::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { + print_verbose("Importing BMFont font from: " + p_source_file); + + Ref<FontData> font; + font.instantiate(); + font->set_antialiased(false); + font->set_multichannel_signed_distance_field(false); + font->set_force_autohinter(false); + font->set_hinting(TextServer::HINTING_NONE); + font->set_oversampling(1.0f); + + FileAccessRef f = FileAccess::open(p_source_file, FileAccess::READ); + if (f == nullptr) { + ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Cannot open font from file ") + "\"" + p_source_file + "\"."); + } + + int base_size = 16; + int height = 0; + int ascent = 0; + int outline = 0; + + bool packed = false; + uint8_t ch[4] = { 0, 0, 0, 0 }; // RGBA + int first_gl_ch = -1; + int first_ol_ch = -1; + int first_cm_ch = -1; + + unsigned char magic[4]; + f->get_buffer((unsigned char *)&magic, 4); + if (magic[0] == 'B' && magic[1] == 'M' && magic[2] == 'F') { + // Binary BMFont file. + ERR_FAIL_COND_V_MSG(magic[3] != 3, ERR_CANT_CREATE, vformat(TTR("Version %d of BMFont is not supported."), (int)magic[3])); + + uint8_t block_type = f->get_8(); + uint32_t block_size = f->get_32(); + while (!f->eof_reached()) { + uint64_t off = f->get_position(); + switch (block_type) { + case 1: /* info */ { + ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, TTR("Invalid BMFont info block size.")); + base_size = f->get_16(); + uint8_t flags = f->get_8(); + ERR_FAIL_COND_V_MSG(flags & 0x02, ERR_CANT_CREATE, TTR("Non-unicode version of BMFont is not supported.")); + f->get_8(); // non-unicode charset, skip + f->get_16(); // stretch_h, skip + f->get_8(); // aa, skip + f->get_32(); // padding, skip + f->get_16(); // spacing, skip + outline = f->get_8(); + // font name, skip + font->set_fixed_size(base_size); + } break; + case 2: /* common */ { + ERR_FAIL_COND_V_MSG(block_size != 15, ERR_CANT_CREATE, TTR("Invalid BMFont common block size.")); + height = f->get_16(); + ascent = f->get_16(); + f->get_32(); // scale, skip + f->get_16(); // pages, skip + uint8_t flags = f->get_8(); + packed = (flags & 0x01); + ch[3] = f->get_8(); + ch[0] = f->get_8(); + ch[1] = f->get_8(); + ch[2] = f->get_8(); + for (int i = 0; i < 4; i++) { + if (ch[i] == 0 && first_gl_ch == -1) { + first_gl_ch = i; + } + if (ch[i] == 1 && first_ol_ch == -1) { + first_ol_ch = i; + } + if (ch[i] == 2 && first_cm_ch == -1) { + first_cm_ch = i; + } + } + } break; + case 3: /* pages */ { + int page = 0; + CharString cs; + char32_t c = f->get_8(); + while (!f->eof_reached() && f->get_position() <= off + block_size) { + if (c == '\0') { + String base_dir = p_source_file.get_base_dir(); + String file = base_dir.plus_file(String::utf8(cs.ptr(), cs.length())); + if (RenderingServer::get_singleton() != nullptr) { + Ref<Image> img; + img.instantiate(); + Error err = ImageLoader::load_image(file, img); + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture: ") + "\"" + file + "\"."); + + if (packed) { + if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline + outline = 0; + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_packed_8bit(font, img, page, base_size); + } else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_packed_4bit(font, img, page, base_size); + } else { + ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format.")); + } + } else { + if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline + outline = 0; + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + font->set_texture_image(0, Vector2i(base_size, 0), page, img); + } else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_rgba_4bit(font, img, page, base_size); + } else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0); + _convert_mono_8bit(font, img, page, first_ol_ch, base_size, 1); + } else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_mono_4bit(font, img, page, first_cm_ch, base_size, 1); + } else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline + outline = 0; + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0); + } else { + ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format.")); + } + } + } + page++; + cs = ""; + } else { + cs += c; + } + c = f->get_8(); + } + } break; + case 4: /* chars */ { + int char_count = block_size / 20; + for (int i = 0; i < char_count; i++) { + Vector2 advance; + Vector2 size; + Vector2 offset; + Rect2 uv_rect; + + char32_t idx = f->get_32(); + uv_rect.position.x = (int16_t)f->get_16(); + uv_rect.position.y = (int16_t)f->get_16(); + uv_rect.size.width = (int16_t)f->get_16(); + size.width = uv_rect.size.width; + uv_rect.size.height = (int16_t)f->get_16(); + size.height = uv_rect.size.height; + offset.x = (int16_t)f->get_16(); + offset.y = (int16_t)f->get_16() - ascent; + advance.x = (int16_t)f->get_16(); + if (advance.x < 0) { + advance.x = size.width + 1; + } + + int texture_idx = f->get_8(); + uint8_t channel = f->get_8(); + + ERR_FAIL_COND_V_MSG(!packed && channel != 15, ERR_CANT_CREATE, TTR("Invalid glyph channel.")); + int ch_off = 0; + switch (channel) { + case 1: + ch_off = 2; + break; // B + case 2: + ch_off = 1; + break; // G + case 4: + ch_off = 0; + break; // R + case 8: + ch_off = 3; + break; // A + default: + ch_off = 0; + break; + } + font->set_glyph_advance(0, base_size, idx, advance); + font->set_glyph_offset(0, Vector2i(base_size, 0), idx, offset); + font->set_glyph_size(0, Vector2i(base_size, 0), idx, size); + font->set_glyph_uv_rect(0, Vector2i(base_size, 0), idx, uv_rect); + font->set_glyph_texture_idx(0, Vector2i(base_size, 0), idx, texture_idx * (packed ? 4 : 1) + ch_off); + if (outline > 0) { + font->set_glyph_offset(0, Vector2i(base_size, 1), idx, offset); + font->set_glyph_size(0, Vector2i(base_size, 1), idx, size); + font->set_glyph_uv_rect(0, Vector2i(base_size, 1), idx, uv_rect); + font->set_glyph_texture_idx(0, Vector2i(base_size, 1), idx, texture_idx * (packed ? 4 : 1) + ch_off); + } + } + } break; + case 5: /* kerning */ { + int pair_count = block_size / 10; + for (int i = 0; i < pair_count; i++) { + Vector2i kpk; + kpk.x = f->get_32(); + kpk.y = f->get_32(); + font->set_kerning(0, base_size, kpk, Vector2((int16_t)f->get_16(), 0)); + } + } break; + default: { + ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Invalid BMFont block type.")); + } break; + } + f->seek(off + block_size); + block_type = f->get_8(); + block_size = f->get_32(); + } + + } else { + // Text BMFont file. + f->seek(0); + while (true) { + String line = f->get_line(); + + int delimiter = line.find(" "); + String type = line.substr(0, delimiter); + int pos = delimiter + 1; + Map<String, String> keys; + + while (pos < line.size() && line[pos] == ' ') { + pos++; + } + + while (pos < line.size()) { + int eq = line.find("=", pos); + if (eq == -1) { + break; + } + String key = line.substr(pos, eq - pos); + int end = -1; + String value; + if (line[eq + 1] == '"') { + end = line.find("\"", eq + 2); + if (end == -1) { + break; + } + value = line.substr(eq + 2, end - 1 - eq - 1); + pos = end + 1; + } else { + end = line.find(" ", eq + 1); + if (end == -1) { + end = line.size(); + } + value = line.substr(eq + 1, end - eq); + pos = end; + } + + while (pos < line.size() && line[pos] == ' ') { + pos++; + } + + keys[key] = value; + } + + if (type == "info") { + if (keys.has("size")) { + base_size = keys["size"].to_int(); + font->set_fixed_size(base_size); + } + if (keys.has("outline")) { + outline = keys["outline"].to_int(); + } + ERR_FAIL_COND_V_MSG((!keys.has("unicode") || keys["unicode"].to_int() != 1), ERR_CANT_CREATE, TTR("Non-unicode version of BMFont is not supported.")); + } else if (type == "common") { + if (keys.has("lineHeight")) { + height = keys["lineHeight"].to_int(); + } + if (keys.has("base")) { + ascent = keys["base"].to_int(); + } + if (keys.has("packed")) { + packed = (keys["packed"].to_int() == 1); + } + if (keys.has("alphaChnl")) { + ch[3] = keys["alphaChnl"].to_int(); + } + if (keys.has("redChnl")) { + ch[0] = keys["redChnl"].to_int(); + } + if (keys.has("greenChnl")) { + ch[1] = keys["greenChnl"].to_int(); + } + if (keys.has("blueChnl")) { + ch[2] = keys["blueChnl"].to_int(); + } + for (int i = 0; i < 4; i++) { + if (ch[i] == 0 && first_gl_ch == -1) { + first_gl_ch = i; + } + if (ch[i] == 1 && first_ol_ch == -1) { + first_ol_ch = i; + } + if (ch[i] == 2 && first_cm_ch == -1) { + first_cm_ch = i; + } + } + } else if (type == "page") { + int page = 0; + if (keys.has("id")) { + page = keys["id"].to_int(); + } + if (keys.has("file")) { + String base_dir = p_source_file.get_base_dir(); + String file = base_dir.plus_file(keys["file"]); + if (RenderingServer::get_singleton() != nullptr) { + Ref<Image> img; + img.instantiate(); + Error err = ImageLoader::load_image(file, img); + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture: ") + "\"" + file + "\"."); + if (packed) { + if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline + outline = 0; + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_packed_8bit(font, img, page, base_size); + } else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_packed_4bit(font, img, page, base_size); + } else { + ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format.")); + } + } else { + if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline + outline = 0; + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + font->set_texture_image(0, Vector2i(base_size, 0), page, img); + } else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_rgba_4bit(font, img, page, base_size); + } else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0); + _convert_mono_8bit(font, img, page, first_ol_ch, base_size, 1); + } else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_mono_4bit(font, img, page, first_cm_ch, base_size, 1); + } else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline + outline = 0; + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format.")); + _convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0); + } else { + ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format.")); + } + } + } + } + } else if (type == "char") { + char32_t idx = 0; + Vector2 advance; + Vector2 size; + Vector2 offset; + Rect2 uv_rect; + int texture_idx = -1; + uint8_t channel = 15; + + if (keys.has("id")) { + idx = keys["id"].to_int(); + } + if (keys.has("x")) { + uv_rect.position.x = keys["x"].to_int(); + } + if (keys.has("y")) { + uv_rect.position.y = keys["y"].to_int(); + } + if (keys.has("width")) { + uv_rect.size.width = keys["width"].to_int(); + size.width = keys["width"].to_int(); + } + if (keys.has("height")) { + uv_rect.size.height = keys["height"].to_int(); + size.height = keys["height"].to_int(); + } + if (keys.has("xoffset")) { + offset.x = keys["xoffset"].to_int(); + } + if (keys.has("yoffset")) { + offset.y = keys["yoffset"].to_int() - ascent; + } + if (keys.has("page")) { + texture_idx = keys["page"].to_int(); + } + if (keys.has("xadvance")) { + advance.x = keys["xadvance"].to_int(); + } + if (advance.x < 0) { + advance.x = size.width + 1; + } + if (keys.has("chnl")) { + channel = keys["chnl"].to_int(); + } + + ERR_FAIL_COND_V_MSG(!packed && channel != 15, ERR_CANT_CREATE, TTR("Invalid glyph channel.")); + int ch_off = 0; + switch (channel) { + case 1: + ch_off = 2; + break; // B + case 2: + ch_off = 1; + break; // G + case 4: + ch_off = 0; + break; // R + case 8: + ch_off = 3; + break; // A + default: + ch_off = 0; + break; + } + font->set_glyph_advance(0, base_size, idx, advance); + font->set_glyph_offset(0, Vector2i(base_size, 0), idx, offset); + font->set_glyph_size(0, Vector2i(base_size, 0), idx, size); + font->set_glyph_uv_rect(0, Vector2i(base_size, 0), idx, uv_rect); + font->set_glyph_texture_idx(0, Vector2i(base_size, 0), idx, texture_idx * (packed ? 4 : 1) + ch_off); + if (outline > 0) { + font->set_glyph_offset(0, Vector2i(base_size, 1), idx, offset); + font->set_glyph_size(0, Vector2i(base_size, 1), idx, size); + font->set_glyph_uv_rect(0, Vector2i(base_size, 1), idx, uv_rect); + font->set_glyph_texture_idx(0, Vector2i(base_size, 1), idx, texture_idx * (packed ? 4 : 1) + ch_off); + } + } else if (type == "kerning") { + Vector2i kpk; + if (keys.has("first")) { + kpk.x = keys["first"].to_int(); + } + if (keys.has("second")) { + kpk.y = keys["second"].to_int(); + } + if (keys.has("amount")) { + font->set_kerning(0, base_size, kpk, Vector2(keys["amount"].to_int(), 0)); + } + } + + if (f->eof_reached()) { + break; + } + } + } + + font->set_ascent(0, base_size, ascent); + font->set_descent(0, base_size, height - ascent); + + int flg = ResourceSaver::SaverFlags::FLAG_BUNDLE_RESOURCES | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS; + if ((bool)p_options["compress"]) { + flg |= ResourceSaver::SaverFlags::FLAG_COMPRESS; + } + + print_verbose("Saving to: " + p_save_path + ".fontdata"); + Error err = ResourceSaver::save(p_save_path + ".fontdata", font, flg); + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save font to file \"" + p_save_path + ".res\"."); + print_verbose("Done saving to: " + p_save_path + ".fontdata"); + return OK; +} + +ResourceImporterBMFont::ResourceImporterBMFont() { +} diff --git a/editor/import/resource_importer_bmfont.h b/editor/import/resource_importer_bmfont.h new file mode 100644 index 0000000000..065703132a --- /dev/null +++ b/editor/import/resource_importer_bmfont.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* resource_importer_bmfont.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 RESOURCE_IMPORTER_BMFONT_H +#define RESOURCE_IMPORTER_BMFONT_H + +#include "core/io/resource_importer.h" +#include "scene/resources/font.h" +#include "servers/text_server.h" + +class ResourceImporterBMFont : public ResourceImporter { + GDCLASS(ResourceImporterBMFont, ResourceImporter); + +public: + virtual String get_importer_name() const override; + virtual String get_visible_name() const override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual String get_save_extension() const override; + virtual String get_resource_type() const override; + + 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 Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; + + ResourceImporterBMFont(); +}; + +#endif // RESOURCE_IMPORTER_BMFONT_H diff --git a/editor/import/resource_importer_dynamicfont.cpp b/editor/import/resource_importer_dynamicfont.cpp new file mode 100644 index 0000000000..8e01adbd56 --- /dev/null +++ b/editor/import/resource_importer_dynamicfont.cpp @@ -0,0 +1,304 @@ +/*************************************************************************/ +/* resource_importer_dynamicfont.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 "resource_importer_dynamicfont.h" + +#include "dynamicfont_import_settings.h" + +#include "core/io/file_access.h" +#include "core/io/resource_saver.h" +#include "editor/editor_node.h" +#include "modules/modules_enabled.gen.h" + +String ResourceImporterDynamicFont::get_importer_name() const { + return "font_data_dynamic"; +} + +String ResourceImporterDynamicFont::get_visible_name() const { + return "Font Data (Dynamic Font)"; +} + +void ResourceImporterDynamicFont::get_recognized_extensions(List<String> *p_extensions) const { + if (p_extensions) { +#ifdef MODULE_FREETYPE_ENABLED + p_extensions->push_back("ttf"); + p_extensions->push_back("otf"); + p_extensions->push_back("woff"); + //p_extensions->push_back("woff2"); + p_extensions->push_back("pfb"); + p_extensions->push_back("pfm"); +#endif + } +} + +String ResourceImporterDynamicFont::get_save_extension() const { + return "fontdata"; +} + +String ResourceImporterDynamicFont::get_resource_type() const { + return "FontData"; +} + +bool ResourceImporterDynamicFont::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { + if (p_option == "msdf_pixel_range" && !bool(p_options["multichannel_signed_distance_field"])) { + return false; + } + if (p_option == "msdf_size" && !bool(p_options["multichannel_signed_distance_field"])) { + return false; + } + if (p_option == "oversampling" && bool(p_options["multichannel_signed_distance_field"])) { + return false; + } + return true; +} + +int ResourceImporterDynamicFont::get_preset_count() const { + return PRESET_MAX; +} + +String ResourceImporterDynamicFont::get_preset_name(int p_idx) const { + switch (p_idx) { + case PRESET_DYNAMIC: + return TTR("Dynamically rendered TrueType/OpenType font"); + case PRESET_MSDF: + return TTR("Prerendered multichannel(+true) signed distance field"); + default: + return String(); + } +} + +void ResourceImporterDynamicFont::get_import_options(List<ImportOption> *r_options, int p_preset) const { + bool msdf = p_preset == PRESET_MSDF; + + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "antialiased"), true)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), (msdf) ? true : false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_RANGE, "1,100,1"), 8)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_RANGE, "1,250,1"), 48)); + + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), 0.0)); + + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true)); + + r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "preload/char_ranges"), Vector<String>())); + r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "preload/glyph_ranges"), Vector<String>())); + r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "preload/configurations"), Vector<String>())); + + r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "support_overrides/language_enabled"), Vector<String>())); + r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "support_overrides/language_disabled"), Vector<String>())); + + r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "support_overrides/script_enabled"), Vector<String>())); + r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "support_overrides/script_disabled"), Vector<String>())); +} + +bool ResourceImporterDynamicFont::_decode_variation(const String &p_token, Dictionary &r_variations, Vector2i &r_size, String &r_name, Vector2i &r_spacing) { + Vector<String> tokens = p_token.split("="); + if (tokens.size() == 2) { + if (tokens[0] == "name") { + r_name = tokens[1]; + } else if (tokens[0] == "size") { + r_size.x = tokens[1].to_int(); + } else if (tokens[0] == "outline_size") { + r_size.y = tokens[1].to_int(); + } else if (tokens[0] == "spacing_space") { + r_spacing.x = tokens[1].to_int(); + } else if (tokens[0] == "spacing_glyph") { + r_spacing.y = tokens[1].to_int(); + } else { + r_variations[tokens[0]] = tokens[1].to_float(); + } + return true; + } else { + WARN_PRINT("Invalid variation: '" + p_token + "'."); + return false; + } +} + +bool ResourceImporterDynamicFont::_decode_range(const String &p_token, int32_t &r_pos) { + if (p_token.begins_with("U+") || p_token.begins_with("u+") || p_token.begins_with("0x")) { + // Unicode character hex index. + r_pos = p_token.substr(2).hex_to_int(); + return true; + } else if (p_token.length() == 3 && p_token[0] == '\'' && p_token[2] == '\'') { + // Unicode character. + r_pos = p_token.unicode_at(1); + return true; + } else if (p_token.is_numeric()) { + // Unicode character decimal index. + r_pos = p_token.to_int(); + return true; + } else { + return false; + } +} + +bool ResourceImporterDynamicFont::has_advanced_options() const { + return true; +} +void ResourceImporterDynamicFont::show_advanced_options(const String &p_path) { + DynamicFontImportSettings::get_singleton()->open_settings(p_path); +} + +Error ResourceImporterDynamicFont::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { + print_verbose("Importing dynamic font from: " + p_source_file); + + bool antialiased = p_options["antialiased"]; + bool msdf = p_options["multichannel_signed_distance_field"]; + int px_range = p_options["msdf_pixel_range"]; + int px_size = p_options["msdf_size"]; + + bool autohinter = p_options["force_autohinter"]; + int hinting = p_options["hinting"]; + real_t oversampling = p_options["oversampling"]; + + // Load base font data. + Vector<uint8_t> data = FileAccess::get_file_as_array(p_source_file); + + // Create font. + Ref<FontData> font; + font.instantiate(); + font->set_data(data); + font->set_antialiased(antialiased); + font->set_multichannel_signed_distance_field(msdf); + font->set_msdf_pixel_range(px_range); + font->set_msdf_size(px_size); + font->set_fixed_size(0); + font->set_force_autohinter(autohinter); + font->set_hinting((TextServer::Hinting)hinting); + font->set_oversampling(oversampling); + + Vector<String> lang_en = p_options["support_overrides/language_enabled"]; + for (int i = 0; i < lang_en.size(); i++) { + font->set_language_support_override(lang_en[i], true); + } + + Vector<String> lang_dis = p_options["support_overrides/language_disabled"]; + for (int i = 0; i < lang_dis.size(); i++) { + font->set_language_support_override(lang_dis[i], false); + } + + Vector<String> scr_en = p_options["support_overrides/script_enabled"]; + for (int i = 0; i < scr_en.size(); i++) { + font->set_script_support_override(scr_en[i], true); + } + + Vector<String> scr_dis = p_options["support_overrides/script_disabled"]; + for (int i = 0; i < scr_dis.size(); i++) { + font->set_script_support_override(scr_dis[i], false); + } + + Vector<String> variations = p_options["preload/configurations"]; + Vector<String> char_ranges = p_options["preload/char_ranges"]; + Vector<String> gl_ranges = p_options["preload/glyph_ranges"]; + + for (int i = 0; i < variations.size(); i++) { + String name; + Dictionary var; + Vector2i size = Vector2(16, 0); + Vector2i spacing; + + Vector<String> variation_tags = variations[i].split(","); + for (int j = 0; j < variation_tags.size(); j++) { + if (!_decode_variation(variation_tags[j], var, size, name, spacing)) { + WARN_PRINT(vformat(TTR("Invalid variation: \"%s\""), variations[i])); + continue; + } + } + RID conf = font->find_cache(var); + + for (int j = 0; j < char_ranges.size(); j++) { + int32_t start, end; + Vector<String> tokens = char_ranges[j].split("-"); + if (tokens.size() == 2) { + if (!_decode_range(tokens[0], start) || !_decode_range(tokens[1], end)) { + WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), char_ranges[j])); + continue; + } + } else if (tokens.size() == 1) { + if (!_decode_range(tokens[0], start)) { + WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), char_ranges[j])); + continue; + } + end = start; + } else { + WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), char_ranges[j])); + continue; + } + + // Preload character ranges for each variations / sizes. + print_verbose(vformat(TTR("Pre-rendering range U+%s...%s from configuration \"%s\" (%d / %d)..."), String::num_int64(start, 16), String::num_int64(end, 16), name, i + 1, variations.size())); + TS->font_render_range(conf, size, start, end); + } + + for (int j = 0; j < gl_ranges.size(); j++) { + int32_t start, end; + Vector<String> tokens = gl_ranges[j].split("-"); + if (tokens.size() == 2) { + if (!_decode_range(tokens[0], start) || !_decode_range(tokens[1], end)) { + WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), gl_ranges[j])); + continue; + } + } else if (tokens.size() == 1) { + if (!_decode_range(tokens[0], start)) { + WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), gl_ranges[j])); + continue; + } + end = start; + } else { + WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), gl_ranges[j])); + continue; + } + + // Preload glyph range for each variations / sizes. + print_verbose(vformat(TTR("Pre-rendering glyph range 0x%s...%s from configuration \"%s\" (%d / %d)..."), String::num_int64(start, 16), String::num_int64(end, 16), name, i + 1, variations.size())); + for (int32_t k = start; k <= end; k++) { + TS->font_render_glyph(conf, size, k); + } + } + + TS->font_set_spacing(conf, size.x, TextServer::SPACING_SPACE, spacing.x); + TS->font_set_spacing(conf, size.x, TextServer::SPACING_GLYPH, spacing.y); + } + + int flg = ResourceSaver::SaverFlags::FLAG_BUNDLE_RESOURCES | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS; + if ((bool)p_options["compress"]) { + flg |= ResourceSaver::SaverFlags::FLAG_COMPRESS; + } + + print_verbose("Saving to: " + p_save_path + ".fontdata"); + Error err = ResourceSaver::save(p_save_path + ".fontdata", font, flg); + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save font to file \"" + p_save_path + ".res\"."); + print_verbose("Done saving to: " + p_save_path + ".fontdata"); + return OK; +} + +ResourceImporterDynamicFont::ResourceImporterDynamicFont() { +} diff --git a/editor/import/resource_importer_dynamicfont.h b/editor/import/resource_importer_dynamicfont.h new file mode 100644 index 0000000000..52f256ab96 --- /dev/null +++ b/editor/import/resource_importer_dynamicfont.h @@ -0,0 +1,71 @@ +/*************************************************************************/ +/* resource_importer_dynamicfont.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 RESOURCE_IMPORTER_FONT_DATA_H +#define RESOURCE_IMPORTER_FONT_DATA_H + +#include "core/io/resource_importer.h" +#include "scene/resources/font.h" +#include "servers/text_server.h" + +class ResourceImporterDynamicFont : public ResourceImporter { + GDCLASS(ResourceImporterDynamicFont, ResourceImporter); + + enum Presets { + PRESET_DYNAMIC, + PRESET_MSDF, + PRESET_MAX + }; + +public: + static bool _decode_range(const String &p_token, int32_t &r_pos); + static bool _decode_variation(const String &p_token, Dictionary &r_variations, Vector2i &r_size, String &r_name, Vector2i &r_spacing); + + virtual String get_importer_name() const override; + virtual String get_visible_name() const override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual String get_save_extension() const override; + virtual String get_resource_type() const override; + + virtual int get_preset_count() const override; + virtual String get_preset_name(int p_idx) const override; + + 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; + + bool has_advanced_options() const override; + void show_advanced_options(const String &p_path) override; + + virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; + + ResourceImporterDynamicFont(); +}; + +#endif // RESOURCE_IMPORTER_FONTDATA_H diff --git a/editor/import/resource_importer_imagefont.cpp b/editor/import/resource_importer_imagefont.cpp new file mode 100644 index 0000000000..997280d1dd --- /dev/null +++ b/editor/import/resource_importer_imagefont.cpp @@ -0,0 +1,162 @@ +/*************************************************************************/ +/* resource_importer_imagefont.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 "resource_importer_imagefont.h" + +#include "core/io/image_loader.h" +#include "core/io/resource_saver.h" + +String ResourceImporterImageFont::get_importer_name() const { + return "font_data_image"; +} + +String ResourceImporterImageFont::get_visible_name() const { + return "Font Data (Monospace Image Font)"; +} + +void ResourceImporterImageFont::get_recognized_extensions(List<String> *p_extensions) const { + if (p_extensions) { + ImageLoader::get_recognized_extensions(p_extensions); + } +} + +String ResourceImporterImageFont::get_save_extension() const { + return "fontdata"; +} + +String ResourceImporterImageFont::get_resource_type() const { + return "FontData"; +} + +bool ResourceImporterImageFont::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { + return true; +} + +void ResourceImporterImageFont::get_import_options(List<ImportOption> *r_options, int p_preset) const { + r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "character_ranges"), Vector<String>())); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "columns"), 1)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "rows"), 1)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "font_size"), 14)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true)); +} + +bool ResourceImporterImageFont::_decode_range(const String &p_token, int32_t &r_pos) { + if (p_token.begins_with("U+") || p_token.begins_with("u+") || p_token.begins_with("0x")) { + // Unicode character hex index. + r_pos = p_token.substr(2).hex_to_int(); + return true; + } else if (p_token.length() == 3 && p_token[0] == '\'' && p_token[2] == '\'') { + // Unicode character. + r_pos = p_token.unicode_at(1); + return true; + } else if (p_token.is_numeric()) { + // Unicode character decimal index. + r_pos = p_token.to_int(); + return true; + } else { + return false; + } +} + +Error ResourceImporterImageFont::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { + print_verbose("Importing image font from: " + p_source_file); + + int columns = p_options["columns"]; + int rows = p_options["rows"]; + int base_size = p_options["font_size"]; + Vector<String> ranges = p_options["character_ranges"]; + + Ref<FontData> font; + font.instantiate(); + font->set_antialiased(false); + font->set_multichannel_signed_distance_field(false); + font->set_fixed_size(base_size); + font->set_force_autohinter(false); + font->set_hinting(TextServer::HINTING_NONE); + font->set_oversampling(1.0f); + + Ref<Image> img; + img.instantiate(); + Error err = ImageLoader::load_image(p_source_file, img); + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture: ") + "\"" + p_source_file + "\"."); + font->set_texture_image(0, Vector2i(base_size, 0), 0, img); + + int count = columns * rows; + int chr_width = img->get_width() / columns; + int chr_height = img->get_height() / rows; + int pos = 0; + + for (int i = 0; i < ranges.size(); i++) { + int32_t start, end; + Vector<String> tokens = ranges[i].split("-"); + if (tokens.size() == 2) { + if (!_decode_range(tokens[0], start) || !_decode_range(tokens[1], end)) { + WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); + continue; + } + } else if (tokens.size() == 1) { + if (!_decode_range(tokens[0], start)) { + WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); + continue; + } + end = start; + } else { + WARN_PRINT("Invalid range: \"" + ranges[i] + "\""); + continue; + } + for (int32_t idx = start; idx <= end; idx++) { + int x = pos % columns; + int y = pos / columns; + font->set_glyph_advance(0, base_size, idx, Vector2(chr_width, 0)); + font->set_glyph_offset(0, Vector2i(base_size, 0), idx, Vector2(0, -0.5 * chr_height)); + font->set_glyph_size(0, Vector2i(base_size, 0), idx, Vector2(chr_width, chr_height)); + font->set_glyph_uv_rect(0, Vector2i(base_size, 0), idx, Rect2(chr_width * x, chr_height * y, chr_width, chr_height)); + font->set_glyph_texture_idx(0, Vector2i(base_size, 0), idx, 0); + pos++; + ERR_FAIL_COND_V_MSG(pos >= count, ERR_CANT_CREATE, "Too many characters in range."); + } + } + font->set_ascent(0, base_size, 0.5 * chr_height); + font->set_descent(0, base_size, 0.5 * chr_height); + + int flg = ResourceSaver::SaverFlags::FLAG_BUNDLE_RESOURCES | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS; + if ((bool)p_options["compress"]) { + flg |= ResourceSaver::SaverFlags::FLAG_COMPRESS; + } + + print_verbose("Saving to: " + p_save_path + ".fontdata"); + err = ResourceSaver::save(p_save_path + ".fontdata", font, flg); + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save font to file \"" + p_save_path + ".res\"."); + print_verbose("Done saving to: " + p_save_path + ".fontdata"); + return OK; +} + +ResourceImporterImageFont::ResourceImporterImageFont() { +} diff --git a/editor/import/resource_importer_imagefont.h b/editor/import/resource_importer_imagefont.h new file mode 100644 index 0000000000..9b2b38596f --- /dev/null +++ b/editor/import/resource_importer_imagefont.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* resource_importer_imagefont.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 RESOURCE_IMPORTER_IMAGE_FONT_H +#define RESOURCE_IMPORTER_IMAGE_FONT_H + +#include "core/io/resource_importer.h" +#include "scene/resources/font.h" +#include "servers/text_server.h" + +class ResourceImporterImageFont : public ResourceImporter { + GDCLASS(ResourceImporterImageFont, ResourceImporter); + +public: + static bool _decode_range(const String &p_token, int32_t &r_pos); + + virtual String get_importer_name() const override; + virtual String get_visible_name() const override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual String get_save_extension() const override; + virtual String get_resource_type() const override; + + 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 Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; + + ResourceImporterImageFont(); +}; + +#endif // RESOURCE_IMPORTER_IMAGE_FONT_H diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 2b03ad928c..c2244befa1 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -46,6 +46,7 @@ #include "scene/resources/box_shape_3d.h" #include "scene/resources/packed_scene.h" #include "scene/resources/resource_format_text.h" +#include "scene/resources/separation_ray_shape_3d.h" #include "scene/resources/sphere_shape_3d.h" #include "scene/resources/surface_tool.h" #include "scene/resources/world_margin_shape_3d.h" @@ -379,6 +380,11 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E BoxShape3D *boxShape = memnew(BoxShape3D); boxShape->set_size(Vector3(2, 2, 2)); colshape->set_shape(boxShape); + } else if (empty_draw_type == "SINGLE_ARROW") { + SeparationRayShape3D *rayShape = memnew(SeparationRayShape3D); + rayShape->set_length(1); + colshape->set_shape(rayShape); + Object::cast_to<Node3D>(sb)->rotate_x(Math_PI / 2); } else if (empty_draw_type == "IMAGE") { WorldMarginShape3D *world_margin_shape = memnew(WorldMarginShape3D); colshape->set_shape(world_margin_shape); diff --git a/editor/import/resource_importer_shader_file.cpp b/editor/import/resource_importer_shader_file.cpp index 4d92490675..c01d8068da 100644 --- a/editor/import/resource_importer_shader_file.cpp +++ b/editor/import/resource_importer_shader_file.cpp @@ -78,7 +78,7 @@ static String _include_function(const String &p_path, void *userpointer) { String *base_path = (String *)userpointer; String include = p_path; - if (include.is_rel_path()) { + if (include.is_relative_path()) { include = base_path->plus_file(include); } diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index daf7b15794..61745cb6ee 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -393,7 +393,7 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String float lossy = p_options["compress/lossy_quality"]; int pack_channels = p_options["compress/channel_pack"]; bool mipmaps = p_options["mipmaps/generate"]; - uint32_t mipmap_limit = int(mipmaps ? int(p_options["mipmaps/limit"]) : int(-1)); + uint32_t mipmap_limit = mipmaps ? uint32_t(p_options["mipmaps/limit"]) : uint32_t(-1); bool fix_alpha_border = p_options["process/fix_alpha_border"]; bool premult_alpha = p_options["process/premult_alpha"]; bool normal_map_invert_y = p_options["process/normal_map_invert_y"]; diff --git a/editor/import/scene_importer_mesh.cpp b/editor/import/scene_importer_mesh.cpp index 0d14347225..d8248e2670 100644 --- a/editor/import/scene_importer_mesh.cpp +++ b/editor/import/scene_importer_mesh.cpp @@ -33,6 +33,8 @@ #include "core/math/math_defs.h" #include "scene/resources/surface_tool.h" +#include <cstdint> + void EditorSceneImporterMesh::add_blend_shape(const String &p_name) { ERR_FAIL_COND(surfaces.size() > 0); blend_shapes.push_back(p_name); @@ -55,13 +57,14 @@ Mesh::BlendShapeMode EditorSceneImporterMesh::get_blend_shape_mode() const { return blend_shape_mode; } -void EditorSceneImporterMesh::add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, const Dictionary &p_lods, const Ref<Material> &p_material, const String &p_name) { +void EditorSceneImporterMesh::add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, const Dictionary &p_lods, const Ref<Material> &p_material, const String &p_name, const uint32_t p_flags) { ERR_FAIL_COND(p_blend_shapes.size() != blend_shapes.size()); ERR_FAIL_COND(p_arrays.size() != Mesh::ARRAY_MAX); Surface s; s.primitive = p_primitive; s.arrays = p_arrays; s.name = p_name; + s.flags = p_flags; Vector<Vector3> vertex_array = p_arrays[Mesh::ARRAY_VERTEX]; int vertex_count = vertex_array.size(); @@ -110,6 +113,12 @@ String EditorSceneImporterMesh::get_surface_name(int p_surface) const { ERR_FAIL_INDEX_V(p_surface, surfaces.size(), String()); return surfaces[p_surface].name; } +void EditorSceneImporterMesh::set_surface_name(int p_surface, const String &p_name) { + ERR_FAIL_INDEX(p_surface, surfaces.size()); + surfaces.write[p_surface].name = p_name; + mesh.unref(); +} + Array EditorSceneImporterMesh::get_surface_blend_shape_arrays(int p_surface, int p_blend_shape) const { ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array()); ERR_FAIL_INDEX_V(p_blend_shape, surfaces[p_surface].blend_shape_data.size(), Array()); @@ -132,6 +141,11 @@ float EditorSceneImporterMesh::get_surface_lod_size(int p_surface, int p_lod) co return surfaces[p_surface].lods[p_lod].distance; } +uint32_t EditorSceneImporterMesh::get_surface_format(int p_surface) const { + ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0); + return surfaces[p_surface].flags; +} + Ref<Material> EditorSceneImporterMesh::get_surface_material(int p_surface) const { ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Ref<Material>()); return surfaces[p_surface].material; @@ -140,6 +154,7 @@ Ref<Material> EditorSceneImporterMesh::get_surface_material(int p_surface) const void EditorSceneImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_material) { ERR_FAIL_INDEX(p_surface, surfaces.size()); surfaces.write[p_surface].material = p_material; + mesh.unref(); } Basis EditorSceneImporterMesh::compute_rotation_matrix_from_ortho_6d(Vector3 p_x_raw, Vector3 p_y_raw) { @@ -244,7 +259,7 @@ bool EditorSceneImporterMesh::has_mesh() const { return mesh.is_valid(); } -Ref<ArrayMesh> EditorSceneImporterMesh::get_mesh(const Ref<Mesh> &p_base) { +Ref<ArrayMesh> EditorSceneImporterMesh::get_mesh(const Ref<ArrayMesh> &p_base) { ERR_FAIL_COND_V(surfaces.size() == 0, Ref<ArrayMesh>()); if (mesh.is_null()) { @@ -276,7 +291,7 @@ Ref<ArrayMesh> EditorSceneImporterMesh::get_mesh(const Ref<Mesh> &p_base) { } } - mesh->add_surface_from_arrays(surfaces[i].primitive, surfaces[i].arrays, bs_data, lods); + mesh->add_surface_from_arrays(surfaces[i].primitive, surfaces[i].arrays, bs_data, lods, surfaces[i].flags); if (surfaces[i].material.is_valid()) { mesh->surface_set_material(mesh->get_surface_count() - 1, surfaces[i].material); } @@ -391,7 +406,7 @@ void EditorSceneImporterMesh::create_shadow_mesh() { } } - shadow_mesh->add_surface(surfaces[i].primitive, new_surface, Array(), lods, Ref<Material>(), surfaces[i].name); + shadow_mesh->add_surface(surfaces[i].primitive, new_surface, Array(), lods, Ref<Material>(), surfaces[i].name, surfaces[i].flags); } } @@ -429,7 +444,11 @@ void EditorSceneImporterMesh::_set_data(const Dictionary &p_data) { if (s.has("material")) { material = s["material"]; } - add_surface(prim, arr, blend_shapes, lods, material, name); + uint32_t flags = 0; + if (s.has("flags")) { + flags = s["flags"]; + } + add_surface(prim, arr, blend_shapes, lods, material, name, flags); } } } @@ -466,6 +485,10 @@ Dictionary EditorSceneImporterMesh::_get_data() const { d["name"] = surfaces[i].name; } + if (surfaces[i].flags != 0) { + d["flags"] = surfaces[i].flags; + } + surface_arr.push_back(d); } data["surfaces"] = surface_arr; @@ -826,7 +849,7 @@ void EditorSceneImporterMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_blend_shape_mode", "mode"), &EditorSceneImporterMesh::set_blend_shape_mode); ClassDB::bind_method(D_METHOD("get_blend_shape_mode"), &EditorSceneImporterMesh::get_blend_shape_mode); - ClassDB::bind_method(D_METHOD("add_surface", "primitive", "arrays", "blend_shapes", "lods", "material", "name"), &EditorSceneImporterMesh::add_surface, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(Ref<Material>()), DEFVAL(String())); + ClassDB::bind_method(D_METHOD("add_surface", "primitive", "arrays", "blend_shapes", "lods", "material", "name", "flags"), &EditorSceneImporterMesh::add_surface, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(Ref<Material>()), DEFVAL(String()), DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_surface_count"), &EditorSceneImporterMesh::get_surface_count); ClassDB::bind_method(D_METHOD("get_surface_primitive_type", "surface_idx"), &EditorSceneImporterMesh::get_surface_primitive_type); @@ -837,8 +860,12 @@ void EditorSceneImporterMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_surface_lod_size", "surface_idx", "lod_idx"), &EditorSceneImporterMesh::get_surface_lod_size); ClassDB::bind_method(D_METHOD("get_surface_lod_indices", "surface_idx", "lod_idx"), &EditorSceneImporterMesh::get_surface_lod_indices); ClassDB::bind_method(D_METHOD("get_surface_material", "surface_idx"), &EditorSceneImporterMesh::get_surface_material); + ClassDB::bind_method(D_METHOD("get_surface_format", "surface_idx"), &EditorSceneImporterMesh::get_surface_format); + + ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &EditorSceneImporterMesh::set_surface_name); + ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &EditorSceneImporterMesh::set_surface_material); - ClassDB::bind_method(D_METHOD("get_mesh"), &EditorSceneImporterMesh::get_mesh); + ClassDB::bind_method(D_METHOD("get_mesh", "base_mesh"), &EditorSceneImporterMesh::get_mesh, DEFVAL(Ref<ArrayMesh>())); ClassDB::bind_method(D_METHOD("clear"), &EditorSceneImporterMesh::clear); ClassDB::bind_method(D_METHOD("_set_data", "data"), &EditorSceneImporterMesh::_set_data); diff --git a/editor/import/scene_importer_mesh.h b/editor/import/scene_importer_mesh.h index 2488de7ed0..c8e25244fa 100644 --- a/editor/import/scene_importer_mesh.h +++ b/editor/import/scene_importer_mesh.h @@ -36,6 +36,9 @@ #include "scene/resources/convex_polygon_shape_3d.h" #include "scene/resources/mesh.h" #include "scene/resources/navigation_mesh.h" + +#include <cstdint> + // The following classes are used by importers instead of ArrayMesh and MeshInstance3D // so the data is not registered (hence, quality loss), importing happens faster and // its easier to modify before saving @@ -57,6 +60,7 @@ class EditorSceneImporterMesh : public Resource { Vector<LOD> lods; Ref<Material> material; String name; + uint32_t flags = 0; }; Vector<Surface> surfaces; Vector<String> blend_shapes; @@ -80,7 +84,7 @@ public: int get_blend_shape_count() const; String get_blend_shape_name(int p_blend_shape) const; - void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref<Material> &p_material = Ref<Material>(), const String &p_name = String()); + void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref<Material> &p_material = Ref<Material>(), const String &p_name = String(), const uint32_t p_flags = 0); int get_surface_count() const; void set_blend_shape_mode(Mesh::BlendShapeMode p_blend_shape_mode); @@ -88,12 +92,14 @@ public: Mesh::PrimitiveType get_surface_primitive_type(int p_surface); String get_surface_name(int p_surface) const; + void set_surface_name(int p_surface, const String &p_name); Array get_surface_arrays(int p_surface) const; Array get_surface_blend_shape_arrays(int p_surface, int p_blend_shape) const; int get_surface_lod_count(int p_surface) const; Vector<int> get_surface_lod_indices(int p_surface, int p_lod) const; float get_surface_lod_size(int p_surface, int p_lod) const; Ref<Material> get_surface_material(int p_surface) const; + uint32_t get_surface_format(int p_surface) const; void set_surface_material(int p_surface, const Ref<Material> &p_material); @@ -112,7 +118,7 @@ public: Size2i get_lightmap_size_hint() const; bool has_mesh() const; - Ref<ArrayMesh> get_mesh(const Ref<Mesh> &p_base = Ref<Mesh>()); + Ref<ArrayMesh> get_mesh(const Ref<ArrayMesh> &p_base = Ref<ArrayMesh>()); void clear(); }; #endif // EDITOR_SCENE_IMPORTER_MESH_H diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp index ef3b0588b8..36a814c30a 100644 --- a/editor/plugins/abstract_polygon_2d_editor.cpp +++ b/editor/plugins/abstract_polygon_2d_editor.cpp @@ -367,7 +367,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) edge_point = PosVertex(); return true; } else { - const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); if (!_is_line() && wip.size() > 1 && xform.xform(wip[0]).distance_to(xform.xform(cpoint)) < grab_threshold) { //wip closed @@ -502,7 +502,7 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl offset = _get_offset(j); } - if (!wip_active && j == edited_point.polygon && EDITOR_GET("editors/poly_editor/show_previous_outline")) { + if (!wip_active && j == edited_point.polygon && EDITOR_GET("editors/polygon_editor/show_previous_outline")) { const Color col = Color(0.5, 0.5, 0.5); // FIXME polygon->get_outline_color(); const int n = pre_move_edit.size(); for (int i = 0; i < n - (is_closed ? 0 : 1); i++) { @@ -625,7 +625,7 @@ AbstractPolygon2DEditor::Vertex AbstractPolygon2DEditor::get_active_point() cons } AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_point(const Vector2 &p_pos) const { - const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); const int n_polygons = _get_polygon_count(); const Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform(); @@ -653,7 +653,7 @@ AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_point(const } AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(const Vector2 &p_pos) const { - const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); const real_t eps = grab_threshold * 2; const real_t eps2 = eps * eps; diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index b4e9f468de..830b010d01 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -345,7 +345,7 @@ void AnimationPlayerEditor::_animation_rename() { void AnimationPlayerEditor::_animation_load() { ERR_FAIL_COND(!player); - file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES); file->clear_filters(); List<String> extensions; @@ -355,7 +355,6 @@ void AnimationPlayerEditor::_animation_load() { } file->popup_file_dialog(); - current_option = RESOURCE_LOAD; } void AnimationPlayerEditor::_animation_save_in_path(const Ref<Resource> &p_resource, const String &p_path) { @@ -416,7 +415,6 @@ void AnimationPlayerEditor::_animation_save_as(const Ref<Resource> &p_resource) file->set_current_path(path); file->set_title(TTR("Save Resource As...")); file->popup_file_dialog(); - current_option = RESOURCE_SAVE; } void AnimationPlayerEditor::_animation_remove() { @@ -718,44 +716,48 @@ void AnimationPlayerEditor::_animation_edit() { } } -void AnimationPlayerEditor::_dialog_action(String p_path) { - switch (current_option) { - case RESOURCE_LOAD: { - ERR_FAIL_COND(!player); +void AnimationPlayerEditor::_save_animation(String p_file) { + String current = animation->get_item_text(animation->get_selected()); + if (current != "") { + Ref<Animation> anim = player->get_animation(current); - Ref<Resource> res = ResourceLoader::load(p_path, "Animation"); - ERR_FAIL_COND_MSG(res.is_null(), "Cannot load Animation from file '" + p_path + "'."); - ERR_FAIL_COND_MSG(!res->is_class("Animation"), "Loaded resource from file '" + p_path + "' is not Animation."); + ERR_FAIL_COND(!Object::cast_to<Resource>(*anim)); - String anim_name = p_path.get_file(); - int ext_pos = anim_name.rfind("."); - if (ext_pos != -1) { - anim_name = anim_name.substr(0, ext_pos); - } + RES current_res = RES(Object::cast_to<Resource>(*anim)); - undo_redo->create_action(TTR("Load Animation")); - undo_redo->add_do_method(player, "add_animation", anim_name, res); - undo_redo->add_undo_method(player, "remove_animation", anim_name); - if (player->has_animation(anim_name)) { - undo_redo->add_undo_method(player, "add_animation", anim_name, player->get_animation(anim_name)); - } - undo_redo->add_do_method(this, "_animation_player_changed", player); - undo_redo->add_undo_method(this, "_animation_player_changed", player); - undo_redo->commit_action(); - break; - } - case RESOURCE_SAVE: { - String current = animation->get_item_text(animation->get_selected()); - if (current != "") { - Ref<Animation> anim = player->get_animation(current); + _animation_save_in_path(current_res, p_file); + } +} - ERR_FAIL_COND(!Object::cast_to<Resource>(*anim)); +void AnimationPlayerEditor::_load_animations(Vector<String> p_files) { + ERR_FAIL_COND(!player); - RES current_res = RES(Object::cast_to<Resource>(*anim)); + for (int i = 0; i < p_files.size(); i++) { + String file = p_files[i]; - _animation_save_in_path(current_res, p_path); - } + Ref<Resource> res = ResourceLoader::load(file, "Animation"); + ERR_FAIL_COND_MSG(res.is_null(), "Cannot load Animation from file '" + file + "'."); + ERR_FAIL_COND_MSG(!res->is_class("Animation"), "Loaded resource from file '" + file + "' is not Animation."); + if (file.rfind("/") != -1) { + file = file.substr(file.rfind("/") + 1, file.length()); + } + if (file.rfind("\\") != -1) { + file = file.substr(file.rfind("\\") + 1, file.length()); } + + if (file.find(".") != -1) { + file = file.substr(0, file.find(".")); + } + + undo_redo->create_action(TTR("Load Animation")); + undo_redo->add_do_method(player, "add_animation", file, res); + undo_redo->add_undo_method(player, "remove_animation", file); + if (player->has_animation(file)) { + undo_redo->add_undo_method(player, "add_animation", file, player->get_animation(file)); + } + undo_redo->add_do_method(this, "_animation_player_changed", player); + undo_redo->add_undo_method(this, "_animation_player_changed", player); + undo_redo->commit_action(); } } @@ -1220,7 +1222,7 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) { } } -void AnimationPlayerEditor::_unhandled_key_input(const Ref<InputEvent> &p_ev) { +void AnimationPlayerEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventKey> k = p_ev; @@ -1497,7 +1499,6 @@ void AnimationPlayerEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_animation_player_changed"), &AnimationPlayerEditor::_animation_player_changed); ClassDB::bind_method(D_METHOD("_list_changed"), &AnimationPlayerEditor::_list_changed); ClassDB::bind_method(D_METHOD("_animation_duplicate"), &AnimationPlayerEditor::_animation_duplicate); - ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &AnimationPlayerEditor::_unhandled_key_input); ClassDB::bind_method(D_METHOD("_prepare_onion_layers_1"), &AnimationPlayerEditor::_prepare_onion_layers_1); ClassDB::bind_method(D_METHOD("_prepare_onion_layers_2"), &AnimationPlayerEditor::_prepare_onion_layers_2); @@ -1696,7 +1697,8 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay animation->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_animation_selected)); - file->connect("file_selected", callable_mp(this, &AnimationPlayerEditor::_dialog_action)); + file->connect("file_selected", callable_mp(this, &AnimationPlayerEditor::_save_animation)); + file->connect("files_selected", callable_mp(this, &AnimationPlayerEditor::_load_animations)); frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed), make_binds(true, false)); scale->connect("text_submitted", callable_mp(this, &AnimationPlayerEditor::_scale_changed)); @@ -1736,6 +1738,8 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay onion.capture.shader = Ref<Shader>(memnew(Shader)); onion.capture.shader->set_code(R"( +// Animation editor onion skinning shader. + shader_type canvas_item; uniform vec4 bkg_color; diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 5c2348f86b..be80b7f4e3 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -112,7 +112,6 @@ class AnimationPlayerEditor : public VBoxContainer { EditorFileDialog *file; ConfirmationDialog *delete_dialog; - int current_option; struct BlendEditor { AcceptDialog *dialog = nullptr; @@ -185,7 +184,8 @@ class AnimationPlayerEditor : public VBoxContainer { void _animation_duplicate(); void _animation_resource_edit(); void _scale_changed(const String &p_scale); - void _dialog_action(String p_file); + void _save_animation(String p_file); + void _load_animations(Vector<String> p_files); void _seek_frame_changed(const String &p_frame); void _seek_value_changed(float p_value, bool p_set = false, bool p_timeline_only = false); void _blend_editor_next_changed(const int p_idx); @@ -200,7 +200,7 @@ class AnimationPlayerEditor : public VBoxContainer { void _animation_key_editor_seek(float p_pos, bool p_drag, bool p_timeline_only = false); void _animation_key_editor_anim_len_changed(float p_len); - void _unhandled_key_input(const Ref<InputEvent> &p_ev); + virtual void unhandled_key_input(const Ref<InputEvent> &p_ev) override; void _animation_tool_menu(int p_option); void _onion_skinning_menu(int p_option); diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index 785bab42cf..5405723d10 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -614,7 +614,7 @@ void EditorAssetLibrary::_update_repository_options() { } } -void EditorAssetLibrary::_unhandled_key_input(const Ref<InputEvent> &p_event) { +void EditorAssetLibrary::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); const Ref<InputEventKey> key = p_event; @@ -1322,8 +1322,6 @@ void EditorAssetLibrary::disable_community_support() { } void EditorAssetLibrary::_bind_methods() { - ClassDB::bind_method("_unhandled_key_input", &EditorAssetLibrary::_unhandled_key_input); - ADD_SIGNAL(MethodInfo("install_asset", PropertyInfo(Variant::STRING, "zip_path"), PropertyInfo(Variant::STRING, "name"))); } diff --git a/editor/plugins/asset_library_editor_plugin.h b/editor/plugins/asset_library_editor_plugin.h index c6ca1ecd4f..286546f962 100644 --- a/editor/plugins/asset_library_editor_plugin.h +++ b/editor/plugins/asset_library_editor_plugin.h @@ -299,7 +299,7 @@ class EditorAssetLibrary : public PanelContainer { protected: static void _bind_methods(); void _notification(int p_what); - void _unhandled_key_input(const Ref<InputEvent> &p_event); + virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; public: void disable_community_support(); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 477e066e87..d96cc1cd18 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -472,7 +472,7 @@ real_t CanvasItemEditor::snap_angle(real_t p_target, real_t p_start) const { } } -void CanvasItemEditor::_unhandled_key_input(const Ref<InputEvent> &p_ev) { +void CanvasItemEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventKey> k = p_ev; @@ -590,7 +590,7 @@ void CanvasItemEditor::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_no return; } - const real_t grab_distance = EDITOR_GET("editors/poly_editor/point_grab_radius"); + const real_t grab_distance = EDITOR_GET("editors/polygon_editor/point_grab_radius"); CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); for (int i = p_node->get_child_count() - 1; i >= 0; i--) { @@ -4195,6 +4195,7 @@ void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) { p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM); if (p_zoom == zoom) { + zoom_widget->set_zoom(p_zoom); return; } @@ -4918,7 +4919,7 @@ void CanvasItemEditor::_focus_selection(int p_op) { void CanvasItemEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_override_camera_button", "game_running"), &CanvasItemEditor::_update_override_camera_button); ClassDB::bind_method("_get_editor_data", &CanvasItemEditor::_get_editor_data); - ClassDB::bind_method("_unhandled_key_input", &CanvasItemEditor::_unhandled_key_input); + ClassDB::bind_method(D_METHOD("set_state"), &CanvasItemEditor::set_state); ClassDB::bind_method(D_METHOD("update_viewport"), &CanvasItemEditor::update_viewport); ClassDB::bind_method(D_METHOD("_zoom_on_position"), &CanvasItemEditor::_zoom_on_position); diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index bff580315e..1965efbf30 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -455,7 +455,7 @@ private: void _keying_changed(); - void _unhandled_key_input(const Ref<InputEvent> &p_ev); + virtual void unhandled_key_input(const Ref<InputEvent> &p_ev) override; void _draw_text_at_position(Point2 p_position, String p_string, Side p_side); void _draw_margin_at_position(int p_value, Point2 p_position, Side p_side); diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.cpp b/editor/plugins/collision_polygon_3d_editor_plugin.cpp index 5d5f78e0dc..8b354c33a1 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.cpp +++ b/editor/plugins/collision_polygon_3d_editor_plugin.cpp @@ -138,7 +138,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Vector<Vector2> poly = node->call("get_polygon"); //first check if a point is to be added (segment split) - real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); switch (mode) { case MODE_CREATE: { diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp index c2684305ef..bfcc293625 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -38,6 +38,7 @@ #include "scene/resources/convex_polygon_shape_2d.h" #include "scene/resources/rectangle_shape_2d.h" #include "scene/resources/segment_shape_2d.h" +#include "scene/resources/separation_ray_shape_2d.h" #include "scene/resources/world_margin_shape_2d.h" void CollisionShape2DEditor::_node_removed(Node *p_node) { @@ -80,6 +81,15 @@ Variant CollisionShape2DEditor::get_handle_value(int idx) const { } break; + case SEPARATION_RAY_SHAPE: { + Ref<SeparationRayShape2D> ray = node->get_shape(); + + if (idx == 0) { + return ray->get_length(); + } + + } break; + case RECTANGLE_SHAPE: { Ref<RectangleShape2D> rect = node->get_shape(); @@ -152,6 +162,15 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) { } break; + case SEPARATION_RAY_SHAPE: { + Ref<SeparationRayShape2D> ray = node->get_shape(); + + ray->set_length(Math::abs(p_point.y)); + + canvas_item_editor->update_viewport(); + + } break; + case RECTANGLE_SHAPE: { if (idx < 8) { Ref<RectangleShape2D> rect = node->get_shape(); @@ -253,6 +272,16 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) { } break; + case SEPARATION_RAY_SHAPE: { + Ref<SeparationRayShape2D> ray = node->get_shape(); + + undo_redo->add_do_method(ray.ptr(), "set_length", ray->get_length()); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_method(ray.ptr(), "set_length", p_org); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + + } break; + case RECTANGLE_SHAPE: { Ref<RectangleShape2D> rect = node->get_shape(); @@ -394,6 +423,8 @@ void CollisionShape2DEditor::_get_current_shape_type() { shape_type = CONVEX_POLYGON_SHAPE; } else if (Object::cast_to<WorldMarginShape2D>(*s)) { shape_type = WORLD_MARGIN_SHAPE; + } else if (Object::cast_to<SeparationRayShape2D>(*s)) { + shape_type = SEPARATION_RAY_SHAPE; } else if (Object::cast_to<RectangleShape2D>(*s)) { shape_type = RECTANGLE_SHAPE; } else if (Object::cast_to<SegmentShape2D>(*s)) { @@ -471,6 +502,16 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla } break; + case SEPARATION_RAY_SHAPE: { + Ref<SeparationRayShape2D> shape = node->get_shape(); + + handles.resize(1); + handles.write[0] = Point2(0, shape->get_length()); + + p_overlay->draw_texture(h, gt.xform(handles[0]) - size); + + } break; + case RECTANGLE_SHAPE: { Ref<RectangleShape2D> shape = node->get_shape(); diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h index 056e1b5b7d..421e674df8 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.h +++ b/editor/plugins/collision_shape_2d_editor_plugin.h @@ -47,6 +47,7 @@ class CollisionShape2DEditor : public Control { CONCAVE_POLYGON_SHAPE, CONVEX_POLYGON_SHAPE, WORLD_MARGIN_SHAPE, + SEPARATION_RAY_SHAPE, RECTANGLE_SHAPE, SEGMENT_SHAPE }; diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index 07ff0eb346..4a22dc5b62 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -101,7 +101,7 @@ void CurveEditor::_notification(int p_what) { } } -void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) { +void CurveEditor::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb_ref = p_event; if (mb_ref.is_valid()) { const InputEventMouseButton &mb = **mb_ref; @@ -757,10 +757,6 @@ void CurveEditor::_draw() { } } -void CurveEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &CurveEditor::on_gui_input); -} - //--------------- bool EditorInspectorPluginCurve::can_handle(Object *p_object) { diff --git a/editor/plugins/curve_editor_plugin.h b/editor/plugins/curve_editor_plugin.h index 2e8dd43d7e..c351f6ebe9 100644 --- a/editor/plugins/curve_editor_plugin.h +++ b/editor/plugins/curve_editor_plugin.h @@ -74,10 +74,8 @@ public: protected: void _notification(int p_what); - static void _bind_methods(); - private: - void on_gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void on_preset_item_selected(int preset_id); void _curve_changed(); void on_context_menu_item_selected(int action_id); diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 95f68d5f7f..415832ab3b 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -826,55 +826,6 @@ bool EditorFontPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "FontData") || ClassDB::is_parent_class(p_type, "Font"); } -struct FSample { - String script; - String sample; -}; - -static FSample _samples[] = { - { "hani", U"漢字" }, - { "armn", U"Աբ" }, - { "copt", U"Αα" }, - { "cyrl", U"Аб" }, - { "grek", U"Αα" }, - { "hebr", U"אב" }, - { "arab", U"اب" }, - { "syrc", U"ܐܒ" }, - { "thaa", U"ހށ" }, - { "deva", U"आ" }, - { "beng", U"আ" }, - { "guru", U"ਆ" }, - { "gujr", U"આ" }, - { "orya", U"ଆ" }, - { "taml", U"ஆ" }, - { "telu", U"ఆ" }, - { "knda", U"ಆ" }, - { "mylm", U"ആ" }, - { "sinh", U"ආ" }, - { "thai", U"กิ" }, - { "laoo", U"ກິ" }, - { "tibt", U"ༀ" }, - { "mymr", U"က" }, - { "geor", U"Ⴀა" }, - { "hang", U"한글" }, - { "ethi", U"ሀ" }, - { "cher", U"Ꭳ" }, - { "cans", U"ᐁ" }, - { "ogam", U"ᚁ" }, - { "runr", U"ᚠ" }, - { "tglg", U"ᜀ" }, - { "hano", U"ᜠ" }, - { "buhd", U"ᝀ" }, - { "tagb", U"ᝠ" }, - { "khmr", U"ក" }, - { "mong", U"ᠠ" }, - { "limb", U"ᤁ" }, - { "tale", U"ᥐ" }, - { "latn", U"Ab" }, - { "zyyy", U"😀" }, - { "", U"" } -}; - Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size) const { RES res = ResourceLoader::load(p_path); Ref<Font> sampled_font; @@ -886,15 +837,15 @@ Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, } String sample; - for (int j = 0; j < sampled_font->get_data_count(); j++) { - for (int i = 0; _samples[i].script != String(); i++) { - if (sampled_font->get_data(j)->is_script_supported(_samples[i].script)) { - if (sampled_font->get_data(j)->has_char(_samples[i].sample[0])) { - sample += _samples[i].sample; - } - } + static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀"; + for (int i = 0; i < sample_base.length(); i++) { + if (sampled_font->has_char(sample_base[i])) { + sample += sample_base[i]; } } + if (sample.is_empty()) { + sample = sampled_font->get_supported_chars().substr(0, 6); + } Vector2 size = sampled_font->get_string_size(sample, 50); Vector2 pos; diff --git a/editor/plugins/font_editor_plugin.cpp b/editor/plugins/font_editor_plugin.cpp index 22c9cc9ab1..52fb5b69ea 100644 --- a/editor/plugins/font_editor_plugin.cpp +++ b/editor/plugins/font_editor_plugin.cpp @@ -50,70 +50,24 @@ Size2 FontDataPreview::get_minimum_size() const { return Vector2(64, 64) * EDSCALE; } -struct FSample { - String script; - String sample; -}; - -static FSample _samples[] = { - { "hani", U"漢字" }, - { "armn", U"Աբ" }, - { "copt", U"Αα" }, - { "cyrl", U"Аб" }, - { "grek", U"Αα" }, - { "hebr", U"אב" }, - { "arab", U"اب" }, - { "syrc", U"ܐܒ" }, - { "thaa", U"ހށ" }, - { "deva", U"आ" }, - { "beng", U"আ" }, - { "guru", U"ਆ" }, - { "gujr", U"આ" }, - { "orya", U"ଆ" }, - { "taml", U"ஆ" }, - { "telu", U"ఆ" }, - { "knda", U"ಆ" }, - { "mylm", U"ആ" }, - { "sinh", U"ආ" }, - { "thai", U"กิ" }, - { "laoo", U"ກິ" }, - { "tibt", U"ༀ" }, - { "mymr", U"က" }, - { "geor", U"Ⴀა" }, - { "hang", U"한글" }, - { "ethi", U"ሀ" }, - { "cher", U"Ꭳ" }, - { "cans", U"ᐁ" }, - { "ogam", U"ᚁ" }, - { "runr", U"ᚠ" }, - { "tglg", U"ᜀ" }, - { "hano", U"ᜠ" }, - { "buhd", U"ᝀ" }, - { "tagb", U"ᝠ" }, - { "khmr", U"ក" }, - { "mong", U"ᠠ" }, - { "limb", U"ᤁ" }, - { "tale", U"ᥐ" }, - { "latn", U"Ab" }, - { "zyyy", U"😀" }, - { "", U"" } -}; - void FontDataPreview::set_data(const Ref<FontData> &p_data) { Ref<Font> f = memnew(Font); f->add_data(p_data); line->clear(); - - String sample; - for (int i = 0; _samples[i].script != String(); i++) { - if (p_data->is_script_supported(_samples[i].script)) { - if (p_data->has_char(_samples[i].sample[0])) { - sample += _samples[i].sample; + if (p_data.is_valid()) { + String sample; + static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀"; + for (int i = 0; i < sample_base.length(); i++) { + if (p_data->has_char(sample_base[i])) { + sample += sample_base[i]; } } + if (sample.is_empty()) { + sample = p_data->get_supported_chars().substr(0, 6); + } + line->add_string(sample, f, 72); } - line->add_string(sample, f, 72); update(); } @@ -124,159 +78,6 @@ FontDataPreview::FontDataPreview() { /*************************************************************************/ -void FontDataEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_SORT_CHILDREN) { - int split_width = get_name_split_ratio() * get_size().width; - button->set_size(Size2(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))->get_width(), get_size().height)); - if (is_layout_rtl()) { - if (le != nullptr) { - fit_child_in_rect(le, Rect2(Vector2(split_width, 0), Size2(split_width, get_size().height))); - } - fit_child_in_rect(chk, Rect2(Vector2(split_width - chk->get_size().x, 0), Size2(chk->get_size().x, get_size().height))); - fit_child_in_rect(button, Rect2(Vector2(0, 0), Size2(button->get_size().width, get_size().height))); - } else { - if (le != nullptr) { - fit_child_in_rect(le, Rect2(Vector2(0, 0), Size2(split_width, get_size().height))); - } - fit_child_in_rect(chk, Rect2(Vector2(split_width, 0), Size2(chk->get_size().x, get_size().height))); - fit_child_in_rect(button, Rect2(Vector2(get_size().width - button->get_size().width, 0), Size2(button->get_size().width, get_size().height))); - } - update(); - } - if (p_what == NOTIFICATION_DRAW) { - int split_width = get_name_split_ratio() * get_size().width; - Color dark_color = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); - if (is_layout_rtl()) { - draw_rect(Rect2(Vector2(0, 0), Size2(split_width, get_size().height)), dark_color); - } else { - draw_rect(Rect2(Vector2(split_width, 0), Size2(split_width, get_size().height)), dark_color); - } - } - if (p_what == NOTIFICATION_THEME_CHANGED) { - if (le != nullptr) { - button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - } else { - button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - } - queue_sort(); - } - if (p_what == NOTIFICATION_RESIZED) { - queue_sort(); - } -} - -void FontDataEditor::update_property() { - if (le == nullptr) { - bool c = get_edited_object()->get(get_edited_property()); - chk->set_pressed(c); - chk->set_disabled(is_read_only()); - } -} - -Size2 FontDataEditor::get_minimum_size() const { - return Size2(0, 60); -} - -void FontDataEditor::_bind_methods() { -} - -void FontDataEditor::init_lang_add() { - le = memnew(LineEdit); - le->set_placeholder("Language code"); - le->set_custom_minimum_size(Size2(get_size().width / 2, 0)); - le->set_editable(true); - add_child(le); - - button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - button->connect("pressed", callable_mp(this, &FontDataEditor::add_lang)); -} - -void FontDataEditor::init_lang_edit() { - button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - button->connect("pressed", callable_mp(this, &FontDataEditor::remove_lang)); - chk->connect("toggled", callable_mp(this, &FontDataEditor::toggle_lang)); -} - -void FontDataEditor::init_script_add() { - le = memnew(LineEdit); - le->set_placeholder("Script code"); - le->set_custom_minimum_size(Size2(get_size().width / 2, 0)); - le->set_editable(true); - add_child(le); - - button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - button->connect("pressed", callable_mp(this, &FontDataEditor::add_script)); -} - -void FontDataEditor::init_script_edit() { - button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - button->connect("pressed", callable_mp(this, &FontDataEditor::remove_script)); - chk->connect("toggled", callable_mp(this, &FontDataEditor::toggle_script)); -} - -void FontDataEditor::add_lang() { - FontData *fd = Object::cast_to<FontData>(get_edited_object()); - if (fd != nullptr && !le->get_text().is_empty()) { - fd->set_language_support_override(le->get_text(), chk->is_pressed()); - le->set_text(""); - chk->set_pressed(false); - } -} - -void FontDataEditor::add_script() { - FontData *fd = Object::cast_to<FontData>(get_edited_object()); - if (fd != nullptr && le->get_text().length() == 4) { - fd->set_script_support_override(le->get_text(), chk->is_pressed()); - le->set_text(""); - chk->set_pressed(false); - } -} - -void FontDataEditor::toggle_lang(bool p_pressed) { - FontData *fd = Object::cast_to<FontData>(get_edited_object()); - if (fd != nullptr) { - String lang = String(get_edited_property()).replace("language_support_override/", ""); - fd->set_language_support_override(lang, p_pressed); - } -} - -void FontDataEditor::toggle_script(bool p_pressed) { - FontData *fd = Object::cast_to<FontData>(get_edited_object()); - if (fd != nullptr) { - String script = String(get_edited_property()).replace("script_support_override/", ""); - fd->set_script_support_override(script, p_pressed); - } -} - -void FontDataEditor::remove_lang() { - FontData *fd = Object::cast_to<FontData>(get_edited_object()); - if (fd != nullptr) { - String lang = String(get_edited_property()).replace("language_support_override/", ""); - fd->remove_language_support_override(lang); - } -} - -void FontDataEditor::remove_script() { - FontData *fd = Object::cast_to<FontData>(get_edited_object()); - if (fd != nullptr) { - String script = String(get_edited_property()).replace("script_support_override/", ""); - fd->remove_script_support_override(script); - } -} - -FontDataEditor::FontDataEditor() { - chk = memnew(CheckBox); - chk->set_text(TTR("On")); - chk->set_flat(true); - add_child(chk); - - button = memnew(Button); - button->set_flat(true); - add_child(button); -} - -/*************************************************************************/ - bool EditorInspectorPluginFont::can_handle(Object *p_object) { return Object::cast_to<FontData>(p_object) != nullptr; } @@ -291,34 +92,6 @@ void EditorInspectorPluginFont::parse_begin(Object *p_object) { } bool EditorInspectorPluginFont::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { - if (p_path.begins_with("language_support_override/") && p_object->is_class("FontData")) { - String lang = p_path.replace("language_support_override/", ""); - - FontDataEditor *editor = memnew(FontDataEditor); - if (lang != "_new") { - editor->init_lang_edit(); - } else { - editor->init_lang_add(); - } - add_property_editor(p_path, editor); - - return true; - } - - if (p_path.begins_with("script_support_override/") && p_object->is_class("FontData")) { - String script = p_path.replace("script_support_override/", ""); - - FontDataEditor *editor = memnew(FontDataEditor); - if (script != "_new") { - editor->init_script_edit(); - } else { - editor->init_script_add(); - } - add_property_editor(p_path, editor); - - return true; - } - return false; } diff --git a/editor/plugins/font_editor_plugin.h b/editor/plugins/font_editor_plugin.h index 71464003a0..3530815872 100644 --- a/editor/plugins/font_editor_plugin.h +++ b/editor/plugins/font_editor_plugin.h @@ -55,39 +55,6 @@ public: /*************************************************************************/ -class FontDataEditor : public EditorProperty { - GDCLASS(FontDataEditor, EditorProperty); - - LineEdit *le = nullptr; - CheckBox *chk = nullptr; - Button *button = nullptr; - - void toggle_lang(bool p_pressed); - void toggle_script(bool p_pressed); - void add_lang(); - void add_script(); - void remove_lang(); - void remove_script(); - -protected: - void _notification(int p_what); - - static void _bind_methods(); - -public: - virtual Size2 get_minimum_size() const override; - virtual void update_property() override; - - void init_lang_add(); - void init_lang_edit(); - void init_script_add(); - void init_script_edit(); - - FontDataEditor(); -}; - -/*************************************************************************/ - class EditorInspectorPluginFont : public EditorInspectorPlugin { GDCLASS(EditorInspectorPluginFont, EditorInspectorPlugin); diff --git a/editor/plugins/mesh_editor_plugin.cpp b/editor/plugins/mesh_editor_plugin.cpp index 39ab3215ff..768f29e15a 100644 --- a/editor/plugins/mesh_editor_plugin.cpp +++ b/editor/plugins/mesh_editor_plugin.cpp @@ -32,7 +32,7 @@ #include "editor/editor_scale.h" -void MeshEditor::_gui_input(Ref<InputEvent> p_event) { +void MeshEditor::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> mm = p_event; @@ -103,10 +103,6 @@ void MeshEditor::_button_pressed(Node *p_button) { } } -void MeshEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &MeshEditor::_gui_input); -} - MeshEditor::MeshEditor() { viewport = memnew(SubViewport); Ref<World3D> world_3d; diff --git a/editor/plugins/mesh_editor_plugin.h b/editor/plugins/mesh_editor_plugin.h index 455fcb5fe9..1e88b70202 100644 --- a/editor/plugins/mesh_editor_plugin.h +++ b/editor/plugins/mesh_editor_plugin.h @@ -64,8 +64,7 @@ class MeshEditor : public SubViewportContainer { protected: void _notification(int p_what); - void _gui_input(Ref<InputEvent> p_event); - static void _bind_methods(); + void gui_input(const Ref<InputEvent> &p_event) override; public: void edit(Ref<Mesh> p_mesh); diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp index b3f92c9d95..18e7480287 100644 --- a/editor/plugins/mesh_library_editor_plugin.cpp +++ b/editor/plugins/mesh_library_editor_plugin.cpp @@ -47,23 +47,25 @@ void MeshLibraryEditor::edit(const Ref<MeshLibrary> &p_mesh_library) { } } -void MeshLibraryEditor::_menu_confirm() { +void MeshLibraryEditor::_menu_remove_confirm() { switch (option) { case MENU_OPTION_REMOVE_ITEM: { mesh_library->remove_item(to_erase); } break; - case MENU_OPTION_UPDATE_FROM_SCENE: { - String existing = mesh_library->get_meta("_editor_source_scene"); - ERR_FAIL_COND(existing == ""); - _import_scene_cbk(existing); - - } break; default: { }; } } -void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge) { +void MeshLibraryEditor::_menu_update_confirm(bool p_apply_xforms) { + cd_update->hide(); + apply_xforms = p_apply_xforms; + String existing = mesh_library->get_meta("_editor_source_scene"); + ERR_FAIL_COND(existing == ""); + _import_scene_cbk(existing); +} + +void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge, bool p_apply_xforms) { if (!p_merge) { p_library->clear(); } @@ -108,6 +110,13 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, } p_library->set_item_mesh(id, mesh); + + if (p_apply_xforms) { + p_library->set_item_mesh_transform(id, mi->get_transform()); + } else { + p_library->set_item_mesh_transform(id, Transform3D()); + } + mesh_instances[id] = mi; Vector<MeshLibrary::ShapeData> collisions; @@ -197,15 +206,16 @@ void MeshLibraryEditor::_import_scene_cbk(const String &p_str) { ERR_FAIL_COND_MSG(!scene, "Cannot create an instance from PackedScene '" + p_str + "'."); - _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE); + _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE, apply_xforms); memdelete(scene); mesh_library->set_meta("_editor_source_scene", p_str); + menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), false); } -Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge) { - _import_scene(p_base_scene, ml, p_merge); +Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge, bool p_apply_xforms) { + _import_scene(p_base_scene, ml, p_merge, p_apply_xforms); return OK; } @@ -219,16 +229,21 @@ void MeshLibraryEditor::_menu_cbk(int p_option) { String p = editor->get_inspector()->get_selected_path(); if (p.begins_with("/MeshLibrary/item") && p.get_slice_count("/") >= 3) { to_erase = p.get_slice("/", 3).to_int(); - cd->set_text(vformat(TTR("Remove item %d?"), to_erase)); - cd->popup_centered(Size2(300, 60)); + cd_remove->set_text(vformat(TTR("Remove item %d?"), to_erase)); + cd_remove->popup_centered(Size2(300, 60)); } } break; case MENU_OPTION_IMPORT_FROM_SCENE: { + apply_xforms = false; + file->popup_file_dialog(); + } break; + case MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS: { + apply_xforms = true; file->popup_file_dialog(); } break; case MENU_OPTION_UPDATE_FROM_SCENE: { - cd->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene")))); - cd->popup_centered(Size2(500, 60)); + cd_update->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene")))); + cd_update->popup_centered(Size2(500, 60)); } break; } } @@ -258,16 +273,22 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { menu->get_popup()->add_item(TTR("Add Item"), MENU_OPTION_ADD_ITEM); menu->get_popup()->add_item(TTR("Remove Selected Item"), MENU_OPTION_REMOVE_ITEM); menu->get_popup()->add_separator(); - menu->get_popup()->add_item(TTR("Import from Scene"), MENU_OPTION_IMPORT_FROM_SCENE); + menu->get_popup()->add_item(TTR("Import from Scene (Ignore Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE); + menu->get_popup()->add_item(TTR("Import from Scene (Apply Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS); menu->get_popup()->add_item(TTR("Update from Scene"), MENU_OPTION_UPDATE_FROM_SCENE); menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), true); menu->get_popup()->connect("id_pressed", callable_mp(this, &MeshLibraryEditor::_menu_cbk)); menu->hide(); editor = p_editor; - cd = memnew(ConfirmationDialog); - add_child(cd); - cd->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_confirm)); + cd_remove = memnew(ConfirmationDialog); + add_child(cd_remove); + cd_remove->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_remove_confirm)); + cd_update = memnew(ConfirmationDialog); + add_child(cd_update); + cd_update->get_ok_button()->set_text("Apply without Transforms"); + cd_update->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(false)); + cd_update->add_button("Apply with Transforms")->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(true)); } void MeshLibraryEditorPlugin::edit(Object *p_node) { diff --git a/editor/plugins/mesh_library_editor_plugin.h b/editor/plugins/mesh_library_editor_plugin.h index 6c33c8bb9e..9e225ffb9b 100644 --- a/editor/plugins/mesh_library_editor_plugin.h +++ b/editor/plugins/mesh_library_editor_plugin.h @@ -41,23 +41,27 @@ class MeshLibraryEditor : public Control { EditorNode *editor; MenuButton *menu; - ConfirmationDialog *cd; + ConfirmationDialog *cd_remove; + ConfirmationDialog *cd_update; EditorFileDialog *file; + bool apply_xforms; int to_erase; enum { MENU_OPTION_ADD_ITEM, MENU_OPTION_REMOVE_ITEM, MENU_OPTION_UPDATE_FROM_SCENE, - MENU_OPTION_IMPORT_FROM_SCENE + MENU_OPTION_IMPORT_FROM_SCENE, + MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS }; int option; void _import_scene_cbk(const String &p_str); void _menu_cbk(int p_option); - void _menu_confirm(); + void _menu_remove_confirm(); + void _menu_update_confirm(bool p_apply_xforms); - static void _import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge); + static void _import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge, bool p_apply_xforms); protected: static void _bind_methods(); @@ -66,7 +70,7 @@ public: MenuButton *get_menu_button() const { return menu; } void edit(const Ref<MeshLibrary> &p_mesh_library); - static Error update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge = true); + static Error update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge = true, bool p_apply_xforms = false); MeshLibraryEditor(EditorNode *p_editor); }; diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 5d1b4d8ead..d20f3d105b 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -66,6 +66,7 @@ #include "scene/resources/cylinder_shape_3d.h" #include "scene/resources/height_map_shape_3d.h" #include "scene/resources/primitive_meshes.h" +#include "scene/resources/separation_ray_shape_3d.h" #include "scene/resources/sphere_shape_3d.h" #include "scene/resources/surface_tool.h" #include "scene/resources/world_margin_shape_3d.h" @@ -2096,7 +2097,6 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Color bonecolor = Color(1.0, 0.4, 0.4, 0.3); Color rootcolor = Color(0.4, 1.0, 0.4, 0.1); - //LocalVector<int> bones_to_process = skel->get_parentless_bones(); LocalVector<int> bones_to_process; bones_to_process = skel->get_parentless_bones(); @@ -2108,19 +2108,18 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { child_bones_vector = skel->get_bone_children(current_bone_idx); int child_bones_size = child_bones_vector.size(); - // You have children but no parent, then you must be a root/parentless bone. - if (child_bones_size >= 0 && skel->get_bone_parent(current_bone_idx) <= 0) { - grests[current_bone_idx] = skel->global_pose_to_local_pose(current_bone_idx, skel->get_bone_global_pose(current_bone_idx)); + if (skel->get_bone_parent(current_bone_idx) < 0) { + grests[current_bone_idx] = skel->get_bone_rest(current_bone_idx); } for (int i = 0; i < child_bones_size; i++) { int child_bone_idx = child_bones_vector[i]; - grests[child_bone_idx] = skel->global_pose_to_local_pose(child_bone_idx, skel->get_bone_global_pose(child_bone_idx)); + grests[child_bone_idx] = grests[current_bone_idx] * skel->get_bone_rest(child_bone_idx); Vector3 v0 = grests[current_bone_idx].origin; Vector3 v1 = grests[child_bone_idx].origin; - Vector3 d = skel->get_bone_rest(child_bone_idx).origin.normalized(); - real_t dist = skel->get_bone_rest(child_bone_idx).origin.length(); + Vector3 d = (v1 - v0).normalized(); + real_t dist = v0.distance_to(v1); // Find closest axis. int closest = -1; @@ -4067,6 +4066,10 @@ String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_g return p_id == 0 ? "Radius" : "Height"; } + if (Object::cast_to<SeparationRayShape3D>(*s)) { + return "Length"; + } + return ""; } @@ -4098,6 +4101,11 @@ Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p return p_id == 0 ? cs2->get_radius() : cs2->get_height(); } + if (Object::cast_to<SeparationRayShape3D>(*s)) { + Ref<SeparationRayShape3D> cs2 = s; + return cs2->get_length(); + } + return Variant(); } @@ -4133,6 +4141,22 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i ss->set_radius(d); } + if (Object::cast_to<SeparationRayShape3D>(*s)) { + Ref<SeparationRayShape3D> rs = s; + Vector3 ra, rb; + Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, 4096), sg[0], sg[1], ra, rb); + float d = ra.z; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + + if (d < 0.001) { + d = 0.001; + } + + rs->set_length(d); + } + if (Object::cast_to<BoxShape3D>(*s)) { Vector3 axis; axis[p_id] = 1.0; @@ -4287,6 +4311,20 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo ur->commit_action(); } + + if (Object::cast_to<SeparationRayShape3D>(*s)) { + Ref<SeparationRayShape3D> ss = s; + if (p_cancel) { + ss->set_length(p_restore); + return; + } + + UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Change Separation Ray Shape Length")); + ur->add_do_method(ss.ptr(), "set_length", ss->get_length()); + ur->add_undo_method(ss.ptr(), "set_length", p_restore); + ur->commit_action(); + } } void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { @@ -4557,6 +4595,19 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_segments(cs2->get_debug_mesh_lines()); } + if (Object::cast_to<SeparationRayShape3D>(*s)) { + Ref<SeparationRayShape3D> rs = s; + + Vector<Vector3> points; + points.push_back(Vector3()); + points.push_back(Vector3(0, 0, rs->get_length())); + p_gizmo->add_lines(points, material); + p_gizmo->add_collision_segments(points); + Vector<Vector3> handles; + handles.push_back(Vector3(0, 0, rs->get_length())); + p_gizmo->add_handles(handles, handles_material); + } + if (Object::cast_to<HeightMapShape3D>(*s)) { Ref<HeightMapShape3D> hms = s; diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 4300a56ef0..291cafab2b 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -181,7 +181,7 @@ void ViewportRotationControl::_get_sorted_axis(Vector<Axis2D> &r_axis) { r_axis.sort_custom<Axis2DCompare>(); } -void ViewportRotationControl::_gui_input(Ref<InputEvent> p_event) { +void ViewportRotationControl::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); const Ref<InputEventMouseButton> mb = p_event; @@ -252,10 +252,6 @@ void ViewportRotationControl::set_viewport(Node3DEditorViewport *p_viewport) { viewport = p_viewport; } -void ViewportRotationControl::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &ViewportRotationControl::_gui_input); -} - void Node3DEditorViewport::_update_camera(real_t p_interp_delta) { bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL; @@ -4428,7 +4424,7 @@ Node3DEditorViewport::~Node3DEditorViewport() { ////////////////////////////////////////////////////////////// -void Node3DEditorViewportContainer::_gui_input(const Ref<InputEvent> &p_event) { +void Node3DEditorViewportContainer::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseButton> mb = p_event; @@ -4720,10 +4716,6 @@ Node3DEditorViewportContainer::View Node3DEditorViewportContainer::get_view() { return view; } -void Node3DEditorViewportContainer::_bind_methods() { - ClassDB::bind_method("_gui_input", &Node3DEditorViewportContainer::_gui_input); -} - Node3DEditorViewportContainer::Node3DEditorViewportContainer() { set_clip_contents(true); view = VIEW_USE_1_VIEWPORT; @@ -5606,6 +5598,8 @@ void Node3DEditor::_init_indicators() { Ref<Shader> grid_shader = memnew(Shader); grid_shader->set_code(R"( +// 3D editor grid shader. + shader_type spatial; render_mode unshaded; @@ -5847,6 +5841,8 @@ void fragment() { Ref<Shader> rotate_shader = memnew(Shader); rotate_shader->set_code(R"( +// 3D editor rotation manipulator gizmo shader. + shader_type spatial; render_mode unshaded, depth_test_disabled; @@ -5895,6 +5891,8 @@ void fragment() { Ref<Shader> border_shader = memnew(Shader); border_shader->set_code(R"( +// 3D editor rotation manipulator gizmo shader (white outline). + shader_type spatial; render_mode unshaded, depth_test_disabled; @@ -6512,7 +6510,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { } } -void Node3DEditor::_unhandled_key_input(Ref<InputEvent> p_event) { +void Node3DEditor::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!is_visible_in_tree()) { @@ -6890,7 +6888,6 @@ void Node3DEditor::_register_all_gizmos() { } void Node3DEditor::_bind_methods() { - ClassDB::bind_method("_unhandled_key_input", &Node3DEditor::_unhandled_key_input); ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data); ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo); ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection); @@ -7515,6 +7512,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { sun_direction_shader.instantiate(); sun_direction_shader->set_code(R"( +// 3D editor Preview Sun direction shader. + shader_type canvas_item; uniform vec3 sun_direction; diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 868b834993..59f3ec6fcd 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -74,9 +74,8 @@ class ViewportRotationControl : public Control { const float AXIS_CIRCLE_RADIUS = 8.0f * EDSCALE; protected: - static void _bind_methods(); void _notification(int p_what); - void _gui_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _draw(); void _draw_axis(const Axis2D &p_axis); void _get_sorted_axis(Vector<Axis2D> &r_axis); @@ -470,11 +469,10 @@ private: Vector2 drag_begin_pos; Vector2 drag_begin_ratio; - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; protected: void _notification(int p_what); - static void _bind_methods(); public: void set_view(View p_view); @@ -744,7 +742,7 @@ private: protected: void _notification(int p_what); //void _gui_input(InputEvent p_event); - void _unhandled_key_input(Ref<InputEvent> p_event); + virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; static void _bind_methods(); diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp index 0a949c8610..53c5b8dd70 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.cpp +++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp @@ -31,6 +31,7 @@ #include "packed_scene_translation_parser_plugin.h" #include "core/io/resource_loader.h" +#include "scene/gui/option_button.h" #include "scene/resources/packed_scene.h" void PackedSceneEditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const { @@ -50,21 +51,31 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, Ref<SceneState> state = Ref<PackedScene>(loaded_res)->get_state(); Vector<String> parsed_strings; - String property_name; - Variant property_value; for (int i = 0; i < state->get_node_count(); i++) { - if (!ClassDB::is_parent_class(state->get_node_type(i), "Control") && !ClassDB::is_parent_class(state->get_node_type(i), "Viewport")) { + String node_type = state->get_node_type(i); + if (!ClassDB::is_parent_class(node_type, "Control") && !ClassDB::is_parent_class(node_type, "Window")) { continue; } + // Find the `auto_translate` property, and abort the string parsing of the node if disabled. + bool auto_translating = true; for (int j = 0; j < state->get_node_property_count(i); j++) { - property_name = state->get_node_property_name(i, j); - if (!lookup_properties.has(property_name)) { - continue; + if (state->get_node_property_name(i, j) == "auto_translate" && (bool)state->get_node_property_value(i, j) == false) { + auto_translating = false; + break; } + } + if (!auto_translating) { + continue; + } - property_value = state->get_node_property_value(i, j); + for (int j = 0; j < state->get_node_property_count(i); j++) { + String property_name = state->get_node_property_name(i, j); + if (!lookup_properties.has(property_name) || (exception_list.has(node_type) && exception_list[node_type].has(property_name))) { + continue; + } + Variant property_value = state->get_node_property_value(i, j); if (property_name == "script" && property_value.get_type() == Variant::OBJECT && !property_value.is_null()) { // Parse built-in script. Ref<Script> s = Object::cast_to<Script>(property_value); @@ -76,7 +87,16 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, parsed_strings.append_array(temp); r_ids_ctx_plural->append_array(ids_context_plural); } - } else if (property_name == "filters") { + } else if ((node_type == "MenuButton" || node_type == "OptionButton") && property_name == "items") { + Vector<String> str_values = property_value; + int incr_value = node_type == "MenuButton" ? PopupMenu::ITEM_PROPERTY_SIZE : OptionButton::ITEM_PROPERTY_SIZE; + for (int k = 0; k < str_values.size(); k += incr_value) { + String desc = str_values[k].get_slice(";", 1).strip_edges(); + if (!desc.is_empty()) { + parsed_strings.push_back(desc); + } + } + } else if (node_type == "FileDialog" && property_name == "filters") { // Extract FileDialog's filters property with values in format "*.png ; PNG Images","*.gd ; GDScript Files". Vector<String> str_values = property_value; for (int k = 0; k < str_values.size(); k++) { @@ -105,12 +125,17 @@ PackedSceneEditorTranslationParserPlugin::PackedSceneEditorTranslationParserPlug lookup_properties.insert("text"); lookup_properties.insert("hint_tooltip"); lookup_properties.insert("placeholder_text"); + lookup_properties.insert("items"); + lookup_properties.insert("title"); lookup_properties.insert("dialog_text"); lookup_properties.insert("filters"); lookup_properties.insert("script"); - //Add exception list (to prevent false positives) - //line edit, text edit, richtextlabel - //Set<String> exception_list; - //exception_list.insert("RichTextLabel"); + // Exception list (to prevent false positives). + exception_list.insert("LineEdit", Vector<StringName>()); + exception_list["LineEdit"].append("text"); + exception_list.insert("TextEdit", Vector<StringName>()); + exception_list["TextEdit"].append("text"); + exception_list.insert("CodeEdit", Vector<StringName>()); + exception_list["CodeEdit"].append("text"); } diff --git a/editor/plugins/packed_scene_translation_parser_plugin.h b/editor/plugins/packed_scene_translation_parser_plugin.h index e51d65414e..af0291b69c 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.h +++ b/editor/plugins/packed_scene_translation_parser_plugin.h @@ -37,7 +37,9 @@ class PackedSceneEditorTranslationParserPlugin : public EditorTranslationParserP GDCLASS(PackedSceneEditorTranslationParserPlugin, EditorTranslationParserPlugin); // Scene Node's properties that contain translation strings. - Set<String> lookup_properties; + Set<StringName> lookup_properties; + // Properties from specific Nodes that should be ignored. + Map<StringName, Vector<StringName>> exception_list; public: virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override; diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp index 584eb84ecd..119ecddf63 100644 --- a/editor/plugins/path_2d_editor_plugin.cpp +++ b/editor/plugins/path_2d_editor_plugin.cpp @@ -70,7 +70,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { return false; } - real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index 9377395418..782152b002 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -1041,7 +1041,7 @@ void Polygon2DEditor::_uv_draw() { for (int i = 0; i < uvs.size(); i++) { int next = uv_draw_max > 0 ? (i + 1) % uv_draw_max : 0; - if (i < uv_draw_max && uv_drag && uv_move_current == UV_MODE_EDIT_POINT && EDITOR_DEF("editors/poly_editor/show_previous_outline", true)) { + if (i < uv_draw_max && uv_drag && uv_move_current == UV_MODE_EDIT_POINT && EDITOR_DEF("editors/polygon_editor/show_previous_outline", true)) { uv_edit_draw->draw_line(mtx.xform(points_prev[i]), mtx.xform(points_prev[next]), prev_color, Math::round(EDSCALE)); } diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp index cbea2405b8..eae6916a92 100644 --- a/editor/plugins/resource_preloader_editor_plugin.cpp +++ b/editor/plugins/resource_preloader_editor_plugin.cpp @@ -35,9 +35,6 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" -void ResourcePreloaderEditor::_gui_input(Ref<InputEvent> p_event) { -} - void ResourcePreloaderEditor::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { load->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); @@ -335,7 +332,6 @@ void ResourcePreloaderEditor::drop_data_fw(const Point2 &p_point, const Variant } void ResourcePreloaderEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &ResourcePreloaderEditor::_gui_input); ClassDB::bind_method(D_METHOD("_update_library"), &ResourcePreloaderEditor::_update_library); ClassDB::bind_method(D_METHOD("_remove_resource", "to_remove"), &ResourcePreloaderEditor::_remove_resource); diff --git a/editor/plugins/resource_preloader_editor_plugin.h b/editor/plugins/resource_preloader_editor_plugin.h index bc10b48a16..04ab458eb5 100644 --- a/editor/plugins/resource_preloader_editor_plugin.h +++ b/editor/plugins/resource_preloader_editor_plugin.h @@ -75,7 +75,7 @@ class ResourcePreloaderEditor : public PanelContainer { protected: void _notification(int p_what); - void _gui_input(Ref<InputEvent> p_event); + static void _bind_methods(); public: diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 567cd33305..7ef5993ec5 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -37,6 +37,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "editor/debugger/editor_debugger_node.h" +#include "editor/debugger/script_editor_debugger.h" #include "editor/editor_node.h" #include "editor/editor_run_script.h" #include "editor/editor_scale.h" @@ -115,9 +116,9 @@ void EditorStandardSyntaxHighlighter::_update_cache() { } /* Autoloads. */ - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->value(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.value(); if (info.is_singleton) { highlighter->add_keyword_color(info.name, usertype_color); } @@ -316,7 +317,7 @@ void ScriptEditorQuickOpen::_sbox_input(const Ref<InputEvent> &p_ie) { k->get_keycode() == KEY_DOWN || k->get_keycode() == KEY_PAGEUP || k->get_keycode() == KEY_PAGEDOWN)) { - search_options->call("_gui_input", k); + search_options->gui_input(k); search_box->accept_event(); } } @@ -479,6 +480,29 @@ void ScriptEditor::_clear_execution(REF p_script) { } } +void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) { + Ref<Script> script = Object::cast_to<Script>(*p_script); + if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) { + if (edit(p_script, p_line, 0, false)) { + editor->push_item(p_script.ptr()); + + ScriptEditorBase *se = _get_current_editor(); + if (se) { + se->set_breakpoint(p_line, p_enabled); + } + } + } +} + +void ScriptEditor::_clear_breakpoints() { + for (int i = 0; i < tab_container->get_child_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + if (se) { + se->clear_breakpoints(); + } + } +} + ScriptEditorBase *ScriptEditor::_get_current_editor() const { int selected = tab_container->get_current_tab(); if (selected < 0 || selected >= tab_container->get_child_count()) { @@ -2728,7 +2752,7 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co } } -void ScriptEditor::_unhandled_key_input(const Ref<InputEvent> &p_event) { +void ScriptEditor::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!is_visible_in_tree() || !p_event->is_pressed() || p_event->is_echo()) { @@ -3272,7 +3296,7 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_update_script_connections", &ScriptEditor::_update_script_connections); ClassDB::bind_method("_help_class_open", &ScriptEditor::_help_class_open); ClassDB::bind_method("_live_auto_reload_running_scripts", &ScriptEditor::_live_auto_reload_running_scripts); - ClassDB::bind_method("_unhandled_key_input", &ScriptEditor::_unhandled_key_input); + ClassDB::bind_method("_update_members_overview", &ScriptEditor::_update_members_overview); ClassDB::bind_method("_update_recent_scripts", &ScriptEditor::_update_recent_scripts); @@ -3484,6 +3508,8 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { debugger->connect("set_execution", callable_mp(this, &ScriptEditor::_set_execution)); debugger->connect("clear_execution", callable_mp(this, &ScriptEditor::_clear_execution)); debugger->connect("breaked", callable_mp(this, &ScriptEditor::_breaked)); + debugger->get_default_debugger()->connect("set_breakpoint", callable_mp(this, &ScriptEditor::_set_breakpoint)); + debugger->get_default_debugger()->connect("clear_breakpoints", callable_mp(this, &ScriptEditor::_clear_breakpoints)); menu_hb->add_spacer(); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index a1474c5f66..e2420b4623 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -155,6 +155,8 @@ public: virtual void tag_saved_version() = 0; virtual void reload(bool p_soft) {} virtual Array get_breakpoints() = 0; + virtual void set_breakpoint(int p_line, bool p_enabled) = 0; + virtual void clear_breakpoints() = 0; virtual void add_callback(const String &p_function, PackedStringArray p_args) = 0; virtual void update_settings() = 0; virtual void set_debugger_active(bool p_active) = 0; @@ -374,6 +376,8 @@ class ScriptEditor : public PanelContainer { void _breaked(bool p_breaked, bool p_can_debug); void _update_window_menu(); void _script_created(Ref<Script> p_script); + void _set_breakpoint(REF p_scrpt, int p_line, bool p_enabled); + void _clear_breakpoints(); ScriptEditorBase *_get_current_editor() const; Array _get_open_script_editors() const; @@ -411,7 +415,7 @@ class ScriptEditor : public PanelContainer { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); - void _unhandled_key_input(const Ref<InputEvent> &p_event); + virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; void _script_list_gui_input(const Ref<InputEvent> &ev); void _make_script_list_context_menu(); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 5f48106afc..48239a5d99 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -158,7 +158,6 @@ void ScriptTextEditor::enable_editor() { editor_enabled = true; _enable_code_editor(); - _set_theme_for_script(); _validate_script(); } @@ -831,7 +830,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c if (info.is_singleton) { EditorNode::get_singleton()->load_scene(info.path); } - } else if (p_symbol.is_rel_path()) { + } else if (p_symbol.is_relative_path()) { // Every symbol other than absolute path is relative path so keep this condition at last. String path = _get_absolute_path(p_symbol); if (FileAccess::exists(path)) { @@ -858,7 +857,7 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) { ScriptLanguage::LookupResult result; if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK || (ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton)) { text_edit->set_symbol_lookup_word_as_valid(true); - } else if (p_symbol.is_rel_path()) { + } else if (p_symbol.is_relative_path()) { String path = _get_absolute_path(p_symbol); if (FileAccess::exists(path)) { text_edit->set_symbol_lookup_word_as_valid(true); @@ -1370,6 +1369,14 @@ Array ScriptTextEditor::get_breakpoints() { return code_editor->get_text_editor()->get_breakpointed_lines(); } +void ScriptTextEditor::set_breakpoint(int p_line, bool p_enabled) { + code_editor->get_text_editor()->set_line_as_breakpoint(p_line, p_enabled); +} + +void ScriptTextEditor::clear_breakpoints() { + code_editor->get_text_editor()->clear_breakpointed_lines(); +} + void ScriptTextEditor::set_tooltip_request_func(String p_method, Object *p_obj) { code_editor->get_text_editor()->set_tooltip_request_func(p_obj, p_method, this); } diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 1ca6f56ea1..4208d67f17 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -225,6 +225,8 @@ public: virtual void reload(bool p_soft) override; virtual Array get_breakpoints() override; + virtual void set_breakpoint(int p_line, bool p_enabled) override; + virtual void clear_breakpoints() override; virtual void add_callback(const String &p_function, PackedStringArray p_args) override; virtual void update_settings() override; diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 2883dbbc81..400f9f560f 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -40,7 +40,7 @@ #include "scene/gui/margin_container.h" #include "scene/gui/panel_container.h" -void SpriteFramesEditor::_gui_input(Ref<InputEvent> p_event) { +void SpriteFramesEditor::gui_input(const Ref<InputEvent> &p_event) { } void SpriteFramesEditor::_open_sprite_sheet() { diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index e6c59e3533..17e30f0cab 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -147,7 +147,7 @@ class SpriteFramesEditor : public HSplitContainer { protected: void _notification(int p_what); - void _gui_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; static void _bind_methods(); public: diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index 839e1c5f7a..6bf0042393 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -121,6 +121,8 @@ public: virtual void set_edit_state(const Variant &p_state) override; virtual Vector<String> get_functions() override; virtual Array get_breakpoints() override; + virtual void set_breakpoint(int p_line, bool p_enabled) override{}; + virtual void clear_breakpoints() override{}; virtual void goto_line(int p_line, bool p_with_error = false) override; void goto_line_selection(int p_line, int p_begin, int p_end); virtual void set_executing_line(int p_line) override; diff --git a/editor/plugins/texture_3d_editor_plugin.cpp b/editor/plugins/texture_3d_editor_plugin.cpp index 3987cdd6a0..bd1923f4ab 100644 --- a/editor/plugins/texture_3d_editor_plugin.cpp +++ b/editor/plugins/texture_3d_editor_plugin.cpp @@ -34,9 +34,6 @@ #include "core/io/resource_loader.h" #include "editor/editor_settings.h" -void Texture3DEditor::_gui_input(Ref<InputEvent> p_event) { -} - void Texture3DEditor::_texture_rect_draw() { texture_rect->draw_rect(Rect2(Point2(), texture_rect->get_size()), Color(1, 1, 1, 1)); } @@ -79,6 +76,8 @@ void Texture3DEditor::_update_material() { void Texture3DEditor::_make_shaders() { shader.instantiate(); shader->set_code(R"( +// Texture3DEditor preview shader. + shader_type canvas_item; uniform sampler3D tex; @@ -145,7 +144,6 @@ void Texture3DEditor::edit(Ref<Texture3D> p_texture) { } void Texture3DEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &Texture3DEditor::_gui_input); ClassDB::bind_method(D_METHOD("_layer_changed"), &Texture3DEditor::_layer_changed); } diff --git a/editor/plugins/texture_3d_editor_plugin.h b/editor/plugins/texture_3d_editor_plugin.h index 9d90d3653f..855194e644 100644 --- a/editor/plugins/texture_3d_editor_plugin.h +++ b/editor/plugins/texture_3d_editor_plugin.h @@ -65,7 +65,6 @@ class Texture3DEditor : public Control { protected: void _notification(int p_what); - void _gui_input(Ref<InputEvent> p_event); static void _bind_methods(); diff --git a/editor/plugins/texture_layered_editor_plugin.cpp b/editor/plugins/texture_layered_editor_plugin.cpp index 80359452ac..424e018a47 100644 --- a/editor/plugins/texture_layered_editor_plugin.cpp +++ b/editor/plugins/texture_layered_editor_plugin.cpp @@ -34,7 +34,7 @@ #include "core/io/resource_loader.h" #include "editor/editor_settings.h" -void TextureLayeredEditor::_gui_input(Ref<InputEvent> p_event) { +void TextureLayeredEditor::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> mm = p_event; @@ -106,6 +106,8 @@ void TextureLayeredEditor::_update_material() { void TextureLayeredEditor::_make_shaders() { shaders[0].instantiate(); shaders[0]->set_code(R"( +// TextureLayeredEditor preview shader (2D array). + shader_type canvas_item; uniform sampler2DArray tex; @@ -118,6 +120,8 @@ void fragment() { shaders[1].instantiate(); shaders[1]->set_code(R"( +// TextureLayeredEditor preview shader (cubemap). + shader_type canvas_item; uniform samplerCube tex; @@ -132,6 +136,8 @@ void fragment() { shaders[2].instantiate(); shaders[2]->set_code(R"( +// TextureLayeredEditor preview shader (cubemap array). + shader_type canvas_item; uniform samplerCubeArray tex; @@ -214,7 +220,6 @@ void TextureLayeredEditor::edit(Ref<TextureLayered> p_texture) { } void TextureLayeredEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &TextureLayeredEditor::_gui_input); ClassDB::bind_method(D_METHOD("_layer_changed"), &TextureLayeredEditor::_layer_changed); } diff --git a/editor/plugins/texture_layered_editor_plugin.h b/editor/plugins/texture_layered_editor_plugin.h index c4ced62fb9..a7fe4b94e9 100644 --- a/editor/plugins/texture_layered_editor_plugin.h +++ b/editor/plugins/texture_layered_editor_plugin.h @@ -67,7 +67,7 @@ class TextureLayeredEditor : public Control { protected: void _notification(int p_what); - void _gui_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; static void _bind_methods(); public: diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index 13f04cb804..0add83f64d 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -41,7 +41,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" -void TileAtlasView::_gui_input(const Ref<InputEvent> &p_event) { +void TileAtlasView::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { drag_type = DRAG_TYPE_NONE; @@ -548,8 +548,6 @@ void TileAtlasView::_notification(int p_what) { } void TileAtlasView::_bind_methods() { - ClassDB::bind_method("_gui_input", &TileAtlasView::_gui_input); - ADD_SIGNAL(MethodInfo("transform_changed", PropertyInfo(Variant::FLOAT, "zoom"), PropertyInfo(Variant::VECTOR2, "scroll"))); } @@ -582,7 +580,7 @@ TileAtlasView::TileAtlasView() { center_container = memnew(CenterContainer); center_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); center_container->set_anchors_preset(Control::PRESET_CENTER); - center_container->connect("gui_input", callable_mp(this, &TileAtlasView::_gui_input)); + center_container->connect("gui_input", callable_mp(this, &TileAtlasView::gui_input)); panel->add_child(center_container); missing_source_label = memnew(Label); diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index b2046f4322..5b0df366ae 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -62,7 +62,7 @@ private: void _update_zoom_and_panning(bool p_zoom_on_mouse_pos = false); void _zoom_widget_changed(); void _center_view(); - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; Map<Vector2, Map<int, Rect2i>> alternative_tiles_rect_cache; void _update_alternative_tiles_rect_cache(); diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index bab55df65a..fd5c59af34 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -110,7 +110,7 @@ void DummyObject::clear_dummy_properties() { void GenericTilePolygonEditor::_base_control_draw() { ERR_FAIL_COND(!tile_set.is_valid()); - real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); @@ -262,7 +262,7 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { } void GenericTilePolygonEditor::_grab_polygon_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_point_index) { - const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); r_polygon_index = -1; r_point_index = -1; float closest_distance = grab_threshold + 1.0; @@ -280,7 +280,7 @@ void GenericTilePolygonEditor::_grab_polygon_point(Vector2 p_pos, const Transfor } void GenericTilePolygonEditor::_grab_polygon_segment_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_segment_index, Vector2 &r_point) { - const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); Point2 point = p_polygon_xform.affine_inverse().xform(p_pos); r_polygon_index = -1; @@ -340,7 +340,7 @@ void GenericTilePolygonEditor::_snap_to_half_pixel(Point2 &r_point) { } void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) { - real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); hovered_polygon_index = -1; hovered_point_index = -1; @@ -1465,12 +1465,13 @@ void TileDataTerrainsEditor::_tile_set_changed() { ERR_FAIL_COND(!tile_set.is_valid()); // Fix if wrong values are selected. - if (int(dummy_object->get("terrain_set")) > tile_set->get_terrain_sets_count()) { + int terrain_set = int(dummy_object->get("terrain_set")); + if (terrain_set >= tile_set->get_terrain_sets_count()) { + terrain_set = -1; dummy_object->set("terrain_set", -1); } - int terrain_set = int(dummy_object->get("terrain")); if (terrain_set >= 0) { - if (int(dummy_object->get("terrain")) > tile_set->get_terrains_count(terrain_set)) { + if (int(dummy_object->get("terrain")) >= tile_set->get_terrains_count(terrain_set)) { dummy_object->set("terrain", -1); } } diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 77084f551a..b5e070b4d6 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -3549,30 +3549,76 @@ void TileMapEditor::_update_layers_selection() { tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer); } -void TileMapEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { +void TileMapEditor::_move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) { UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); ERR_FAIL_COND(!undo_redo); TileMap *tile_map = Object::cast_to<TileMap>(p_edited); - if (tile_map) { - if (p_property == "layers_count") { - int new_layers_count = (int)p_new_value; - if (new_layers_count < tile_map->get_layers_count()) { - List<PropertyInfo> property_list; - tile_map->get_property_list(&property_list); - - for (PropertyInfo property_info : property_list) { - Vector<String> components = String(property_info.name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index >= new_layers_count) { - undo_redo->add_undo_property(tile_map, property_info.name, tile_map->get(property_info.name)); - } - } + if (!tile_map) { + return; + } + + // Compute the array indices to save. + int begin = 0; + int end; + if (p_array_prefix == "layer_") { + end = tile_map->get_layers_count(); + } else { + ERR_FAIL_MSG("Invalid array prefix for TileSet."); + } + if (p_from_index < 0) { + // Adding new. + if (p_to_pos >= 0) { + begin = p_to_pos; + } else { + end = 0; // Nothing to save when adding at the end. + } + } else if (p_to_pos < 0) { + // Removing. + begin = p_from_index; + } else { + // Moving. + begin = MIN(p_from_index, p_to_pos); + end = MIN(MAX(p_from_index, p_to_pos) + 1, end); + } + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); + // Save layers' properties. + if (p_from_index < 0) { + undo_redo->add_undo_method(tile_map, "remove_layer", p_to_pos < 0 ? tile_map->get_layers_count() : p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_undo_method(tile_map, "add_layer", p_from_index); + } + + List<PropertyInfo> properties; + tile_map->get_property_list(&properties); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(p_array_prefix)) { + String str = pi.name.trim_prefix(p_array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + if (array_index >= begin && array_index < end) { + ADD_UNDO(tile_map, pi.name); } } } } +#undef ADD_UNDO + + if (p_from_index < 0) { + undo_redo->add_do_method(tile_map, "add_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_map, "remove_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_map, "move_layer", p_from_index, p_to_pos); + } } bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { @@ -3851,7 +3897,7 @@ TileMapEditor::TileMapEditor() { _tab_changed(0); // Registers UndoRedo inspector callback. - EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileMapEditor::_undo_redo_inspector_callback)); + EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileMap"), callable_mp(this, &TileMapEditor::_move_tile_map_array_element)); } TileMapEditor::~TileMapEditor() { diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index 6e2f2ce2ba..6126db59e9 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -341,7 +341,7 @@ private: void _update_layers_selection(); // Inspector undo/redo callback. - void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); + void _move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos); protected: void _notification(int p_what); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 432f48fa85..c3a3f40e00 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -1866,7 +1866,7 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); ERR_FAIL_COND(!undo_redo); -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited); if (tile_data) { diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index ba98a7d6b3..48d0d9b333 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -330,11 +330,192 @@ void TileSetEditor::_tile_set_changed() { tile_set_changed_needs_update = true; } +void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) { + UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); + ERR_FAIL_COND(!undo_redo); + + TileSet *tile_set = Object::cast_to<TileSet>(p_edited); + if (!tile_set) { + return; + } + + Vector<String> components = String(p_array_prefix).split("/", true, 2); + + // Compute the array indices to save. + int begin = 0; + int end; + if (p_array_prefix == "occlusion_layer_") { + end = tile_set->get_occlusion_layers_count(); + } else if (p_array_prefix == "physics_layer_") { + end = tile_set->get_physics_layers_count(); + } else if (p_array_prefix == "terrain_set_") { + end = tile_set->get_terrain_sets_count(); + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + int terrain_set = components[0].trim_prefix("terrain_set_").to_int(); + end = tile_set->get_terrains_count(terrain_set); + } else if (p_array_prefix == "navigation_layer_") { + end = tile_set->get_navigation_layers_count(); + } else if (p_array_prefix == "custom_data_layer_") { + end = tile_set->get_custom_data_layers_count(); + } else { + ERR_FAIL_MSG("Invalid array prefix for TileSet."); + } + if (p_from_index < 0) { + // Adding new. + if (p_to_pos >= 0) { + begin = p_to_pos; + } else { + end = 0; // Nothing to save when adding at the end. + } + } else if (p_to_pos < 0) { + // Removing. + begin = p_from_index; + } else { + // Moving. + begin = MIN(p_from_index, p_to_pos); + end = MIN(MAX(p_from_index, p_to_pos) + 1, end); + } + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); + // Save layers' properties. + List<PropertyInfo> properties; + tile_set->get_property_list(&properties); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(p_array_prefix)) { + String str = pi.name.trim_prefix(p_array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + if (array_index >= begin && array_index < end) { + ADD_UNDO(tile_set, pi.name); + } + } + } + } + + // Save properties for TileSetAtlasSources tile data + for (int i = 0; i < tile_set->get_source_count(); i++) { + int source_id = tile_set->get_source_id(i); + + Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id); + if (tas.is_valid()) { + for (int j = 0; j < tas->get_tiles_count(); j++) { + Vector2i tile_id = tas->get_tile_id(j); + for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) { + int alternative_id = tas->get_alternative_tile_id(tile_id, k); + TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id)); + ERR_FAIL_COND(!tile_data); + + // Actually saving stuff. + if (p_array_prefix == "occlusion_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", layer_index)); + } + } else if (p_array_prefix == "physics_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", layer_index)); + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(layer_index); polygon_index++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, polygon_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, polygon_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, polygon_index)); + } + } + } else if (p_array_prefix == "terrain_set_") { + ADD_UNDO(tile_data, "terrain_set"); + for (int terrain_set_index = begin; terrain_set_index < end; terrain_set_index++) { + for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(l); + if (tile_data->is_valid_peering_bit_terrain(bit)) { + ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l])); + } + } + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + for (int terrain_index = 0; terrain_index < TileSet::CELL_NEIGHBOR_MAX; terrain_index++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(terrain_index); + if (tile_data->is_valid_peering_bit_terrain(bit)) { + ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[terrain_index])); + } + } + } else if (p_array_prefix == "navigation_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", layer_index)); + } + } else if (p_array_prefix == "custom_data_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("custom_data_%d", layer_index)); + } + } + } + } + } + } +#undef ADD_UNDO + + // Add do method. + if (p_array_prefix == "occlusion_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_occlusion_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_occlusion_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_occlusion_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "physics_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_physics_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_physics_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_physics_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "terrain_set_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_terrain_set", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_terrain_set", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_terrain_set", p_from_index, p_to_pos); + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + int terrain_set = components[0].trim_prefix("terrain_set_").to_int(); + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_terrain", terrain_set, p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_terrain", terrain_set, p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_terrain", terrain_set, p_from_index, p_to_pos); + } + } else if (p_array_prefix == "navigation_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_navigation_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_navigation_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_navigation_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "custom_data_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_custom_data_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_custom_data_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_custom_data_layer", p_from_index, p_to_pos); + } + } +} + void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); ERR_FAIL_COND(!undo_redo); -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); TileSet *tile_set = Object::cast_to<TileSet>(p_edited); if (tile_set) { Vector<String> components = p_property.split("/", true, 3); @@ -350,30 +531,7 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id)); ERR_FAIL_COND(!tile_data); - if (p_property == "occlusion_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_occlusion_layers_count(); - if (new_layer_count < old_layer_count) { - for (int occclusion_layer_index = new_layer_count - 1; occclusion_layer_index < old_layer_count; occclusion_layer_index++) { - ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", occclusion_layer_index)); - } - } - } else if (p_property == "physics_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_physics_layers_count(); - if (new_layer_count < old_layer_count) { - for (int physics_layer_index = new_layer_count - 1; physics_layer_index < old_layer_count; physics_layer_index++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", physics_layer_index)); - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(physics_layer_index); polygon_index++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", physics_layer_index, polygon_index)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", physics_layer_index, polygon_index)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", physics_layer_index, polygon_index)); - } - } - } - } else if ((p_property == "terrains_sets_count" && tile_data->get_terrain_set() >= (int)p_new_value) || - (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") || - (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrains_count" && tile_data->get_terrain_set() == components[0].trim_prefix("terrain_set_").to_int() && (int)p_new_value < tile_set->get_terrains_count(tile_data->get_terrain_set()))) { + if (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") { ADD_UNDO(tile_data, "terrain_set"); for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(l); @@ -381,22 +539,6 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l])); } } - } else if (p_property == "navigation_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_navigation_layers_count(); - if (new_layer_count < old_layer_count) { - for (int navigation_layer_index = new_layer_count - 1; navigation_layer_index < old_layer_count; navigation_layer_index++) { - ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", navigation_layer_index)); - } - } - } else if (p_property == "custom_data_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_custom_data_layers_count(); - if (new_layer_count < old_layer_count) { - for (int custom_data_layer_index = new_layer_count - 1; custom_data_layer_index < old_layer_count; custom_data_layer_index++) { - ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer_index)); - } - } } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int() && components[1] == "type") { int custom_data_layer = components[0].trim_prefix("custom_data_layer_").is_valid_int(); ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer)); @@ -531,6 +673,7 @@ TileSetEditor::TileSetEditor() { tile_set_scenes_collection_source_editor->hide(); // Registers UndoRedo inspector callback. + EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileSet"), callable_mp(this, &TileSetEditor::_move_tile_set_array_element)); EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback)); } diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index 970e3fabb6..fe854b2281 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -71,6 +71,7 @@ private: void _tile_set_changed(); + void _move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos); void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); protected: diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 4ed9f712c1..50808a25af 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -2547,6 +2547,7 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa } } } + _member_cancel(); VisualShaderNodeUniform *uniform = Object::cast_to<VisualShaderNodeUniform>(vsnode.ptr()); if (uniform) { @@ -3171,7 +3172,7 @@ void VisualShaderEditor::_sbox_input(const Ref<InputEvent> &p_ie) { ie->get_keycode() == KEY_DOWN || ie->get_keycode() == KEY_ENTER || ie->get_keycode() == KEY_KP_ENTER)) { - members->call("_gui_input", ie); + members->gui_input(ie); node_filter->accept_event(); } } diff --git a/editor/pot_generator.cpp b/editor/pot_generator.cpp index b58b7e4cac..d57345cac1 100644 --- a/editor/pot_generator.cpp +++ b/editor/pot_generator.cpp @@ -108,7 +108,6 @@ void POTGenerator::_write_to_pot(const String &p_file) { const String header = "# LANGUAGE translation for " + project_name + " for the following files:\n" + extracted_files + "#\n" - "#\n" "# FIRST AUTHOR < EMAIL @ADDRESS>, YEAR.\n" "#\n" "#, fuzzy\n" @@ -116,8 +115,9 @@ void POTGenerator::_write_to_pot(const String &p_file) { "msgstr \"\"\n" "\"Project-Id-Version: " + project_name + "\\n\"\n" + "\"MIME-Version: 1.0\\n\"\n" "\"Content-Type: text/plain; charset=UTF-8\\n\"\n" - "\"Content-Transfer-Encoding: 8-bit\\n\"\n\n"; + "\"Content-Transfer-Encoding: 8-bit\\n\"\n"; file->store_string(header); @@ -129,6 +129,9 @@ void POTGenerator::_write_to_pot(const String &p_file) { String plural = v_msgid_data[i].plural; const Set<String> &locations = v_msgid_data[i].locations; + // Put the blank line at the start, to avoid a double at the end when closing the file. + file->store_line(""); + // Write file locations. for (Set<String>::Element *E = locations.front(); E; E = E->next()) { file->store_line("#: " + E->get().trim_prefix("res://")); @@ -142,13 +145,13 @@ void POTGenerator::_write_to_pot(const String &p_file) { // Write msgid. _write_msgid(file, msgid, false); - // Write msgid_plural + // Write msgid_plural. if (!plural.is_empty()) { _write_msgid(file, plural, true); file->store_line("msgstr[0] \"\""); - file->store_line("msgstr[1] \"\"\n"); + file->store_line("msgstr[1] \"\""); } else { - file->store_line("msgstr \"\"\n"); + file->store_line("msgstr \"\""); } } } diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 8d425a1e51..05cf3791f4 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1887,7 +1887,7 @@ void ProjectManager::_update_project_buttons() { erase_missing_btn->set_disabled(!_project_list->is_any_project_missing()); } -void ProjectManager::_unhandled_key_input(const Ref<InputEvent> &p_ev) { +void ProjectManager::unhandled_key_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventKey> k = p_ev; @@ -2364,7 +2364,6 @@ void ProjectManager::_on_search_term_changed(const String &p_term) { } void ProjectManager::_bind_methods() { - ClassDB::bind_method("_unhandled_key_input", &ProjectManager::_unhandled_key_input); ClassDB::bind_method("_update_project_buttons", &ProjectManager::_update_project_buttons); ClassDB::bind_method("_version_button_pressed", &ProjectManager::_version_button_pressed); } diff --git a/editor/project_manager.h b/editor/project_manager.h index 0fc69bd313..f45d34d461 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -121,7 +121,7 @@ class ProjectManager : public Control { void _install_project(const String &p_zip_path, const String &p_title); void _dim_window(); - void _unhandled_key_input(const Ref<InputEvent> &p_ev); + virtual void unhandled_key_input(const Ref<InputEvent> &p_ev) override; void _files_dropped(PackedStringArray p_files, int p_screen); void _version_button_pressed(); diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index b8ccab78dd..db12e90540 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -222,7 +222,6 @@ void ProjectSettingsEditor::_add_feature_overrides() { presets.insert("standalone"); presets.insert("32"); presets.insert("64"); - presets.insert("Server"); // Not available as an export platform yet, so it needs to be added manually EditorExport *ee = EditorExport::get_singleton(); diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index 1272d064a0..f167ded4e7 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -48,7 +48,7 @@ void PropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { case KEY_DOWN: case KEY_PAGEUP: case KEY_PAGEDOWN: { - search_options->call("_gui_input", k); + search_options->gui_input(k); search_box->accept_event(); TreeItem *root = search_options->get_root(); diff --git a/editor/quick_open.cpp b/editor/quick_open.cpp index f8af3e8f36..fc3abbb87e 100644 --- a/editor/quick_open.cpp +++ b/editor/quick_open.cpp @@ -130,12 +130,6 @@ float EditorQuickOpen::_score_path(const String &p_search, const String &p_path) return score * (1.0f - 0.1f * (float(pos) / file.length())); } - // Positive bias for matches close to the end of the path. - pos = p_path.rfindn(p_search); - if (pos != -1) { - return 1.1f + 0.09 / (p_path.length() - pos + 1); - } - // Similarity return p_path.to_lower().similarity(p_search.to_lower()); } @@ -170,7 +164,7 @@ void EditorQuickOpen::_sbox_input(const Ref<InputEvent> &p_ie) { case KEY_DOWN: case KEY_PAGEUP: case KEY_PAGEDOWN: { - search_options->call("_gui_input", k); + search_options->gui_input(k); search_box->accept_event(); if (allow_multi_select) { diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 0b228c2695..a08a628216 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -62,7 +62,7 @@ void SceneTreeDock::_quick_open() { instantiate_scenes(quick_open->get_selected_files(), scene_tree->get_selected()); } -void SceneTreeDock::_input(Ref<InputEvent> p_event) { +void SceneTreeDock::input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseButton> mb = p_event; @@ -72,7 +72,7 @@ void SceneTreeDock::_input(Ref<InputEvent> p_event) { } } -void SceneTreeDock::_unhandled_key_input(Ref<InputEvent> p_event) { +void SceneTreeDock::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (get_focus_owner() && get_focus_owner()->is_text_field()) { @@ -2837,6 +2837,11 @@ void SceneTreeDock::set_filter(const String &p_filter) { scene_tree->set_filter(p_filter); } +void SceneTreeDock::save_branch_to_file(String p_directory) { + new_scene_from_dialog->set_current_dir(p_directory); + _tool_selected(TOOL_NEW_SCENE_FROM); +} + void SceneTreeDock::_focus_node() { Node *node = scene_tree->get_selected(); ERR_FAIL_COND(!node); @@ -3163,8 +3168,7 @@ void SceneTreeDock::_create_remap_for_resource(RES p_resource, Map<RES, RES> &r_ void SceneTreeDock::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_owners"), &SceneTreeDock::_set_owners); - ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &SceneTreeDock::_unhandled_key_input); - ClassDB::bind_method(D_METHOD("_input"), &SceneTreeDock::_input); + ClassDB::bind_method(D_METHOD("_update_script_button"), &SceneTreeDock::_update_script_button); ClassDB::bind_method(D_METHOD("instantiate"), &SceneTreeDock::instantiate); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index ccdc0a3786..1086e8615f 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -210,8 +210,8 @@ class SceneTreeDock : public VBoxContainer { void _node_prerenamed(Node *p_node, const String &p_new_name); void _nodes_drag_begin(); - void _input(Ref<InputEvent> p_event); - void _unhandled_key_input(Ref<InputEvent> p_event); + virtual void input(const Ref<InputEvent> &p_event) override; + virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; void _import_subscene(); @@ -269,6 +269,7 @@ protected: public: String get_filter(); void set_filter(const String &p_filter); + void save_branch_to_file(String p_directory); void _focus_node(); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index e7ba80677d..d54bf73028 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -291,10 +291,12 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll } } + // Display the node name in all tooltips so that long node names can be previewed + // without having to rename them. if (p_node == get_scene_node() && p_node->get_scene_inherited_state().is_valid()) { item->add_button(0, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); - String tooltip = TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path() + "\n" + TTR("Type:") + " " + p_node->get_class(); + String tooltip = String(p_node->get_name()) + "\n" + TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path() + "\n" + TTR("Type:") + " " + p_node->get_class(); if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } @@ -303,7 +305,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll } else if (p_node != get_scene_node() && p_node->get_filename() != "" && can_open_instance) { item->add_button(0, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); - String tooltip = TTR("Instance:") + " " + p_node->get_filename() + "\n" + TTR("Type:") + " " + p_node->get_class(); + String tooltip = String(p_node->get_name()) + "\n" + TTR("Instance:") + " " + p_node->get_filename() + "\n" + TTR("Type:") + " " + p_node->get_class(); if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } @@ -315,7 +317,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll type = p_node->get_class(); } - String tooltip = TTR("Type:") + " " + type; + String tooltip = String(p_node->get_name()) + "\n" + TTR("Type:") + " " + type; if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index 71802d1737..649caf5373 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -138,7 +138,7 @@ void EditorSettingsDialog::_notification(int p_what) { } } -void EditorSettingsDialog::_unhandled_input(const Ref<InputEvent> &p_event) { +void EditorSettingsDialog::unhandled_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); const Ref<InputEventKey> k = p_event; @@ -542,7 +542,6 @@ void EditorSettingsDialog::_editor_restart_close() { } void EditorSettingsDialog::_bind_methods() { - ClassDB::bind_method(D_METHOD("_unhandled_input"), &EditorSettingsDialog::_unhandled_input); ClassDB::bind_method(D_METHOD("_update_shortcuts"), &EditorSettingsDialog::_update_shortcuts); ClassDB::bind_method(D_METHOD("_settings_changed"), &EditorSettingsDialog::_settings_changed); } diff --git a/editor/settings_config_dialog.h b/editor/settings_config_dialog.h index c38fceedf1..2b6c9b3e1d 100644 --- a/editor/settings_config_dialog.h +++ b/editor/settings_config_dialog.h @@ -83,7 +83,7 @@ class EditorSettingsDialog : public AcceptDialog { void _settings_property_edited(const String &p_name); void _settings_save(); - void _unhandled_input(const Ref<InputEvent> &p_event); + virtual void unhandled_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); void _update_icons(); diff --git a/editor/translations/el.po b/editor/translations/el.po index 93b5941f64..e773b011a4 100644 --- a/editor/translations/el.po +++ b/editor/translations/el.po @@ -6,7 +6,7 @@ # Georgios Katsanakis <geo.elgeo@gmail.com>, 2019. # Overloaded <manoschool@yahoo.gr>, 2019. # Eternal Death <eternaldeath0001@gmail.com>, 2019. -# Overloaded @ Orama Interactive http://orama-interactive.com/ <manoschool@yahoo.gr>, 2020. +# Overloaded @ Orama Interactive https://orama-interactive.com/ <manoschool@yahoo.gr>, 2020. # pandektis <pandektis@gmail.com>, 2020. # KostasMSC <kargyris@athtech.gr>, 2020. # lawfulRobot <czavantias@gmail.com>, 2020, 2021. diff --git a/main/main.cpp b/main/main.cpp index 5a2aaa8b8f..ece194b0f1 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -571,8 +571,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph List<String>::Element *I = args.front(); - I = args.front(); - while (I) { I->get() = unescape_cmdline(I->get().strip_edges()); I = I->next(); @@ -1561,8 +1559,8 @@ Error Main::setup2(Thread::ID p_main_tid_override) { { GLOBAL_DEF_RST_NOVAL("input_devices/pen_tablet/driver", ""); - GLOBAL_DEF_RST_NOVAL("input_devices/pen_tablet/driver.Windows", ""); - ProjectSettings::get_singleton()->set_custom_property_info("input_devices/pen_tablet/driver.Windows", PropertyInfo(Variant::STRING, "input_devices/pen_tablet/driver.Windows", PROPERTY_HINT_ENUM, "wintab,winink")); + GLOBAL_DEF_RST_NOVAL("input_devices/pen_tablet/driver.windows", ""); + ProjectSettings::get_singleton()->set_custom_property_info("input_devices/pen_tablet/driver.windows", PropertyInfo(Variant::STRING, "input_devices/pen_tablet/driver.windows", PROPERTY_HINT_ENUM, "wintab,winink")); } if (tablet_driver == "") { // specified in project.godot @@ -1969,7 +1967,7 @@ bool Main::start() { for (int i = 0; i < _doc_data_class_path_count; i++) { // Custom modules are always located by absolute path. String path = _doc_data_class_paths[i].path; - if (path.is_rel_path()) { + if (path.is_relative_path()) { path = doc_tool_path.plus_file(path); } String name = _doc_data_class_paths[i].name; @@ -2127,11 +2125,11 @@ bool Main::start() { if (!project_manager && !editor) { // game if (game_path != "" || script != "") { //autoload - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); //first pass, add the constants so they exist before any script is loaded - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (info.is_singleton) { for (int i = 0; i < ScriptServer::get_language_count(); i++) { @@ -2142,8 +2140,8 @@ bool Main::start() { //second pass, load into global constants List<Node *> to_add; - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); RES res = ResourceLoader::load(info.path); ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); diff --git a/main/main_timer_sync.cpp b/main/main_timer_sync.cpp index 0d172be65e..42023e5a2f 100644 --- a/main/main_timer_sync.cpp +++ b/main/main_timer_sync.cpp @@ -57,7 +57,7 @@ int MainTimerSync::get_average_physics_steps(double &p_min, double &p_max) { const double typical_lower = typical_physics_steps[i]; const double current_min = typical_lower / (i + 1); if (current_min > p_max) { - return i; // bail out of further restrictions would void the interval + return i; // bail out if further restrictions would void the interval } else if (current_min > p_min) { p_min = current_min; } @@ -105,6 +105,12 @@ MainFrameTime MainTimerSync::advance_core(double p_physics_step, int p_physics_t } } +#ifdef DEBUG_ENABLED + if (max_typical_steps < 0) { + WARN_PRINT_ONCE("`max_typical_steps` is negative. This could hint at an engine bug or system timer misconfiguration."); + } +#endif + // try to keep it consistent with previous iterations if (ret.physics_steps < min_typical_steps) { const int max_possible_steps = floor((time_accum)*p_physics_ticks_per_second + get_physics_jitter_fix()); @@ -124,6 +130,10 @@ MainFrameTime MainTimerSync::advance_core(double p_physics_step, int p_physics_t } } + if (ret.physics_steps < 0) { + ret.physics_steps = 0; + } + time_accum -= ret.physics_steps * p_physics_step; // keep track of accumulated step counts @@ -151,6 +161,9 @@ MainFrameTime MainTimerSync::advance_checked(double p_physics_step, int p_physic p_process_step = 1.0 / fixed_fps; } + float min_output_step = p_process_step / 8; + min_output_step = MAX(min_output_step, 1E-6); + // compensate for last deficit p_process_step += time_deficit; @@ -177,9 +190,37 @@ MainFrameTime MainTimerSync::advance_checked(double p_physics_step, int p_physic // last clamping: make sure time_accum is between 0 and p_physics_step for consistency between physics and process ret.clamp_process_step(process_minus_accum, process_minus_accum + p_physics_step); + // all the operations above may have turned ret.p_process_step negative or zero, keep a minimal value + if (ret.process_step < min_output_step) { + ret.process_step = min_output_step; + } + // restore time_accum time_accum = ret.process_step - process_minus_accum; + // forcing ret.process_step to be positive may trigger a violation of the + // promise that time_accum is between 0 and p_physics_step +#ifdef DEBUG_ENABLED + if (time_accum < -1E-7) { + WARN_PRINT_ONCE("Intermediate value of `time_accum` is negative. This could hint at an engine bug or system timer misconfiguration."); + } +#endif + + if (time_accum > p_physics_step) { + const int extra_physics_steps = floor(time_accum * p_physics_ticks_per_second); + time_accum -= extra_physics_steps * p_physics_step; + ret.physics_steps += extra_physics_steps; + } + +#ifdef DEBUG_ENABLED + if (time_accum < -1E-7) { + WARN_PRINT_ONCE("Final value of `time_accum` is negative. It should always be between 0 and `p_physics_step`. This hints at an engine bug."); + } + if (time_accum > p_physics_step + 1E-7) { + WARN_PRINT_ONCE("Final value of `time_accum` is larger than `p_physics_step`. It should always be between 0 and `p_physics_step`. This hints at an engine bug."); + } +#endif + // track deficit time_deficit = p_process_step - ret.process_step; diff --git a/methods.py b/methods.py index 86a802bf5f..50b413a0e6 100644 --- a/methods.py +++ b/methods.py @@ -319,7 +319,7 @@ def disable_module(self): self.disabled_modules.append(self.current_module) -def module_check_dependencies(self, module, dependencies): +def module_check_dependencies(self, module, dependencies, silent=False): """ Checks if module dependencies are enabled for a given module, and prints a warning if they aren't. @@ -333,11 +333,12 @@ def module_check_dependencies(self, module, dependencies): missing_deps.append(dep) if missing_deps != []: - print( - "Disabling '{}' module as the following dependencies are not satisfied: {}".format( - module, ", ".join(missing_deps) + if not silent: + print( + "Disabling '{}' module as the following dependencies are not satisfied: {}".format( + module, ", ".join(missing_deps) + ) ) - ) return False else: return True diff --git a/misc/dist/html/editor.html b/misc/dist/html/editor.html index 347c22adf8..2cae215951 100644 --- a/misc/dist/html/editor.html +++ b/misc/dist/html/editor.html @@ -1,5 +1,5 @@ <!DOCTYPE html> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" /> diff --git a/misc/dist/html/full-size.html b/misc/dist/html/full-size.html index 7afb6fdb6b..90e8167369 100644 --- a/misc/dist/html/full-size.html +++ b/misc/dist/html/full-size.html @@ -1,5 +1,5 @@ <!DOCTYPE html> -<html xmlns='http://www.w3.org/1999/xhtml' lang='' xml:lang=''> +<html xmlns='https://www.w3.org/1999/xhtml' lang='' xml:lang=''> <head> <meta charset='utf-8' /> <meta name='viewport' content='width=device-width, user-scalable=no' /> diff --git a/misc/dist/linux/org.godotengine.Godot.xml b/misc/dist/linux/org.godotengine.Godot.xml index d4452018c4..2f06bb3639 100644 --- a/misc/dist/linux/org.godotengine.Godot.xml +++ b/misc/dist/linux/org.godotengine.Godot.xml @@ -1,5 +1,5 @@ <?xml version="1.0"?> -<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"> +<mime-info xmlns="https://specifications.freedesktop.org/shared-mime-info-spec"> <mime-type type="application/x-godot-project"> <comment>Godot Engine project</comment> <sub-class-of type="text/plain"/> diff --git a/misc/dist/windows/modpath.pas b/misc/dist/windows/modpath.pas index ab09f18254..6d80d4201e 100644 --- a/misc/dist/windows/modpath.pas +++ b/misc/dist/windows/modpath.pas @@ -3,9 +3,9 @@ // Inno Setup Ver: 5.4.2 // Script Version: 1.4.2 // Author: Jared Breland <jbreland@legroom.net> -// Homepage: http://www.legroom.net/software +// Homepage: https://www.legroom.net/software // License: GNU Lesser General Public License (LGPL), version 3 -// http://www.gnu.org/licenses/lgpl.html +// https://www.gnu.org/licenses/lgpl.html // // Script Function: // Allow modification of environmental path directly from Inno Setup installers diff --git a/misc/hooks/canonicalize_filename.sh b/misc/hooks/canonicalize_filename.sh index 5fcae6ee70..fe66999d8c 100755 --- a/misc/hooks/canonicalize_filename.sh +++ b/misc/hooks/canonicalize_filename.sh @@ -16,7 +16,7 @@ # specified filename. This should reproduce the results of the GNU version of # readlink with the -f option. # -# Reference: http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac +# Reference: https://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac canonicalize_filename () { local target_file="$1" local physical_directory="" diff --git a/misc/scripts/black_format.sh b/misc/scripts/black_format.sh index f93e8cbc2a..2ad9a23832 100755 --- a/misc/scripts/black_format.sh +++ b/misc/scripts/black_format.sh @@ -15,7 +15,7 @@ PY_FILES=$(find \( -path "./.git" \ \) -print) black -l 120 $PY_FILES -git diff > patch.patch +git diff --color > patch.patch # If no patch has been generated all is OK, clean up, and exit. if [ ! -s patch.patch ] ; then diff --git a/misc/scripts/clang_format.sh b/misc/scripts/clang_format.sh index 63c66d41c3..bcd63aa73b 100755 --- a/misc/scripts/clang_format.sh +++ b/misc/scripts/clang_format.sh @@ -40,7 +40,7 @@ while IFS= read -rd '' f; do done done -git diff > patch.patch +git diff --color > patch.patch # If no patch has been generated all is OK, clean up, and exit. if [ ! -s patch.patch ] ; then diff --git a/misc/scripts/file_format.sh b/misc/scripts/file_format.sh index 795431cd28..0b49b175f2 100755 --- a/misc/scripts/file_format.sh +++ b/misc/scripts/file_format.sh @@ -42,7 +42,7 @@ while IFS= read -rd '' f; do perl -i -ple 's/\s*$//g' "$f" done -git diff > patch.patch +git diff --color > patch.patch # If no patch has been generated all is OK, clean up, and exit. if [ ! -s patch.patch ] ; then diff --git a/misc/scripts/make_icons.sh b/misc/scripts/make_icons.sh index b590f03d38..cbb9266055 100755 --- a/misc/scripts/make_icons.sh +++ b/misc/scripts/make_icons.sh @@ -20,7 +20,7 @@ zip godot-icons.zip icon*.png icotool -c -o godot-icon.ico icon{16,24,32,48,64,128,256}.png # icns for macOS -# Only some sizes: http://iconhandbook.co.uk/reference/chart/osx/ +# Only some sizes: https://iconhandbook.co.uk/reference/chart/osx/ png2icns godot-icon.icns icon{16,32,128,256,512,1024}.png rm -f icon*.png diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h index 5e102d8b05..01ac1e4836 100644 --- a/modules/bullet/rigid_body_bullet.h +++ b/modules/bullet/rigid_body_bullet.h @@ -301,7 +301,7 @@ public: void reload_axis_lock(); /// Doc: - /// https://web.archive.org/web/20180404091446/http://www.bulletphysics.org/mediawiki-1.5.8/index.php/Anti_tunneling_by_Motion_Clamping + /// https://web.archive.org/web/20180404091446/https://www.bulletphysics.org/mediawiki-1.5.8/index.php/Anti_tunneling_by_Motion_Clamping void set_continuous_collision_detection(bool p_enable); bool is_continuous_collision_detection_enabled() const; diff --git a/modules/bullet/space_bullet.h b/modules/bullet/space_bullet.h index 2070e0e633..cf8549030d 100644 --- a/modules/bullet/space_bullet.h +++ b/modules/bullet/space_bullet.h @@ -76,13 +76,13 @@ private: public: BulletPhysicsDirectSpaceState(SpaceBullet *p_space); - virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) override; - virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &r_closest_safe, real_t &r_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) override; + virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) override; + virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &r_closest_safe, real_t &r_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) override; /// Returns the list of contacts pairs in this order: Local contact, other body contact - virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; virtual Vector3 get_closest_point_to_object_volume(RID p_object, const Vector3 p_point) const override; }; diff --git a/modules/camera/camera_osx.mm b/modules/camera/camera_osx.mm index 4875eb578a..02f7287d1b 100644 --- a/modules/camera/camera_osx.mm +++ b/modules/camera/camera_osx.mm @@ -33,6 +33,7 @@ #include "camera_osx.h" #include "servers/camera/camera_feed.h" + #import <AVFoundation/AVFoundation.h> ////////////////////////////////////////////////////////////////////////// @@ -253,10 +254,25 @@ CameraFeedOSX::~CameraFeedOSX() { bool CameraFeedOSX::activate_feed() { if (capture_session) { - // already recording! + // Already recording! } else { - // start camera capture - capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + // Start camera capture, check permission. + if (@available(macOS 10.14, *)) { + AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + if (status == AVAuthorizationStatusAuthorized) { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } else if (status == AVAuthorizationStatusNotDetermined) { + // Request permission. + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo + completionHandler:^(BOOL granted) { + if (granted) { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } + }]; + } + } else { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } }; return true; diff --git a/modules/enet/doc_classes/ENetConnection.xml b/modules/enet/doc_classes/ENetConnection.xml index c2a85ffdf8..00469ab44c 100644 --- a/modules/enet/doc_classes/ENetConnection.xml +++ b/modules/enet/doc_classes/ENetConnection.xml @@ -40,6 +40,7 @@ <description> Sets the compression method used for network packets. These have different tradeoffs of compression speed versus bandwidth, you may need to test which one works best for your use case if you use compression at all. [b]Note:[/b] Most games' network design involve sending many small packets frequently (smaller than 4 KB each). If in doubt, it is recommended to keep the default compression algorithm as it works best on these small packets. + [b]Note:[/b] The compression mode must be set to the same value on both the server and all its clients. Clients will fail to connect if the compression mode set on the client differs from the one set on the server. </description> </method> <method name="connect_to_host"> @@ -155,7 +156,7 @@ ENet's built-in range encoding. Works well on small packets, but is not the most efficient algorithm on packets larger than 4 KB. </constant> <constant name="COMPRESS_FASTLZ" value="2" enum="CompressionMode"> - [url=http://fastlz.org/]FastLZ[/url] compression. This option uses less CPU resources compared to [constant COMPRESS_ZLIB], at the expense of using more bandwidth. + [url=https://fastlz.org/]FastLZ[/url] compression. This option uses less CPU resources compared to [constant COMPRESS_ZLIB], at the expense of using more bandwidth. </constant> <constant name="COMPRESS_ZLIB" value="3" enum="CompressionMode"> [url=https://www.zlib.net/]Zlib[/url] compression. This option uses less bandwidth compared to [constant COMPRESS_FASTLZ], at the expense of using more CPU resources. diff --git a/modules/enet/doc_classes/ENetMultiplayerPeer.xml b/modules/enet/doc_classes/ENetMultiplayerPeer.xml index 3a37b396a4..43e1d40e47 100644 --- a/modules/enet/doc_classes/ENetMultiplayerPeer.xml +++ b/modules/enet/doc_classes/ENetMultiplayerPeer.xml @@ -4,7 +4,7 @@ A MultiplayerPeer implementation using the [url=http://enet.bespin.org/index.html]ENet[/url] library. </brief_description> <description> - A MultiplayerPeer implementation that should be passed to [member MultiplayerAPI.network_peer] after being initialized as either a client, server, or mesh. Events can then be handled by connecting to [MultiplayerAPI] signals. See [ENetConnection] for more information on the ENet library wrapper. + A MultiplayerPeer implementation that should be passed to [member MultiplayerAPI.multiplayer_peer] after being initialized as either a client, server, or mesh. Events can then be handled by connecting to [MultiplayerAPI] signals. See [ENetConnection] for more information on the ENet library wrapper. [b]Note:[/b] ENet only uses UDP, not TCP. When forwarding the server port to make your server accessible on the public Internet, you only need to forward the server port in UDP. You can use the [UPNP] class to try to forward the server port automatically when starting the server. </description> <tutorials> @@ -44,7 +44,7 @@ <return type="int" enum="Error" /> <argument index="0" name="unique_id" type="int" /> <description> - Initialize this [MultiplayerPeer] in mesh mode. The provided [code]unique_id[/code] will be used as the local peer network unique ID once assigned as the [member MultiplayerAPI.network_peer]. In the mesh configuration you will need to set up each new peer manually using [ENetConnection] before calling [method add_mesh_peer]. While this technique is more advanced, it allows for better control over the connection process (e.g. when dealing with NAT punch-through) and for better distribution of the network load (which would otherwise be more taxing on the server). + Initialize this [MultiplayerPeer] in mesh mode. The provided [code]unique_id[/code] will be used as the local peer network unique ID once assigned as the [member MultiplayerAPI.multiplayer_peer]. In the mesh configuration you will need to set up each new peer manually using [ENetConnection] before calling [method add_mesh_peer]. While this technique is more advanced, it allows for better control over the connection process (e.g. when dealing with NAT punch-through) and for better distribution of the network load (which would otherwise be more taxing on the server). </description> </method> <method name="create_server"> @@ -81,7 +81,7 @@ <member name="server_relay" type="bool" setter="set_server_relay_enabled" getter="is_server_relay_enabled" default="true"> Enable or disable the server feature that notifies clients of other peers' connection/disconnection, and relays messages between them. When this option is [code]false[/code], clients won't be automatically notified of other peers and won't be able to send them packets through the server. </member> - <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="MultiplayerPeer.TransferMode" default="2" /> + <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="TransferMode" default="2" /> </members> <constants> </constants> diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp index 38ca38385c..afd31207f6 100644 --- a/modules/enet/enet_multiplayer_peer.cpp +++ b/modules/enet/enet_multiplayer_peer.cpp @@ -41,11 +41,11 @@ int ENetMultiplayerPeer::get_transfer_channel() const { return transfer_channel; } -void ENetMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { +void ENetMultiplayerPeer::set_transfer_mode(Multiplayer::TransferMode p_mode) { transfer_mode = p_mode; } -MultiplayerPeer::TransferMode ENetMultiplayerPeer::get_transfer_mode() const { +Multiplayer::TransferMode ENetMultiplayerPeer::get_transfer_mode() const { return transfer_mode; } @@ -393,7 +393,9 @@ bool ENetMultiplayerPeer::is_server() const { } void ENetMultiplayerPeer::close_connection(uint32_t wait_usec) { - ERR_FAIL_COND_MSG(!_is_active(), "The multiplayer instance isn't currently active."); + if (!_is_active()) { + return; + } _pop_current_packet(); @@ -453,15 +455,15 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size channel = SYSCH_MAX + transfer_channel - 1; } else { switch (transfer_mode) { - case TRANSFER_MODE_UNRELIABLE: { + case Multiplayer::TRANSFER_MODE_UNRELIABLE: { packet_flags = ENET_PACKET_FLAG_UNSEQUENCED; channel = SYSCH_UNRELIABLE; } break; - case TRANSFER_MODE_UNRELIABLE_ORDERED: { + case Multiplayer::TRANSFER_MODE_ORDERED: { packet_flags = 0; channel = SYSCH_UNRELIABLE; } break; - case TRANSFER_MODE_RELIABLE: { + case Multiplayer::TRANSFER_MODE_RELIABLE: { packet_flags = ENET_PACKET_FLAG_RELIABLE; channel = SYSCH_RELIABLE; } break; diff --git a/modules/enet/enet_multiplayer_peer.h b/modules/enet/enet_multiplayer_peer.h index 78e280db7c..b5316b8292 100644 --- a/modules/enet/enet_multiplayer_peer.h +++ b/modules/enet/enet_multiplayer_peer.h @@ -32,7 +32,7 @@ #define NETWORKED_MULTIPLAYER_ENET_H #include "core/crypto/crypto.h" -#include "core/io/multiplayer_peer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "enet_connection.h" #include <enet/enet.h> @@ -66,7 +66,7 @@ private: int target_peer = 0; int transfer_channel = 0; - TransferMode transfer_mode = TRANSFER_MODE_RELIABLE; + Multiplayer::TransferMode transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; bool refuse_connections = false; bool server_relay = true; @@ -104,8 +104,8 @@ public: virtual void set_transfer_channel(int p_channel) override; virtual int get_transfer_channel() const override; - virtual void set_transfer_mode(TransferMode p_mode) override; - virtual TransferMode get_transfer_mode() const override; + virtual void set_transfer_mode(Multiplayer::TransferMode p_mode) override; + virtual Multiplayer::TransferMode get_transfer_mode() const override; virtual void set_target_peer(int p_peer) override; virtual int get_packet_peer() const override; diff --git a/modules/fbx/fbx_parser/FBXParser.cpp b/modules/fbx/fbx_parser/FBXParser.cpp index a92b23f4ee..d9ef025a16 100644 --- a/modules/fbx/fbx_parser/FBXParser.cpp +++ b/modules/fbx/fbx_parser/FBXParser.cpp @@ -575,7 +575,7 @@ void ReadBinaryDataArray(char type, uint32_t count, const char *&data, const cha std::copy(data, end, buff.begin()); } else if (encmode == 1) { // zlib/deflate, next comes ZIP head (0x78 0x01) - // see http://www.ietf.org/rfc/rfc1950.txt + // see https://www.ietf.org/rfc/rfc1950.txt z_stream zstream; zstream.opaque = Z_NULL; diff --git a/modules/gdnative/SCsub b/modules/gdnative/SCsub index 45354ce692..21ee39f3ed 100644 --- a/modules/gdnative/SCsub +++ b/modules/gdnative/SCsub @@ -17,7 +17,6 @@ env_gdnative.Prepend(CPPPATH=["#modules/gdnative/include/"]) Export("env_gdnative") SConscript("net/SCsub") -SConscript("xr/SCsub") SConscript("pluginscript/SCsub") SConscript("videodecoder/SCsub") SConscript("text/SCsub") diff --git a/modules/gdnative/config.py b/modules/gdnative/config.py index fd860e9763..fa985501b5 100644 --- a/modules/gdnative/config.py +++ b/modules/gdnative/config.py @@ -8,7 +8,6 @@ def configure(env): def get_doc_classes(): return [ - "XRInterfaceGDNative", "GDNative", "GDNativeLibrary", "MultiplayerPeerGDNative", diff --git a/modules/gdnative/doc_classes/GDNativeLibrary.xml b/modules/gdnative/doc_classes/GDNativeLibrary.xml index f84d4e60f3..94eae3cd06 100644 --- a/modules/gdnative/doc_classes/GDNativeLibrary.xml +++ b/modules/gdnative/doc_classes/GDNativeLibrary.xml @@ -4,7 +4,7 @@ An external library containing functions or script classes to use in Godot. </brief_description> <description> - A GDNative library can implement [NativeScript]s, global functions to call with the [GDNative] class, or low-level engine extensions through interfaces such as [XRInterfaceGDNative]. The library must be compiled for each platform and architecture that the project will run on. + A GDNative library can implement [NativeScript]s, global functions to call with the [GDNative] class, or low-level engine extensions through interfaces such as XRInterfaceGDNative. The library must be compiled for each platform and architecture that the project will run on. </description> <tutorials> <link title="GDNative C example">https://docs.godotengine.org/en/latest/tutorials/plugins/gdnative/gdnative-c-example.html</link> diff --git a/modules/gdnative/doc_classes/XRInterfaceGDNative.xml b/modules/gdnative/doc_classes/XRInterfaceGDNative.xml deleted file mode 100644 index 13de815793..0000000000 --- a/modules/gdnative/doc_classes/XRInterfaceGDNative.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="XRInterfaceGDNative" inherits="XRInterface" version="4.0"> - <brief_description> - GDNative wrapper for an XR interface. - </brief_description> - <description> - This is a wrapper class for GDNative implementations of the XR interface. To use a GDNative XR interface, simply instantiate this object and set your GDNative library containing the XR interface implementation. - </description> - <tutorials> - </tutorials> - <methods> - </methods> - <constants> - </constants> -</class> diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 8c65447e5d..66d2dc267d 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -5048,169 +5048,6 @@ ] }, { - "name": "xr", - "type": "XR", - "version": { - "major": 1, - "minor": 1 - }, - "next": null, - "api": [ - { - "name": "godot_xr_register_interface", - "return_type": "void", - "arguments": [ - [ - "const godot_xr_interface_gdnative *", - "p_interface" - ] - ] - }, - { - "name": "godot_xr_get_worldscale", - "return_type": "godot_real_t", - "arguments": [] - }, - { - "name": "godot_xr_get_reference_frame", - "return_type": "godot_transform3d", - "arguments": [] - }, - { - "name": "godot_xr_blit", - "return_type": "void", - "arguments": [ - [ - "godot_int", - "p_eye" - ], - [ - "godot_rid *", - "p_render_target" - ], - [ - "godot_rect2 *", - "p_screen_rect" - ] - ] - }, - { - "name": "godot_xr_get_texid", - "return_type": "godot_int", - "arguments": [ - [ - "godot_rid *", - "p_render_target" - ] - ] - }, - { - "name": "godot_xr_add_controller", - "return_type": "godot_int", - "arguments": [ - [ - "char *", - "p_device_name" - ], - [ - "godot_int", - "p_hand" - ], - [ - "godot_bool", - "p_tracks_orientation" - ], - [ - "godot_bool", - "p_tracks_position" - ] - ] - }, - { - "name": "godot_xr_remove_controller", - "return_type": "void", - "arguments": [ - [ - "godot_int", - "p_controller_id" - ] - ] - }, - { - "name": "godot_xr_set_controller_transform", - "return_type": "void", - "arguments": [ - [ - "godot_int", - "p_controller_id" - ], - [ - "godot_transform3d *", - "p_transform" - ], - [ - "godot_bool", - "p_tracks_orientation" - ], - [ - "godot_bool", - "p_tracks_position" - ] - ] - }, - { - "name": "godot_xr_set_controller_button", - "return_type": "void", - "arguments": [ - [ - "godot_int", - "p_controller_id" - ], - [ - "godot_int", - "p_button" - ], - [ - "godot_bool", - "p_is_pressed" - ] - ] - }, - { - "name": "godot_xr_set_controller_axis", - "return_type": "void", - "arguments": [ - [ - "godot_int", - "p_controller_id" - ], - [ - "godot_int", - "p_exis" - ], - [ - "godot_real_t", - "p_value" - ], - [ - "godot_bool", - "p_can_be_negative" - ] - ] - }, - { - "name": "godot_xr_get_controller_rumble", - "return_type": "godot_real_t", - "arguments": [ - [ - "godot_int", - "p_controller_id" - ] - ] - } - ] - }, - { "name": "videodecoder", "type": "VIDEODECODER", "version": { @@ -5517,7 +5354,7 @@ }, { "name": "godot_glyph_get_advance", - "return_type": "godot_float", + "return_type": "godot_real_t", "arguments": [ [ "const godot_glyph *", @@ -5534,7 +5371,7 @@ "p_self" ], [ - "godot_float", + "godot_real_t", "p_advance" ] ] diff --git a/modules/gdnative/gdnative_builders.py b/modules/gdnative/gdnative_builders.py index d03298d7a9..181fd71b82 100644 --- a/modules/gdnative/gdnative_builders.py +++ b/modules/gdnative/gdnative_builders.py @@ -19,7 +19,6 @@ def _build_gdnative_api_struct_header(api): "", "#include <gdnative/gdnative.h>", "#include <android/godot_android.h>", - "#include <xr/godot_xr.h>", "#include <nativescript/godot_nativescript.h>", "#include <net/godot_net.h>", "#include <pluginscript/godot_pluginscript.h>", diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h index c97f5f0389..09eac2492f 100644 --- a/modules/gdnative/include/nativescript/godot_nativescript.h +++ b/modules/gdnative/include/nativescript/godot_nativescript.h @@ -39,12 +39,8 @@ extern "C" { typedef enum { GODOT_METHOD_RPC_MODE_DISABLED, - GODOT_METHOD_RPC_MODE_REMOTE, - GODOT_METHOD_RPC_MODE_MASTER, - GODOT_METHOD_RPC_MODE_PUPPET, - GODOT_METHOD_RPC_MODE_REMOTESYNC, - GODOT_METHOD_RPC_MODE_MASTERSYNC, - GODOT_METHOD_RPC_MODE_PUPPETSYNC, + GODOT_METHOD_RPC_MODE_ANY, + GODOT_METHOD_RPC_MODE_AUTHORITY, } godot_nativescript_method_rpc_mode; typedef enum { diff --git a/modules/gdnative/include/text/godot_text.h b/modules/gdnative/include/text/godot_text.h index 6428f2f149..940cfd11f8 100644 --- a/modules/gdnative/include/text/godot_text.h +++ b/modules/gdnative/include/text/godot_text.h @@ -60,68 +60,109 @@ typedef struct { typedef struct { godot_gdnative_api_version version; + void *(*constructor)(godot_object *); void (*destructor)(void *); + godot_string (*get_name)(const void *); godot_bool (*has_feature)(const void *, godot_int); + + void (*free)(void *, godot_rid *); + bool (*has)(void *, godot_rid *); + bool (*load_support_data)(void *, const godot_string *); godot_string (*get_support_data_filename)(const void *); godot_string (*get_support_data_info)(const void *); bool (*save_support_data)(void *, const godot_string *); + bool (*is_locale_right_to_left)(void *, const godot_string *); - void (*free)(void *, godot_rid *); - bool (*has)(void *, godot_rid *); - godot_rid (*create_font_system)(void *, const godot_string *, int); - godot_rid (*create_font_resource)(void *, const godot_string *, int); - godot_rid (*create_font_memory)(void *, const uint8_t *, size_t, godot_string *, int); - godot_rid (*create_font_bitmap)(void *, float, float, int); - void (*font_bitmap_add_texture)(void *, godot_rid *, const godot_object *); - void (*font_bitmap_add_char)(void *, godot_rid *, char32_t, int, const godot_rect2 *, const godot_vector2 *, float); - void (*font_bitmap_add_kerning_pair)(void *, godot_rid *, char32_t, char32_t, int); - float (*font_get_height)(void *, godot_rid *, int); - float (*font_get_ascent)(void *, godot_rid *, int); - float (*font_get_descent)(void *, godot_rid *, int); - float (*font_get_underline_position)(void *, godot_rid *, int); - float (*font_get_underline_thickness)(void *, godot_rid *, int); - int (*font_get_spacing_space)(void *, godot_rid *); - void (*font_set_spacing_space)(void *, godot_rid *, int); - int (*font_get_spacing_glyph)(void *, godot_rid *); - void (*font_set_spacing_glyph)(void *, godot_rid *, int); + int32_t (*name_to_tag)(const void *, const godot_string *); + godot_string (*tag_to_name)(const void *, int32_t); + + godot_rid (*create_font)(void *); + void (*font_set_data)(void *, godot_rid *, const godot_packed_byte_array *); + void (*font_set_data_ptr)(void *, godot_rid *, const uint8_t *, size_t); void (*font_set_antialiased)(void *, godot_rid *, bool); - bool (*font_get_antialiased)(void *, godot_rid *); - godot_dictionary (*font_get_feature_list)(void *, godot_rid *); - godot_dictionary (*font_get_variation_list)(void *, godot_rid *); - void (*font_set_variation)(void *, godot_rid *, const godot_string *, double); - double (*font_get_variation)(void *, godot_rid *, const godot_string *); - void (*font_set_distance_field_hint)(void *, godot_rid *, bool); - bool (*font_get_distance_field_hint)(void *, godot_rid *); - void (*font_set_hinting)(void *, godot_rid *, godot_int); - godot_int (*font_get_hinting)(void *, godot_rid *); + bool (*font_is_antialiased)(const void *, godot_rid *); + void (*font_set_multichannel_signed_distance_field)(void *, godot_rid *, bool); + bool (*font_is_multichannel_signed_distance_field)(const void *, godot_rid *); + void (*font_set_msdf_pixel_range)(void *, godot_rid *, godot_int); + godot_int (*font_get_msdf_pixel_range)(const void *, godot_rid *); + void (*font_set_msdf_size)(void *, godot_rid *, godot_int); + godot_int (*font_get_msdf_size)(const void *, godot_rid *); + void (*font_set_fixed_size)(void *, godot_rid *, godot_int); + godot_int (*font_get_fixed_size)(const void *, godot_rid *); void (*font_set_force_autohinter)(void *, godot_rid *, bool); - bool (*font_get_force_autohinter)(void *, godot_rid *); - bool (*font_has_char)(void *, godot_rid *, char32_t); - godot_string (*font_get_supported_chars)(void *, godot_rid *); - bool (*font_has_outline)(void *, godot_rid *); - int (*font_get_base_size)(void *, godot_rid *); - bool (*font_is_language_supported)(void *, godot_rid *, const godot_string *); + bool (*font_is_force_autohinter)(const void *, godot_rid *); + void (*font_set_hinting)(void *, godot_rid *, godot_int); + godot_int (*font_get_hinting)(const void *, godot_rid *); + void (*font_set_variation_coordinates)(void *, godot_rid *, const godot_dictionary *); + godot_dictionary (*font_get_variation_coordinates)(const void *, godot_rid *); + void (*font_set_oversampling)(void *, godot_rid *, godot_real_t); + godot_real_t (*font_get_oversampling)(const void *, godot_rid *); + godot_array (*font_get_size_cache_list)(const void *, godot_rid *); + void (*font_clear_size_cache)(void *, godot_rid *); + void (*font_remove_size_cache)(void *, godot_rid *, const godot_vector2i *); + void (*font_set_ascent)(void *, godot_rid *, godot_int, godot_real_t); + godot_real_t (*font_get_ascent)(const void *, godot_rid *, godot_int); + void (*font_set_descent)(void *, godot_rid *, godot_int, godot_real_t); + godot_real_t (*font_get_descent)(const void *, godot_rid *, godot_int); + void (*font_set_underline_position)(void *, godot_rid *, godot_int, godot_real_t); + godot_real_t (*font_get_underline_position)(const void *, godot_rid *, godot_int); + void (*font_set_underline_thickness)(void *, godot_rid *, godot_int, godot_real_t); + godot_real_t (*font_get_underline_thickness)(const void *, godot_rid *, godot_int); + void (*font_set_scale)(void *, godot_rid *, godot_int, godot_real_t); + godot_real_t (*font_get_scale)(const void *, godot_rid *, godot_int); + void (*font_set_spacing)(void *, godot_rid *, godot_int, godot_int, godot_int); + godot_int (*font_get_spacing)(const void *, godot_rid *, godot_int, godot_int); + godot_int (*font_get_texture_count)(const void *, godot_rid *, const godot_vector2i *); + void (*font_clear_textures)(void *, godot_rid *, const godot_vector2i *); + void (*font_remove_texture)(void *, godot_rid *, const godot_vector2i *, godot_int); + void (*font_set_texture_image)(void *, godot_rid *, const godot_vector2i *, godot_int, const godot_object *); + godot_object *(*font_get_texture_image)(const void *, godot_rid *, const godot_vector2i *, godot_int); + void (*font_set_texture_offsets)(void *, godot_rid *, const godot_vector2i *, godot_int, const godot_packed_int32_array *); + godot_packed_int32_array (*font_get_texture_offsets)(const void *, godot_rid *, const godot_vector2i *, godot_int); + godot_array (*font_get_glyph_list)(const void *, godot_rid *, const godot_vector2i *); + void (*font_clear_glyphs)(void *, godot_rid *, const godot_vector2i *); + void (*font_remove_glyph)(void *, godot_rid *, const godot_vector2i *, int32_t); + godot_vector2 (*font_get_glyph_advance)(const void *, godot_rid *, godot_int, int32_t); + void (*font_set_glyph_advance)(void *, godot_rid *, godot_int, int32_t, const godot_vector2 *); + godot_vector2 (*font_get_glyph_offset)(const void *, godot_rid *, const godot_vector2i *, int32_t); + void (*font_set_glyph_offset)(void *, godot_rid *, const godot_vector2i *, int32_t, const godot_vector2 *); + godot_vector2 (*font_get_glyph_size)(const void *, godot_rid *, const godot_vector2i *, int32_t); + void (*font_set_glyph_size)(void *, godot_rid *, const godot_vector2i *, int32_t, const godot_vector2 *); + godot_rect2 (*font_get_glyph_uv_rect)(const void *, godot_rid *, const godot_vector2i *, int32_t); + void (*font_set_glyph_uv_rect)(void *, godot_rid *, const godot_vector2i *, int32_t, const godot_rect2 *); + godot_int (*font_get_glyph_texture_idx)(const void *, godot_rid *, const godot_vector2i *, int32_t); + void (*font_set_glyph_texture_idx)(void *, godot_rid *, const godot_vector2i *, int32_t, godot_int); + bool (*font_get_glyph_contours)(const void *, godot_rid *, godot_int, int32_t, godot_packed_vector3_array *, godot_packed_int32_array *, bool *); + godot_array (*font_get_kerning_list)(const void *, godot_rid *, godot_int); + void (*font_clear_kerning_map)(void *, godot_rid *, godot_int); + void (*font_remove_kerning)(void *, godot_rid *, godot_int, const godot_vector2i *); + void (*font_set_kerning)(void *, godot_rid *, godot_int, const godot_vector2i *, const godot_vector2 *); + godot_vector2 (*font_get_kerning)(const void *, godot_rid *, godot_int, const godot_vector2i *); + int32_t (*font_get_glyph_index)(const void *, godot_rid *, godot_int, char32_t, char32_t); + bool (*font_has_char)(const void *, godot_rid *, char32_t); + godot_string (*font_get_supported_chars)(const void *, godot_rid *); + void (*font_render_range)(void *, godot_rid *, const godot_vector2i *, char32_t, char32_t); + void (*font_render_glyph)(void *, godot_rid *, const godot_vector2i *, int32_t); + void (*font_draw_glyph)(const void *, godot_rid *, godot_rid *, godot_int, const godot_vector2 *, int32_t, const godot_color *); + void (*font_draw_glyph_outline)(const void *, godot_rid *, godot_rid *, godot_int, godot_int, const godot_vector2 *, int32_t, const godot_color *); + bool (*font_is_language_supported)(const void *, godot_rid *, const godot_string *); void (*font_set_language_support_override)(void *, godot_rid *, const godot_string *, bool); - bool (*font_get_language_support_override)(void *, godot_rid *, const godot_string *); + bool (*font_get_language_support_override)(const void *, godot_rid *, const godot_string *); void (*font_remove_language_support_override)(void *, godot_rid *, const godot_string *); - godot_packed_string_array (*font_get_language_support_overrides)(void *, godot_rid *); - bool (*font_is_script_supported)(void *, godot_rid *, const godot_string *); + godot_packed_string_array (*font_get_language_support_overrides)(const void *, godot_rid *); + bool (*font_is_script_supported)(const void *, godot_rid *, const godot_string *); void (*font_set_script_support_override)(void *, godot_rid *, const godot_string *, bool); - bool (*font_get_script_support_override)(void *, godot_rid *, const godot_string *); + bool (*font_get_script_support_override)(const void *, godot_rid *, const godot_string *); void (*font_remove_script_support_override)(void *, godot_rid *, const godot_string *); - godot_packed_string_array (*font_get_script_support_overrides)(void *, godot_rid *); - uint32_t (*font_get_glyph_index)(void *, godot_rid *, char32_t, char32_t); - godot_vector2 (*font_get_glyph_advance)(void *, godot_rid *, uint32_t, int); - godot_vector2 (*font_get_glyph_kerning)(void *, godot_rid *, uint32_t, uint32_t, int); - godot_vector2 (*font_draw_glyph)(void *, godot_rid *, godot_rid *, int, const godot_vector2 *, uint32_t, const godot_color *); - godot_vector2 (*font_draw_glyph_outline)(void *, godot_rid *, godot_rid *, int, int, const godot_vector2 *, uint32_t, const godot_color *); - bool (*font_get_glyph_contours)(void *, godot_rid *, int, uint32_t, godot_packed_vector3_array *, godot_packed_int32_array *, bool *); - float (*font_get_oversampling)(void *); - void (*font_set_oversampling)(void *, float); - godot_packed_string_array (*get_system_fonts)(void *); + godot_packed_string_array (*font_get_script_support_overrides)(const void *, godot_rid *); + godot_dictionary (*font_supported_feature_list)(const void *, godot_rid *); + godot_dictionary (*font_supported_variation_list)(const void *, godot_rid *); + godot_real_t (*font_get_global_oversampling)(const void *); + void (*font_set_global_oversampling)(void *, godot_real_t); + godot_rid (*create_shaped_text)(void *, godot_int, godot_int); void (*shaped_text_clear)(void *, godot_rid *); void (*shaped_text_set_direction)(void *, godot_rid *, godot_int); @@ -138,27 +179,28 @@ typedef struct { bool (*shaped_text_resize_object)(void *, godot_rid *, const godot_variant *, const godot_vector2 *, godot_int); godot_rid (*shaped_text_substr)(void *, godot_rid *, godot_int, godot_int); godot_rid (*shaped_text_get_parent)(void *, godot_rid *); - float (*shaped_text_fit_to_width)(void *, godot_rid *, float, uint8_t); - float (*shaped_text_tab_align)(void *, godot_rid *, godot_packed_float32_array *); + godot_real_t (*shaped_text_fit_to_width)(void *, godot_rid *, godot_real_t, uint8_t); + godot_real_t (*shaped_text_tab_align)(void *, godot_rid *, godot_packed_float32_array *); bool (*shaped_text_shape)(void *, godot_rid *); bool (*shaped_text_update_breaks)(void *, godot_rid *); bool (*shaped_text_update_justification_ops)(void *, godot_rid *); - void (*shaped_text_overrun_trim_to_width)(void *, godot_rid *, float, uint8_t); + void (*shaped_text_overrun_trim_to_width)(void *, godot_rid *, godot_real_t, uint8_t); bool (*shaped_text_is_ready)(void *, godot_rid *); godot_packed_glyph_array (*shaped_text_get_glyphs)(void *, godot_rid *); godot_vector2i (*shaped_text_get_range)(void *, godot_rid *); godot_packed_glyph_array (*shaped_text_sort_logical)(void *, godot_rid *); godot_packed_vector2i_array (*shaped_text_get_line_breaks_adv)(void *, godot_rid *, godot_packed_float32_array *, int, bool, uint8_t); - godot_packed_vector2i_array (*shaped_text_get_line_breaks)(void *, godot_rid *, float, int, uint8_t); + godot_packed_vector2i_array (*shaped_text_get_line_breaks)(void *, godot_rid *, godot_real_t, int, uint8_t); godot_packed_vector2i_array (*shaped_text_get_word_breaks)(void *, godot_rid *, int); godot_array (*shaped_text_get_objects)(void *, godot_rid *); godot_rect2 (*shaped_text_get_object_rect)(void *, godot_rid *, const godot_variant *); godot_vector2 (*shaped_text_get_size)(void *, godot_rid *); - float (*shaped_text_get_ascent)(void *, godot_rid *); - float (*shaped_text_get_descent)(void *, godot_rid *); - float (*shaped_text_get_width)(void *, godot_rid *); - float (*shaped_text_get_underline_position)(void *, godot_rid *); - float (*shaped_text_get_underline_thickness)(void *, godot_rid *); + godot_real_t (*shaped_text_get_ascent)(void *, godot_rid *); + godot_real_t (*shaped_text_get_descent)(void *, godot_rid *); + godot_real_t (*shaped_text_get_width)(void *, godot_rid *); + godot_real_t (*shaped_text_get_underline_position)(void *, godot_rid *); + godot_real_t (*shaped_text_get_underline_thickness)(void *, godot_rid *); + godot_string (*format_number)(void *, const godot_string *, const godot_string *); godot_string (*parse_number)(void *, const godot_string *, const godot_string *); godot_string (*percent_sign)(void *, const godot_string *); @@ -185,8 +227,8 @@ void GDAPI godot_glyph_set_flags(godot_glyph *p_self, godot_int p_flags); godot_vector2 GDAPI godot_glyph_get_offset(const godot_glyph *p_self); void GDAPI godot_glyph_set_offset(godot_glyph *p_self, const godot_vector2 *p_offset); -godot_float GDAPI godot_glyph_get_advance(const godot_glyph *p_self); -void GDAPI godot_glyph_set_advance(godot_glyph *p_self, godot_float p_advance); +godot_real_t GDAPI godot_glyph_get_advance(const godot_glyph *p_self); +void GDAPI godot_glyph_set_advance(godot_glyph *p_self, godot_real_t p_advance); godot_rid GDAPI godot_glyph_get_font(const godot_glyph *p_self); void GDAPI godot_glyph_set_font(godot_glyph *p_self, godot_rid *p_font); diff --git a/modules/gdnative/include/xr/godot_xr.h b/modules/gdnative/include/xr/godot_xr.h deleted file mode 100644 index 53cb830cbb..0000000000 --- a/modules/gdnative/include/xr/godot_xr.h +++ /dev/null @@ -1,98 +0,0 @@ -/*************************************************************************/ -/* godot_xr.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 GODOT_NATIVEXR_H -#define GODOT_NATIVEXR_H - -#include <gdnative/gdnative.h> - -#ifdef __cplusplus -extern "C" { -#endif - -// For future versions of the API we should only add new functions at the end of the structure and use the -// version info to detect whether a call is available - -// Use these to populate version in your plugin -#define GODOTVR_API_MAJOR 4 -#define GODOTVR_API_MINOR 0 - -typedef struct { - godot_gdnative_api_version version; /* version of our API */ - void *(*constructor)(godot_object *); - void (*destructor)(void *); - godot_string (*get_name)(const void *); - godot_int (*get_capabilities)(const void *); - godot_bool (*get_anchor_detection_is_enabled)(const void *); - void (*set_anchor_detection_is_enabled)(void *, godot_bool); - godot_int (*get_view_count)(const void *); - godot_bool (*is_initialized)(const void *); - godot_bool (*initialize)(void *); - void (*uninitialize)(void *); - godot_vector2 (*get_render_targetsize)(const void *); - - godot_transform3d (*get_camera_transform)(void *); - godot_transform3d (*get_transform_for_view)(void *, godot_int, godot_transform3d *); - void (*fill_projection_for_view)(void *, godot_real_t *, godot_int, godot_real_t, godot_real_t, godot_real_t); - void (*commit_views)(void *, godot_rid *, godot_rect2 *); - - void (*process)(void *); - void (*notification)(void *, godot_int); - godot_int (*get_camera_feed_id)(void *); - - // possibly deprecate but adding/keeping as a reminder these are in Godot 3 - void (*commit_for_eye)(void *, godot_int, godot_rid *, godot_rect2 *); - godot_int (*get_external_texture_for_eye)(void *, godot_int); - godot_int (*get_external_depth_for_eye)(void *, godot_int); -} godot_xr_interface_gdnative; - -void GDAPI godot_xr_register_interface(const godot_xr_interface_gdnative *p_interface); - -// helper functions to access XRServer data -godot_real_t GDAPI godot_xr_get_worldscale(); -godot_transform3d GDAPI godot_xr_get_reference_frame(); - -// helper functions for rendering -void GDAPI godot_xr_blit(godot_int p_eye, godot_rid *p_render_target, godot_rect2 *p_rect); -godot_int GDAPI godot_xr_get_texid(godot_rid *p_render_target); - -// helper functions for updating XR controllers -godot_int GDAPI godot_xr_add_controller(char *p_device_name, godot_int p_hand, godot_bool p_tracks_orientation, godot_bool p_tracks_position); -void GDAPI godot_xr_remove_controller(godot_int p_controller_id); -void GDAPI godot_xr_set_controller_transform(godot_int p_controller_id, godot_transform3d *p_transform, godot_bool p_tracks_orientation, godot_bool p_tracks_position); -void GDAPI godot_xr_set_controller_button(godot_int p_controller_id, godot_int p_button, godot_bool p_is_pressed); -void GDAPI godot_xr_set_controller_axis(godot_int p_controller_id, godot_int p_axis, godot_real_t p_value, godot_bool p_can_be_negative); -godot_real_t GDAPI godot_xr_get_controller_rumble(godot_int p_controller_id); - -#ifdef __cplusplus -} -#endif - -#endif /* !GODOT_NATIVEXR_H */ diff --git a/modules/gdnative/nativescript/godot_nativescript.cpp b/modules/gdnative/nativescript/godot_nativescript.cpp index 70b14836bf..dadd1a9d10 100644 --- a/modules/gdnative/nativescript/godot_nativescript.cpp +++ b/modules/gdnative/nativescript/godot_nativescript.cpp @@ -127,9 +127,9 @@ void GDAPI godot_nativescript_register_method(void *p_gdnative_handle, const cha E->get().methods.insert(p_function_name, method); if (p_attr.rpc_type != GODOT_METHOD_RPC_MODE_DISABLED) { - MultiplayerAPI::RPCConfig nd; + Multiplayer::RPCConfig nd; nd.name = String(p_name); - nd.rpc_mode = MultiplayerAPI::RPCMode(p_attr.rpc_type); + nd.rpc_mode = Multiplayer::RPCMode(p_attr.rpc_type); E->get().rpc_methods.push_back(nd); } } diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index 26d3aed702..92ba9bd452 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -431,9 +431,9 @@ void NativeScript::get_script_property_list(List<PropertyInfo> *p_list) const { } } -const Vector<MultiplayerAPI::RPCConfig> NativeScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> NativeScript::get_rpc_methods() const { NativeScriptDesc *script_data = get_script_desc(); - ERR_FAIL_COND_V(!script_data, Vector<MultiplayerAPI::RPCConfig>()); + ERR_FAIL_COND_V(!script_data, Vector<Multiplayer::RPCConfig>()); return script_data->rpc_methods; } @@ -828,7 +828,7 @@ Ref<Script> NativeScriptInstance::get_script() const { return script; } -const Vector<MultiplayerAPI::RPCConfig> NativeScriptInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> NativeScriptInstance::get_rpc_methods() const { return script->get_rpc_methods(); } diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h index 777a878660..a7647e8c59 100644 --- a/modules/gdnative/nativescript/nativescript.h +++ b/modules/gdnative/nativescript/nativescript.h @@ -71,7 +71,7 @@ struct NativeScriptDesc { }; Map<StringName, Method> methods; - Vector<MultiplayerAPI::RPCConfig> rpc_methods; + Vector<Multiplayer::RPCConfig> rpc_methods; OrderedHashMap<StringName, Property> properties; Map<StringName, Signal> signals_; // QtCreator doesn't like the name signals StringName base; @@ -175,7 +175,7 @@ public: virtual void get_script_method_list(List<MethodInfo> *p_list) const override; virtual void get_script_property_list(List<PropertyInfo> *p_list) const override; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; String get_class_documentation() const; String get_method_documentation(const StringName &p_method) const; @@ -213,7 +213,7 @@ public: String to_string(bool *r_valid); virtual Ref<Script> get_script() const; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const; virtual ScriptLanguage *get_language(); diff --git a/modules/gdnative/net/multiplayer_peer_gdnative.cpp b/modules/gdnative/net/multiplayer_peer_gdnative.cpp index 9908ed4533..575d5f5060 100644 --- a/modules/gdnative/net/multiplayer_peer_gdnative.cpp +++ b/modules/gdnative/net/multiplayer_peer_gdnative.cpp @@ -72,14 +72,14 @@ int MultiplayerPeerGDNative::get_transfer_channel() const { return interface->get_transfer_channel(interface->data); } -void MultiplayerPeerGDNative::set_transfer_mode(TransferMode p_mode) { +void MultiplayerPeerGDNative::set_transfer_mode(Multiplayer::TransferMode p_mode) { ERR_FAIL_COND(interface == nullptr); interface->set_transfer_mode(interface->data, (godot_int)p_mode); } -MultiplayerPeer::TransferMode MultiplayerPeerGDNative::get_transfer_mode() const { - ERR_FAIL_COND_V(interface == nullptr, TRANSFER_MODE_UNRELIABLE); - return (TransferMode)interface->get_transfer_mode(interface->data); +Multiplayer::TransferMode MultiplayerPeerGDNative::get_transfer_mode() const { + ERR_FAIL_COND_V(interface == nullptr, Multiplayer::TRANSFER_MODE_UNRELIABLE); + return (Multiplayer::TransferMode)interface->get_transfer_mode(interface->data); } void MultiplayerPeerGDNative::set_target_peer(int p_peer_id) { @@ -124,7 +124,7 @@ MultiplayerPeer::ConnectionStatus MultiplayerPeerGDNative::get_connection_status void MultiplayerPeerGDNative::_bind_methods() { ADD_PROPERTY_DEFAULT("transfer_channel", 0); - ADD_PROPERTY_DEFAULT("transfer_mode", TRANSFER_MODE_UNRELIABLE); + ADD_PROPERTY_DEFAULT("transfer_mode", Multiplayer::TRANSFER_MODE_UNRELIABLE); ADD_PROPERTY_DEFAULT("refuse_new_connections", true); } diff --git a/modules/gdnative/net/multiplayer_peer_gdnative.h b/modules/gdnative/net/multiplayer_peer_gdnative.h index ab084faae6..33e424d284 100644 --- a/modules/gdnative/net/multiplayer_peer_gdnative.h +++ b/modules/gdnative/net/multiplayer_peer_gdnative.h @@ -31,7 +31,7 @@ #ifndef MULTIPLAYER_PEER_GDNATIVE_H #define MULTIPLAYER_PEER_GDNATIVE_H -#include "core/io/multiplayer_peer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "modules/gdnative/gdnative.h" #include "modules/gdnative/include/net/godot_net.h" @@ -58,8 +58,8 @@ public: /* Specific to MultiplayerPeer */ virtual void set_transfer_channel(int p_channel) override; virtual int get_transfer_channel() const override; - virtual void set_transfer_mode(TransferMode p_mode) override; - virtual TransferMode get_transfer_mode() const override; + virtual void set_transfer_mode(Multiplayer::TransferMode p_mode) override; + virtual Multiplayer::TransferMode get_transfer_mode() const override; virtual void set_target_peer(int p_peer_id) override; virtual int get_packet_peer() const override; diff --git a/modules/gdnative/pluginscript/pluginscript_instance.cpp b/modules/gdnative/pluginscript/pluginscript_instance.cpp index ed1c0af3ed..feae81397e 100644 --- a/modules/gdnative/pluginscript/pluginscript_instance.cpp +++ b/modules/gdnative/pluginscript/pluginscript_instance.cpp @@ -100,7 +100,7 @@ String PluginScriptInstance::to_string(bool *r_valid) { return str_ret; } -const Vector<MultiplayerAPI::RPCConfig> PluginScriptInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> PluginScriptInstance::get_rpc_methods() const { return _script->get_rpc_methods(); } diff --git a/modules/gdnative/pluginscript/pluginscript_instance.h b/modules/gdnative/pluginscript/pluginscript_instance.h index 25b62ae8ab..bdae265db2 100644 --- a/modules/gdnative/pluginscript/pluginscript_instance.h +++ b/modules/gdnative/pluginscript/pluginscript_instance.h @@ -71,7 +71,7 @@ public: void set_path(const String &p_path); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const; virtual void refcount_incremented(); virtual bool refcount_decremented(); diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp index 5380858582..2b4ceda49d 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.cpp +++ b/modules/gdnative/pluginscript/pluginscript_script.cpp @@ -334,9 +334,9 @@ Error PluginScript::reload(bool p_keep_state) { // rpc_mode is passed as an optional field and is not part of MethodInfo Variant var = v["rpc_mode"]; if (var != Variant()) { - MultiplayerAPI::RPCConfig nd; + Multiplayer::RPCConfig nd; nd.name = mi.name; - nd.rpc_mode = MultiplayerAPI::RPCMode(int(var)); + nd.rpc_mode = Multiplayer::RPCMode(int(var)); // TODO Transfer Channel if (_rpc_methods.find(nd) == -1) { _rpc_methods.push_back(nd); @@ -345,7 +345,7 @@ Error PluginScript::reload(bool p_keep_state) { } // Sort so we are 100% that they are always the same. - _rpc_methods.sort_custom<MultiplayerAPI::SortRPCConfig>(); + _rpc_methods.sort_custom<Multiplayer::SortRPCConfig>(); Array *signals = (Array *)&manifest.signals; for (int i = 0; i < signals->size(); ++i) { @@ -484,7 +484,7 @@ int PluginScript::get_member_line(const StringName &p_member) const { return -1; } -const Vector<MultiplayerAPI::RPCConfig> PluginScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> PluginScript::get_rpc_methods() const { return _rpc_methods; } diff --git a/modules/gdnative/pluginscript/pluginscript_script.h b/modules/gdnative/pluginscript/pluginscript_script.h index 838195147f..1a12a130d1 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.h +++ b/modules/gdnative/pluginscript/pluginscript_script.h @@ -61,7 +61,7 @@ private: Map<StringName, PropertyInfo> _properties_info; Map<StringName, MethodInfo> _signals_info; Map<StringName, MethodInfo> _methods_info; - Vector<MultiplayerAPI::RPCConfig> _rpc_methods; + Vector<Multiplayer::RPCConfig> _rpc_methods; Set<Object *> _instances; //exported members @@ -136,7 +136,7 @@ public: virtual int get_member_line(const StringName &p_member) const override; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; PluginScript(); void init(PluginScriptLanguage *language); diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index a41c4f7b19..e4c2b20224 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -38,7 +38,6 @@ #include "net/register_types.h" #include "pluginscript/register_types.h" #include "videodecoder/register_types.h" -#include "xr/register_types.h" #include "core/config/engine.h" #include "core/config/project_settings.h" @@ -267,7 +266,6 @@ void register_gdnative_types() { GDNativeCallRegistry::singleton->register_native_call_type("standard_varcall", cb_standard_varcall); register_net_types(); - register_xr_types(); register_nativescript_types(); register_pluginscript_types(); register_videodecoder_types(); @@ -331,7 +329,6 @@ void unregister_gdnative_types() { unregister_videodecoder_types(); unregister_pluginscript_types(); unregister_nativescript_types(); - unregister_xr_types(); unregister_net_types(); memdelete(GDNativeCallRegistry::singleton); diff --git a/modules/gdnative/text/text_server_gdnative.cpp b/modules/gdnative/text/text_server_gdnative.cpp index 3ed3f5449e..39db8ae636 100644 --- a/modules/gdnative/text/text_server_gdnative.cpp +++ b/modules/gdnative/text/text_server_gdnative.cpp @@ -88,299 +88,479 @@ bool TextServerGDNative::is_locale_right_to_left(const String &p_locale) { return interface->is_locale_right_to_left(data, (godot_string *)&p_locale); } +int32_t TextServerGDNative::name_to_tag(const String &p_name) const { + ERR_FAIL_COND_V(interface == nullptr, 0); + return interface->name_to_tag(data, (godot_string *)&p_name); +} + +String TextServerGDNative::tag_to_name(int32_t p_tag) const { + ERR_FAIL_COND_V(interface == nullptr, String()); + godot_string result = interface->tag_to_name(data, p_tag); + String name = *(String *)&result; + godot_string_destroy(&result); + return name; +} + /*************************************************************************/ -/* Font interface */ +/* Font */ /*************************************************************************/ -RID TextServerGDNative::create_font_system(const String &p_name, int p_base_size) { +RID TextServerGDNative::create_font() { ERR_FAIL_COND_V(interface == nullptr, RID()); - godot_rid result = interface->create_font_system(data, (const godot_string *)&p_name, p_base_size); + godot_rid result = interface->create_font(data); RID rid = *(RID *)&result; return rid; } -RID TextServerGDNative::create_font_resource(const String &p_filename, int p_base_size) { - ERR_FAIL_COND_V(interface == nullptr, RID()); - godot_rid result = interface->create_font_resource(data, (const godot_string *)&p_filename, p_base_size); - RID rid = *(RID *)&result; - return rid; +void TextServerGDNative::font_set_data(RID p_font_rid, const PackedByteArray &p_data) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_data(data, (godot_rid *)&p_font_rid, (const godot_packed_byte_array *)&p_data); } -RID TextServerGDNative::create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size) { - ERR_FAIL_COND_V(interface == nullptr, RID()); - godot_rid result = interface->create_font_memory(data, p_data, p_size, (godot_string *)&p_type, p_base_size); - RID rid = *(RID *)&result; - return rid; +void TextServerGDNative::font_set_data_ptr(RID p_font_rid, const uint8_t *p_data_ptr, size_t p_data_size) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_data_ptr(data, (godot_rid *)&p_font_rid, p_data_ptr, p_data_size); } -RID TextServerGDNative::create_font_bitmap(float p_height, float p_ascent, int p_base_size) { - ERR_FAIL_COND_V(interface == nullptr, RID()); - godot_rid result = interface->create_font_bitmap(data, p_height, p_ascent, p_base_size); - RID rid = *(RID *)&result; - return rid; +void TextServerGDNative::font_set_antialiased(RID p_font_rid, bool p_antialiased) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_antialiased(data, (godot_rid *)&p_font_rid, p_antialiased); } -void TextServerGDNative::font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) { +bool TextServerGDNative::font_is_antialiased(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, false); + return interface->font_is_antialiased(data, (godot_rid *)&p_font_rid); +} + +void TextServerGDNative::font_set_multichannel_signed_distance_field(RID p_font_rid, bool p_msdf) { ERR_FAIL_COND(interface == nullptr); - interface->font_bitmap_add_texture(data, (godot_rid *)&p_font, (const godot_object *)p_texture.ptr()); + interface->font_set_multichannel_signed_distance_field(data, (godot_rid *)&p_font_rid, p_msdf); +} + +bool TextServerGDNative::font_is_multichannel_signed_distance_field(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, false); + return interface->font_is_multichannel_signed_distance_field(data, (godot_rid *)&p_font_rid); } -void TextServerGDNative::font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { +void TextServerGDNative::font_set_msdf_pixel_range(RID p_font_rid, int p_msdf_pixel_range) { ERR_FAIL_COND(interface == nullptr); - interface->font_bitmap_add_char(data, (godot_rid *)&p_font, p_char, p_texture_idx, (const godot_rect2 *)&p_rect, (const godot_vector2 *)&p_align, p_advance); + interface->font_set_msdf_pixel_range(data, (godot_rid *)&p_font_rid, p_msdf_pixel_range); } -void TextServerGDNative::font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) { +int TextServerGDNative::font_get_msdf_pixel_range(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, 0); + return interface->font_get_msdf_pixel_range(data, (godot_rid *)&p_font_rid); +} + +void TextServerGDNative::font_set_msdf_size(RID p_font_rid, int p_msdf_size) { ERR_FAIL_COND(interface == nullptr); - interface->font_bitmap_add_kerning_pair(data, (godot_rid *)&p_font, p_A, p_B, p_kerning); + interface->font_set_msdf_size(data, (godot_rid *)&p_font_rid, p_msdf_size); } -float TextServerGDNative::font_get_height(RID p_font, int p_size) const { - ERR_FAIL_COND_V(interface == nullptr, 0.f); - return interface->font_get_height(data, (godot_rid *)&p_font, p_size); +int TextServerGDNative::font_get_msdf_size(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, 0); + return interface->font_get_msdf_size(data, (godot_rid *)&p_font_rid); } -float TextServerGDNative::font_get_ascent(RID p_font, int p_size) const { - ERR_FAIL_COND_V(interface == nullptr, 0.f); - return interface->font_get_ascent(data, (godot_rid *)&p_font, p_size); +void TextServerGDNative::font_set_fixed_size(RID p_font_rid, int p_fixed_size) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_fixed_size(data, (godot_rid *)&p_font_rid, p_fixed_size); } -float TextServerGDNative::font_get_descent(RID p_font, int p_size) const { - ERR_FAIL_COND_V(interface == nullptr, 0.f); - return interface->font_get_descent(data, (godot_rid *)&p_font, p_size); +int TextServerGDNative::font_get_fixed_size(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, 0); + return interface->font_get_fixed_size(data, (godot_rid *)&p_font_rid); } -float TextServerGDNative::font_get_underline_position(RID p_font, int p_size) const { - ERR_FAIL_COND_V(interface == nullptr, 0.f); - return interface->font_get_underline_position(data, (godot_rid *)&p_font, p_size); +void TextServerGDNative::font_set_force_autohinter(RID p_font_rid, bool p_force_autohinter) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_force_autohinter(data, (godot_rid *)&p_font_rid, p_force_autohinter); } -float TextServerGDNative::font_get_underline_thickness(RID p_font, int p_size) const { - ERR_FAIL_COND_V(interface == nullptr, 0.f); - return interface->font_get_underline_thickness(data, (godot_rid *)&p_font, p_size); +bool TextServerGDNative::font_is_force_autohinter(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, false); + return interface->font_is_force_autohinter(data, (godot_rid *)&p_font_rid); } -int TextServerGDNative::font_get_spacing_space(RID p_font) const { - ERR_FAIL_COND_V(interface == nullptr, 0); - return interface->font_get_spacing_space(data, (godot_rid *)&p_font); +void TextServerGDNative::font_set_hinting(RID p_font_rid, TextServer::Hinting p_hinting) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_hinting(data, (godot_rid *)&p_font_rid, (godot_int)p_hinting); } -void TextServerGDNative::font_set_spacing_space(RID p_font, int p_value) { +TextServer::Hinting TextServerGDNative::font_get_hinting(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, TextServer::HINTING_NONE); + return (TextServer::Hinting)interface->font_get_hinting(data, (godot_rid *)&p_font_rid); +} + +void TextServerGDNative::font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_variation_coordinates(data, (godot_rid *)&p_font_rid, (const godot_dictionary *)&p_variation_coordinates); +} + +Dictionary TextServerGDNative::font_get_variation_coordinates(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, Dictionary()); + godot_dictionary result = interface->font_get_variation_coordinates(data, (godot_rid *)&p_font_rid); + Dictionary dict = *(Dictionary *)&result; + godot_dictionary_destroy(&result); + return dict; +} + +void TextServerGDNative::font_set_oversampling(RID p_font_rid, real_t p_oversampling) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_oversampling(data, (godot_rid *)&p_font_rid, p_oversampling); +} + +real_t TextServerGDNative::font_get_oversampling(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, 0.0f); + return interface->font_get_oversampling(data, (godot_rid *)&p_font_rid); +} + +Array TextServerGDNative::font_get_size_cache_list(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, Array()); + godot_array result = interface->font_get_size_cache_list(data, (godot_rid *)&p_font_rid); + Array list = *(Array *)&result; + godot_array_destroy(&result); + return list; +} + +void TextServerGDNative::font_clear_size_cache(RID p_font_rid) { + ERR_FAIL_COND(interface == nullptr); + interface->font_clear_size_cache(data, (godot_rid *)&p_font_rid); +} + +void TextServerGDNative::font_remove_size_cache(RID p_font_rid, const Vector2i &p_size) { + ERR_FAIL_COND(interface == nullptr); + interface->font_remove_size_cache(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size); +} + +void TextServerGDNative::font_set_ascent(RID p_font_rid, int p_size, real_t p_ascent) { ERR_FAIL_COND(interface == nullptr); - interface->font_set_spacing_space(data, (godot_rid *)&p_font, p_value); + interface->font_set_ascent(data, (godot_rid *)&p_font_rid, p_size, p_ascent); +} + +real_t TextServerGDNative::font_get_ascent(RID p_font_rid, int p_size) const { + ERR_FAIL_COND_V(interface == nullptr, 0.0f); + return interface->font_get_ascent(data, (godot_rid *)&p_font_rid, p_size); } -int TextServerGDNative::font_get_spacing_glyph(RID p_font) const { +void TextServerGDNative::font_set_descent(RID p_font_rid, int p_size, real_t p_descent) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_descent(data, (godot_rid *)&p_font_rid, p_size, p_descent); +} + +real_t TextServerGDNative::font_get_descent(RID p_font_rid, int p_size) const { + ERR_FAIL_COND_V(interface == nullptr, 0.0f); + return interface->font_get_descent(data, (godot_rid *)&p_font_rid, p_size); +} + +void TextServerGDNative::font_set_underline_position(RID p_font_rid, int p_size, real_t p_underline_position) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_underline_position(data, (godot_rid *)&p_font_rid, p_size, p_underline_position); +} + +real_t TextServerGDNative::font_get_underline_position(RID p_font_rid, int p_size) const { + ERR_FAIL_COND_V(interface == nullptr, 0.0f); + return interface->font_get_underline_position(data, (godot_rid *)&p_font_rid, p_size); +} + +void TextServerGDNative::font_set_underline_thickness(RID p_font_rid, int p_size, real_t p_underline_thickness) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_underline_thickness(data, (godot_rid *)&p_font_rid, p_size, p_underline_thickness); +} + +real_t TextServerGDNative::font_get_underline_thickness(RID p_font_rid, int p_size) const { + ERR_FAIL_COND_V(interface == nullptr, 0.0f); + return interface->font_get_underline_thickness(data, (godot_rid *)&p_font_rid, p_size); +} + +void TextServerGDNative::font_set_scale(RID p_font_rid, int p_size, real_t p_scale) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_scale(data, (godot_rid *)&p_font_rid, p_size, p_scale); +} + +real_t TextServerGDNative::font_get_scale(RID p_font_rid, int p_size) const { + ERR_FAIL_COND_V(interface == nullptr, 0.0f); + return interface->font_get_scale(data, (godot_rid *)&p_font_rid, p_size); +} + +void TextServerGDNative::font_set_spacing(RID p_font_rid, int p_size, TextServer::SpacingType p_spacing, int p_value) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_spacing(data, (godot_rid *)&p_font_rid, p_size, (godot_int)p_spacing, p_value); +} + +int TextServerGDNative::font_get_spacing(RID p_font_rid, int p_size, TextServer::SpacingType p_spacing) const { ERR_FAIL_COND_V(interface == nullptr, 0); - return interface->font_get_spacing_glyph(data, (godot_rid *)&p_font); + return interface->font_get_spacing(data, (godot_rid *)&p_font_rid, p_size, (godot_int)p_spacing); } -void TextServerGDNative::font_set_spacing_glyph(RID p_font, int p_value) { +int TextServerGDNative::font_get_texture_count(RID p_font_rid, const Vector2i &p_size) const { + ERR_FAIL_COND_V(interface == nullptr, -1); + return interface->font_get_texture_count(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size); +} + +void TextServerGDNative::font_clear_textures(RID p_font_rid, const Vector2i &p_size) { ERR_FAIL_COND(interface == nullptr); - interface->font_set_spacing_glyph(data, (godot_rid *)&p_font, p_value); + interface->font_clear_textures(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size); } -void TextServerGDNative::font_set_antialiased(RID p_font, bool p_antialiased) { +void TextServerGDNative::font_remove_texture(RID p_font_rid, const Vector2i &p_size, int p_texture_index) { ERR_FAIL_COND(interface == nullptr); - interface->font_set_antialiased(data, (godot_rid *)&p_font, p_antialiased); + interface->font_remove_texture(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_texture_index); } -bool TextServerGDNative::font_get_antialiased(RID p_font) const { - ERR_FAIL_COND_V(interface == nullptr, false); - return interface->font_get_antialiased(data, (godot_rid *)&p_font); +void TextServerGDNative::font_set_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_texture_image(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_texture_index, (const godot_object *)p_image.ptr()); } -Dictionary TextServerGDNative::font_get_variation_list(RID p_font) const { - ERR_FAIL_COND_V(interface == nullptr, Dictionary()); - godot_dictionary result = interface->font_get_variation_list(data, (godot_rid *)&p_font); - Dictionary info = *(Dictionary *)&result; - godot_dictionary_destroy(&result); +Ref<Image> TextServerGDNative::font_get_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const { + ERR_FAIL_COND_V(interface == nullptr, Ref<Image>()); + godot_object *result = interface->font_get_texture_image(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_texture_index); + return Ref<Image>((Image *)result); +} - return info; +void TextServerGDNative::font_set_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_texture_offsets(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_texture_index, (const godot_packed_int32_array *)&p_offset); +} + +PackedInt32Array TextServerGDNative::font_get_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const { + ERR_FAIL_COND_V(interface == nullptr, PackedInt32Array()); + godot_packed_int32_array result = interface->font_get_texture_offsets(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_texture_index); + PackedInt32Array offset = *(PackedInt32Array *)&result; + godot_packed_int32_array_destroy(&result); + return offset; +} + +Array TextServerGDNative::font_get_glyph_list(RID p_font_rid, const Vector2i &p_size) const { + ERR_FAIL_COND_V(interface == nullptr, Array()); + godot_array result = interface->font_get_glyph_list(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size); + Array list = *(Array *)&result; + godot_array_destroy(&result); + return list; +} + +void TextServerGDNative::font_clear_glyphs(RID p_font_rid, const Vector2i &p_size) { + ERR_FAIL_COND(interface == nullptr); + interface->font_clear_glyphs(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size); } -void TextServerGDNative::font_set_variation(RID p_font, const String &p_name, double p_value) { +void TextServerGDNative::font_remove_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) { ERR_FAIL_COND(interface == nullptr); - interface->font_set_variation(data, (godot_rid *)&p_font, (godot_string *)&p_name, p_value); + interface->font_remove_glyph(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_glyph); } -double TextServerGDNative::font_get_variation(RID p_font, const String &p_name) const { - return interface->font_get_variation(data, (godot_rid *)&p_font, (godot_string *)&p_name); +Vector2 TextServerGDNative::font_get_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(interface == nullptr, Vector2()); + godot_vector2 result = interface->font_get_glyph_advance(data, (godot_rid *)&p_font_rid, p_size, p_glyph); + Vector2 adv = *(Vector2 *)&result; + return adv; } -void TextServerGDNative::font_set_hinting(RID p_font, TextServer::Hinting p_hinting) { +void TextServerGDNative::font_set_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph, const Vector2 &p_advance) { ERR_FAIL_COND(interface == nullptr); - interface->font_set_hinting(data, (godot_rid *)&p_font, (godot_int)p_hinting); + interface->font_set_glyph_advance(data, (godot_rid *)&p_font_rid, p_size, p_glyph, (const godot_vector2 *)&p_advance); } -TextServer::Hinting TextServerGDNative::font_get_hinting(RID p_font) const { - ERR_FAIL_COND_V(interface == nullptr, TextServer::HINTING_NONE); - return (TextServer::Hinting)interface->font_get_hinting(data, (godot_rid *)&p_font); +Vector2 TextServerGDNative::font_get_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(interface == nullptr, Vector2()); + godot_vector2 result = interface->font_get_glyph_offset(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_glyph); + Vector2 off = *(Vector2 *)&result; + return off; } -Dictionary TextServerGDNative::font_get_feature_list(RID p_font) const { - ERR_FAIL_COND_V(interface == nullptr, Dictionary()); - godot_dictionary result = interface->font_get_feature_list(data, (godot_rid *)&p_font); - Dictionary info = *(Dictionary *)&result; - godot_dictionary_destroy(&result); +void TextServerGDNative::font_set_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_glyph_offset(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_glyph, (const godot_vector2 *)&p_offset); +} - return info; +Vector2 TextServerGDNative::font_get_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(interface == nullptr, Vector2()); + godot_vector2 result = interface->font_get_glyph_size(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_glyph); + Vector2 sz = *(Vector2 *)&result; + return sz; } -void TextServerGDNative::font_set_distance_field_hint(RID p_font, bool p_distance_field) { +void TextServerGDNative::font_set_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) { ERR_FAIL_COND(interface == nullptr); - interface->font_set_distance_field_hint(data, (godot_rid *)&p_font, p_distance_field); + interface->font_set_glyph_size(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_glyph, (const godot_vector2 *)&p_gl_size); } -bool TextServerGDNative::font_get_distance_field_hint(RID p_font) const { - ERR_FAIL_COND_V(interface == nullptr, false); - return interface->font_get_distance_field_hint(data, (godot_rid *)&p_font); +Rect2 TextServerGDNative::font_get_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(interface == nullptr, Rect2()); + godot_rect2 result = interface->font_get_glyph_uv_rect(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_glyph); + Rect2 uv = *(Rect2 *)&result; + return uv; } -void TextServerGDNative::font_set_force_autohinter(RID p_font, bool p_enabeld) { +void TextServerGDNative::font_set_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) { ERR_FAIL_COND(interface == nullptr); - interface->font_set_force_autohinter(data, (godot_rid *)&p_font, p_enabeld); + interface->font_set_glyph_uv_rect(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_glyph, (const godot_rect2 *)&p_uv_rect); } -bool TextServerGDNative::font_get_force_autohinter(RID p_font) const { - ERR_FAIL_COND_V(interface == nullptr, false); - return interface->font_get_force_autohinter(data, (godot_rid *)&p_font); +int TextServerGDNative::font_get_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(interface == nullptr, -1); + return interface->font_get_glyph_texture_idx(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_glyph); +} + +void TextServerGDNative::font_set_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_glyph_texture_idx(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_glyph, p_texture_idx); } -bool TextServerGDNative::font_has_char(RID p_font, char32_t p_char) const { +bool TextServerGDNative::font_get_glyph_contours(RID p_font_rid, int p_size, int32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { ERR_FAIL_COND_V(interface == nullptr, false); - return interface->font_has_char(data, (godot_rid *)&p_font, p_char); + return interface->font_get_glyph_contours(data, (godot_rid *)&p_font_rid, p_size, p_index, (godot_packed_vector3_array *)&r_points, (godot_packed_int32_array *)&r_contours, &r_orientation); } -String TextServerGDNative::font_get_supported_chars(RID p_font) const { - ERR_FAIL_COND_V(interface == nullptr, String()); - godot_string result = interface->font_get_supported_chars(data, (godot_rid *)&p_font); - String ret = *(String *)&result; - godot_string_destroy(&result); - return ret; +Array TextServerGDNative::font_get_kerning_list(RID p_font_rid, int p_size) const { + ERR_FAIL_COND_V(interface == nullptr, Array()); + godot_array result = interface->font_get_kerning_list(data, (godot_rid *)&p_font_rid, p_size); + Array list = *(Array *)&result; + godot_array_destroy(&result); + return list; } -bool TextServerGDNative::font_has_outline(RID p_font) const { - ERR_FAIL_COND_V(interface == nullptr, false); - return interface->font_has_outline(data, (godot_rid *)&p_font); +void TextServerGDNative::font_clear_kerning_map(RID p_font_rid, int p_size) { + ERR_FAIL_COND(interface == nullptr); + interface->font_clear_kerning_map(data, (godot_rid *)&p_font_rid, p_size); } -float TextServerGDNative::font_get_base_size(RID p_font) const { - ERR_FAIL_COND_V(interface == nullptr, 0.f); - return interface->font_get_base_size(data, (godot_rid *)&p_font); +void TextServerGDNative::font_remove_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) { + ERR_FAIL_COND(interface == nullptr); + interface->font_remove_kerning(data, (godot_rid *)&p_font_rid, p_size, (const godot_vector2i *)&p_glyph_pair); +} + +void TextServerGDNative::font_set_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_kerning(data, (godot_rid *)&p_font_rid, p_size, (const godot_vector2i *)&p_glyph_pair, (const godot_vector2 *)&p_kerning); +} + +Vector2 TextServerGDNative::font_get_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) const { + ERR_FAIL_COND_V(interface == nullptr, Vector2()); + godot_vector2 result = interface->font_get_kerning(data, (godot_rid *)&p_font_rid, p_size, (const godot_vector2i *)&p_glyph_pair); + Vector2 kern = *(Vector2 *)&result; + return kern; } -bool TextServerGDNative::font_is_language_supported(RID p_font, const String &p_language) const { +int32_t TextServerGDNative::font_get_glyph_index(RID p_font_rid, int p_size, char32_t p_char, char32_t p_variation_selector) const { + ERR_FAIL_COND_V(interface == nullptr, 0); + return interface->font_get_glyph_index(data, (godot_rid *)&p_font_rid, p_size, p_char, p_variation_selector); +} + +bool TextServerGDNative::font_has_char(RID p_font_rid, char32_t p_char) const { ERR_FAIL_COND_V(interface == nullptr, false); - return interface->font_is_language_supported(data, (godot_rid *)&p_font, (godot_string *)&p_language); + return interface->font_has_char(data, (godot_rid *)&p_font_rid, p_char); +} + +String TextServerGDNative::font_get_supported_chars(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, String()); + godot_string result = interface->font_get_supported_chars(data, (godot_rid *)&p_font_rid); + String chars = *(String *)&result; + godot_string_destroy(&result); + return chars; } -void TextServerGDNative::font_set_language_support_override(RID p_font, const String &p_language, bool p_supported) { +void TextServerGDNative::font_render_range(RID p_font_rid, const Vector2i &p_size, char32_t p_start, char32_t p_end) { ERR_FAIL_COND(interface == nullptr); - return interface->font_set_language_support_override(data, (godot_rid *)&p_font, (godot_string *)&p_language, p_supported); + interface->font_render_range(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_start, p_end); } -bool TextServerGDNative::font_get_language_support_override(RID p_font, const String &p_language) { - ERR_FAIL_COND_V(interface == nullptr, false); - return interface->font_get_language_support_override(data, (godot_rid *)&p_font, (godot_string *)&p_language); +void TextServerGDNative::font_render_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_index) { + ERR_FAIL_COND(interface == nullptr); + interface->font_render_glyph(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_index); } -void TextServerGDNative::font_remove_language_support_override(RID p_font, const String &p_language) { +void TextServerGDNative::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color) const { ERR_FAIL_COND(interface == nullptr); - interface->font_remove_language_support_override(data, (godot_rid *)&p_font, (godot_string *)&p_language); + interface->font_draw_glyph(data, (godot_rid *)&p_font_rid, (godot_rid *)&p_canvas, p_size, (const godot_vector2 *)&p_pos, p_index, (const godot_color *)&p_color); } -Vector<String> TextServerGDNative::font_get_language_support_overrides(RID p_font) { - ERR_FAIL_COND_V(interface == nullptr, Vector<String>()); - godot_packed_string_array result = interface->font_get_language_support_overrides(data, (godot_rid *)&p_font); - Vector<String> ret = *(Vector<String> *)&result; - godot_packed_string_array_destroy(&result); - return ret; +void TextServerGDNative::font_draw_glyph_outline(RID p_font_rid, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color) const { + ERR_FAIL_COND(interface == nullptr); + interface->font_draw_glyph_outline(data, (godot_rid *)&p_font_rid, (godot_rid *)&p_canvas, p_size, p_outline_size, (const godot_vector2 *)&p_pos, p_index, (const godot_color *)&p_color); } -bool TextServerGDNative::font_is_script_supported(RID p_font, const String &p_script) const { +bool TextServerGDNative::font_is_language_supported(RID p_font_rid, const String &p_language) const { ERR_FAIL_COND_V(interface == nullptr, false); - return interface->font_is_script_supported(data, (godot_rid *)&p_font, (godot_string *)&p_script); + return interface->font_is_language_supported(data, (godot_rid *)&p_font_rid, (const godot_string *)&p_language); } -void TextServerGDNative::font_set_script_support_override(RID p_font, const String &p_script, bool p_supported) { +void TextServerGDNative::font_set_language_support_override(RID p_font_rid, const String &p_language, bool p_supported) { ERR_FAIL_COND(interface == nullptr); - return interface->font_set_script_support_override(data, (godot_rid *)&p_font, (godot_string *)&p_script, p_supported); + interface->font_set_language_support_override(data, (godot_rid *)&p_font_rid, (const godot_string *)&p_language, p_supported); } -bool TextServerGDNative::font_get_script_support_override(RID p_font, const String &p_script) { +bool TextServerGDNative::font_get_language_support_override(RID p_font_rid, const String &p_language) { ERR_FAIL_COND_V(interface == nullptr, false); - return interface->font_get_script_support_override(data, (godot_rid *)&p_font, (godot_string *)&p_script); + return interface->font_get_language_support_override(data, (godot_rid *)&p_font_rid, (const godot_string *)&p_language); } -void TextServerGDNative::font_remove_script_support_override(RID p_font, const String &p_script) { +void TextServerGDNative::font_remove_language_support_override(RID p_font_rid, const String &p_language) { ERR_FAIL_COND(interface == nullptr); - interface->font_remove_script_support_override(data, (godot_rid *)&p_font, (godot_string *)&p_script); + interface->font_remove_language_support_override(data, (godot_rid *)&p_font_rid, (const godot_string *)&p_language); } -Vector<String> TextServerGDNative::font_get_script_support_overrides(RID p_font) { - ERR_FAIL_COND_V(interface == nullptr, Vector<String>()); - godot_packed_string_array result = interface->font_get_script_support_overrides(data, (godot_rid *)&p_font); - Vector<String> ret = *(Vector<String> *)&result; +Vector<String> TextServerGDNative::font_get_language_support_overrides(RID p_font_rid) { + ERR_FAIL_COND_V(interface == nullptr, PackedStringArray()); + godot_packed_string_array result = interface->font_get_language_support_overrides(data, (godot_rid *)&p_font_rid); + PackedStringArray list = *(PackedStringArray *)&result; godot_packed_string_array_destroy(&result); - return ret; + return list; } -uint32_t TextServerGDNative::font_get_glyph_index(RID p_font, char32_t p_char, char32_t p_variation_selector) const { - ERR_FAIL_COND_V(interface == nullptr, 0); - return interface->font_get_glyph_index(data, (godot_rid *)&p_font, p_char, p_variation_selector); +bool TextServerGDNative::font_is_script_supported(RID p_font_rid, const String &p_script) const { + ERR_FAIL_COND_V(interface == nullptr, false); + return interface->font_is_script_supported(data, (godot_rid *)&p_font_rid, (const godot_string *)&p_script); } -Vector2 TextServerGDNative::font_get_glyph_advance(RID p_font, uint32_t p_index, int p_size) const { - ERR_FAIL_COND_V(interface == nullptr, Vector2()); - godot_vector2 result = interface->font_get_glyph_advance(data, (godot_rid *)&p_font, p_index, p_size); - Vector2 advance = *(Vector2 *)&result; - return advance; +void TextServerGDNative::font_set_script_support_override(RID p_font_rid, const String &p_script, bool p_supported) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_script_support_override(data, (godot_rid *)&p_font_rid, (const godot_string *)&p_script, p_supported); } -Vector2 TextServerGDNative::font_get_glyph_kerning(RID p_font, uint32_t p_index_a, uint32_t p_index_b, int p_size) const { - ERR_FAIL_COND_V(interface == nullptr, Vector2()); - godot_vector2 result = interface->font_get_glyph_kerning(data, (godot_rid *)&p_font, p_index_a, p_index_b, p_size); - Vector2 kerning = *(Vector2 *)&result; - return kerning; +bool TextServerGDNative::font_get_script_support_override(RID p_font_rid, const String &p_script) { + ERR_FAIL_COND_V(interface == nullptr, false); + return interface->font_get_script_support_override(data, (godot_rid *)&p_font_rid, (const godot_string *)&p_script); } -Vector2 TextServerGDNative::font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - ERR_FAIL_COND_V(interface == nullptr, Vector2()); - godot_vector2 result = interface->font_draw_glyph(data, (godot_rid *)&p_font, (godot_rid *)&p_canvas, p_size, (const godot_vector2 *)&p_pos, p_index, (const godot_color *)&p_color); - Vector2 advance = *(Vector2 *)&result; - return advance; +void TextServerGDNative::font_remove_script_support_override(RID p_font_rid, const String &p_script) { + ERR_FAIL_COND(interface == nullptr); + interface->font_remove_script_support_override(data, (godot_rid *)&p_font_rid, (const godot_string *)&p_script); } -Vector2 TextServerGDNative::font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - ERR_FAIL_COND_V(interface == nullptr, Vector2()); - godot_vector2 result = interface->font_draw_glyph_outline(data, (godot_rid *)&p_font, (godot_rid *)&p_canvas, p_size, p_outline_size, (const godot_vector2 *)&p_pos, p_index, (const godot_color *)&p_color); - Vector2 advance = *(Vector2 *)&result; - return advance; +Vector<String> TextServerGDNative::font_get_script_support_overrides(RID p_font_rid) { + ERR_FAIL_COND_V(interface == nullptr, PackedStringArray()); + godot_packed_string_array result = interface->font_get_script_support_overrides(data, (godot_rid *)&p_font_rid); + PackedStringArray list = *(PackedStringArray *)&result; + godot_packed_string_array_destroy(&result); + return list; } -bool TextServerGDNative::font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { - ERR_FAIL_COND_V(interface == nullptr, false); - ERR_FAIL_COND_V(interface->font_get_glyph_contours == nullptr, false); - return interface->font_get_glyph_contours(data, (godot_rid *)&p_font, p_size, p_index, (godot_packed_vector3_array *)&r_points, (godot_packed_int32_array *)&r_contours, (bool *)&r_orientation); +Dictionary TextServerGDNative::font_supported_feature_list(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, Dictionary()); + godot_dictionary result = interface->font_supported_feature_list(data, (godot_rid *)&p_font_rid); + Dictionary dict = *(Dictionary *)&result; + godot_dictionary_destroy(&result); + return dict; } -float TextServerGDNative::font_get_oversampling() const { - ERR_FAIL_COND_V(interface == nullptr, 1.f); - return interface->font_get_oversampling(data); +Dictionary TextServerGDNative::font_supported_variation_list(RID p_font_rid) const { + ERR_FAIL_COND_V(interface == nullptr, Dictionary()); + godot_dictionary result = interface->font_supported_variation_list(data, (godot_rid *)&p_font_rid); + Dictionary dict = *(Dictionary *)&result; + godot_dictionary_destroy(&result); + return dict; } -void TextServerGDNative::font_set_oversampling(float p_oversampling) { - ERR_FAIL_COND(interface == nullptr); - return interface->font_set_oversampling(data, p_oversampling); +real_t TextServerGDNative::font_get_global_oversampling() const { + ERR_FAIL_COND_V(interface == nullptr, 0.0f); + return interface->font_get_global_oversampling(data); } -Vector<String> TextServerGDNative::get_system_fonts() const { - ERR_FAIL_COND_V(interface == nullptr, Vector<String>()); - godot_packed_string_array result = interface->get_system_fonts(data); - Vector<String> fonts = *(Vector<String> *)&result; - godot_packed_string_array_destroy(&result); - return fonts; +void TextServerGDNative::font_set_global_oversampling(real_t p_oversampling) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_global_oversampling(data, p_oversampling); } /*************************************************************************/ @@ -473,12 +653,12 @@ RID TextServerGDNative::shaped_text_get_parent(RID p_shaped) const { return rid; } -float TextServerGDNative::shaped_text_fit_to_width(RID p_shaped, float p_width, uint8_t p_jst_flags) { +real_t TextServerGDNative::shaped_text_fit_to_width(RID p_shaped, real_t p_width, uint8_t p_jst_flags) { ERR_FAIL_COND_V(interface == nullptr, 0.f); return interface->shaped_text_fit_to_width(data, (godot_rid *)&p_shaped, p_width, p_jst_flags); } -float TextServerGDNative::shaped_text_tab_align(RID p_shaped, const Vector<float> &p_tab_stops) { +real_t TextServerGDNative::shaped_text_tab_align(RID p_shaped, const Vector<real_t> &p_tab_stops) { ERR_FAIL_COND_V(interface == nullptr, 0.f); return interface->shaped_text_tab_align(data, (godot_rid *)&p_shaped, (godot_packed_float32_array *)&p_tab_stops); } @@ -498,7 +678,7 @@ bool TextServerGDNative::shaped_text_update_justification_ops(RID p_shaped) { return interface->shaped_text_update_justification_ops(data, (godot_rid *)&p_shaped); } -void TextServerGDNative::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_trim_flags) { +void TextServerGDNative::shaped_text_overrun_trim_to_width(RID p_shaped_line, real_t p_width, uint8_t p_trim_flags) { ERR_FAIL_COND(interface == nullptr); interface->shaped_text_overrun_trim_to_width(data, (godot_rid *)&p_shaped_line, p_width, p_trim_flags); }; @@ -531,7 +711,7 @@ Vector<TextServer::Glyph> TextServerGDNative::shaped_text_sort_logical(RID p_sha return glyphs; } -Vector<Vector2i> TextServerGDNative::shaped_text_get_line_breaks_adv(RID p_shaped, const Vector<float> &p_width, int p_start, bool p_once, uint8_t p_break_flags) const { +Vector<Vector2i> TextServerGDNative::shaped_text_get_line_breaks_adv(RID p_shaped, const Vector<real_t> &p_width, int p_start, bool p_once, uint8_t p_break_flags) const { ERR_FAIL_COND_V(interface == nullptr, Vector<Vector2i>()); if (interface->shaped_text_get_line_breaks_adv != nullptr) { godot_packed_vector2i_array result = interface->shaped_text_get_line_breaks_adv(data, (godot_rid *)&p_shaped, (godot_packed_float32_array *)&p_width, p_start, p_once, p_break_flags); @@ -543,7 +723,7 @@ Vector<Vector2i> TextServerGDNative::shaped_text_get_line_breaks_adv(RID p_shape } } -Vector<Vector2i> TextServerGDNative::shaped_text_get_line_breaks(RID p_shaped, float p_width, int p_start, uint8_t p_break_flags) const { +Vector<Vector2i> TextServerGDNative::shaped_text_get_line_breaks(RID p_shaped, real_t p_width, int p_start, uint8_t p_break_flags) const { ERR_FAIL_COND_V(interface == nullptr, Vector<Vector2i>()); if (interface->shaped_text_get_line_breaks != nullptr) { godot_packed_vector2i_array result = interface->shaped_text_get_line_breaks(data, (godot_rid *)&p_shaped, p_width, p_start, p_break_flags); @@ -588,27 +768,27 @@ Size2 TextServerGDNative::shaped_text_get_size(RID p_shaped) const { return size; } -float TextServerGDNative::shaped_text_get_ascent(RID p_shaped) const { +real_t TextServerGDNative::shaped_text_get_ascent(RID p_shaped) const { ERR_FAIL_COND_V(interface == nullptr, 0.f); return interface->shaped_text_get_ascent(data, (godot_rid *)&p_shaped); } -float TextServerGDNative::shaped_text_get_descent(RID p_shaped) const { +real_t TextServerGDNative::shaped_text_get_descent(RID p_shaped) const { ERR_FAIL_COND_V(interface == nullptr, 0.f); return interface->shaped_text_get_descent(data, (godot_rid *)&p_shaped); } -float TextServerGDNative::shaped_text_get_width(RID p_shaped) const { +real_t TextServerGDNative::shaped_text_get_width(RID p_shaped) const { ERR_FAIL_COND_V(interface == nullptr, 0.f); return interface->shaped_text_get_width(data, (godot_rid *)&p_shaped); } -float TextServerGDNative::shaped_text_get_underline_position(RID p_shaped) const { +real_t TextServerGDNative::shaped_text_get_underline_position(RID p_shaped) const { ERR_FAIL_COND_V(interface == nullptr, 0.f); return interface->shaped_text_get_underline_position(data, (godot_rid *)&p_shaped); } -float TextServerGDNative::shaped_text_get_underline_thickness(RID p_shaped) const { +real_t TextServerGDNative::shaped_text_get_underline_thickness(RID p_shaped) const { ERR_FAIL_COND_V(interface == nullptr, 0.f); return interface->shaped_text_get_underline_thickness(data, (godot_rid *)&p_shaped); } @@ -756,12 +936,12 @@ void GDAPI godot_glyph_set_offset(godot_glyph *p_self, const godot_vector2 *p_of self->y_off = offset->y; } -godot_float GDAPI godot_glyph_get_advance(const godot_glyph *p_self) { +godot_real_t GDAPI godot_glyph_get_advance(const godot_glyph *p_self) { const TextServer::Glyph *self = (const TextServer::Glyph *)p_self; return self->advance; } -void GDAPI godot_glyph_set_advance(godot_glyph *p_self, godot_float p_advance) { +void GDAPI godot_glyph_set_advance(godot_glyph *p_self, godot_real_t p_advance) { TextServer::Glyph *self = (TextServer::Glyph *)p_self; self->advance = p_advance; } diff --git a/modules/gdnative/text/text_server_gdnative.h b/modules/gdnative/text/text_server_gdnative.h index e1b36f4264..f081637e23 100644 --- a/modules/gdnative/text/text_server_gdnative.h +++ b/modules/gdnative/text/text_server_gdnative.h @@ -60,78 +60,130 @@ public: virtual bool is_locale_right_to_left(const String &p_locale) override; + virtual int32_t name_to_tag(const String &p_name) const override; + virtual String tag_to_name(int32_t p_tag) const override; + /* Font interface */ - virtual RID create_font_system(const String &p_name, int p_base_size = 16) override; - virtual RID create_font_resource(const String &p_filename, int p_base_size = 16) override; - virtual RID create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size = 16) override; - virtual RID create_font_bitmap(float p_height, float p_ascent, int p_base_size = 16) override; + virtual RID create_font() override; + + virtual void font_set_data(RID p_font_rid, const PackedByteArray &p_data) override; + virtual void font_set_data_ptr(RID p_font_rid, const uint8_t *p_data_ptr, size_t p_data_size) override; + + virtual void font_set_antialiased(RID p_font_rid, bool p_antialiased) override; + virtual bool font_is_antialiased(RID p_font_rid) const override; + + virtual void font_set_multichannel_signed_distance_field(RID p_font_rid, bool p_msdf) override; + virtual bool font_is_multichannel_signed_distance_field(RID p_font_rid) const override; + + virtual void font_set_msdf_pixel_range(RID p_font_rid, int p_msdf_pixel_range) override; + virtual int font_get_msdf_pixel_range(RID p_font_rid) const override; + + virtual void font_set_msdf_size(RID p_font_rid, int p_msdf_size) override; + virtual int font_get_msdf_size(RID p_font_rid) const override; + + virtual void font_set_fixed_size(RID p_font_rid, int p_fixed_size) override; + virtual int font_get_fixed_size(RID p_font_rid) const override; + + virtual void font_set_force_autohinter(RID p_font_rid, bool p_force_autohinter) override; + virtual bool font_is_force_autohinter(RID p_font_rid) const override; + + virtual void font_set_hinting(RID p_font_rid, TextServer::Hinting p_hinting) override; + virtual TextServer::Hinting font_get_hinting(RID p_font_rid) const override; + + virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override; + virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override; + + virtual void font_set_oversampling(RID p_font_rid, real_t p_oversampling) override; + virtual real_t font_get_oversampling(RID p_font_rid) const override; + + virtual Array font_get_size_cache_list(RID p_font_rid) const override; + virtual void font_clear_size_cache(RID p_font_rid) override; + virtual void font_remove_size_cache(RID p_font_rid, const Vector2i &p_size) override; + + virtual void font_set_ascent(RID p_font_rid, int p_size, real_t p_ascent) override; + virtual real_t font_get_ascent(RID p_font_rid, int p_size) const override; + + virtual void font_set_descent(RID p_font_rid, int p_size, real_t p_descent) override; + virtual real_t font_get_descent(RID p_font_rid, int p_size) const override; + + virtual void font_set_underline_position(RID p_font_rid, int p_size, real_t p_underline_position) override; + virtual real_t font_get_underline_position(RID p_font_rid, int p_size) const override; + + virtual void font_set_underline_thickness(RID p_font_rid, int p_size, real_t p_underline_thickness) override; + virtual real_t font_get_underline_thickness(RID p_font_rid, int p_size) const override; + + virtual void font_set_scale(RID p_font_rid, int p_size, real_t p_scale) override; + virtual real_t font_get_scale(RID p_font_rid, int p_size) const override; + + virtual void font_set_spacing(RID p_font_rid, int p_size, SpacingType p_spacing, int p_value) override; + virtual int font_get_spacing(RID p_font_rid, int p_size, SpacingType p_spacing) const override; - virtual void font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) override; - virtual void font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) override; - virtual void font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) override; + virtual int font_get_texture_count(RID p_font_rid, const Vector2i &p_size) const override; + virtual void font_clear_textures(RID p_font_rid, const Vector2i &p_size) override; + virtual void font_remove_texture(RID p_font_rid, const Vector2i &p_size, int p_texture_index) override; - virtual float font_get_height(RID p_font, int p_size) const override; - virtual float font_get_ascent(RID p_font, int p_size) const override; - virtual float font_get_descent(RID p_font, int p_size) const override; + virtual void font_set_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) override; + virtual Ref<Image> font_get_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const override; - virtual float font_get_underline_position(RID p_font, int p_size) const override; - virtual float font_get_underline_thickness(RID p_font, int p_size) const override; + virtual void font_set_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) override; + virtual PackedInt32Array font_get_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const override; - virtual int font_get_spacing_space(RID p_font) const override; - virtual void font_set_spacing_space(RID p_font, int p_value) override; + virtual Array font_get_glyph_list(RID p_font_rid, const Vector2i &p_size) const override; + virtual void font_clear_glyphs(RID p_font_rid, const Vector2i &p_size) override; + virtual void font_remove_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) override; - virtual int font_get_spacing_glyph(RID p_font) const override; - virtual void font_set_spacing_glyph(RID p_font, int p_value) override; + virtual Vector2 font_get_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph, const Vector2 &p_advance) override; - virtual void font_set_antialiased(RID p_font, bool p_antialiased) override; - virtual bool font_get_antialiased(RID p_font) const override; + virtual Vector2 font_get_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) override; - virtual Dictionary font_get_feature_list(RID p_font) const override; - virtual Dictionary font_get_variation_list(RID p_font) const override; + virtual Vector2 font_get_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) override; - virtual void font_set_variation(RID p_font, const String &p_name, double p_value) override; - virtual double font_get_variation(RID p_font, const String &p_name) const override; + virtual Rect2 font_get_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) override; - virtual void font_set_hinting(RID p_font, Hinting p_hinting) override; - virtual Hinting font_get_hinting(RID p_font) const override; + virtual int font_get_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) override; - virtual void font_set_distance_field_hint(RID p_font, bool p_distance_field) override; - virtual bool font_get_distance_field_hint(RID p_font) const override; + virtual bool font_get_glyph_contours(RID p_font, int p_size, int32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; - virtual void font_set_force_autohinter(RID p_font, bool p_enabeld) override; - virtual bool font_get_force_autohinter(RID p_font) const override; + virtual Array font_get_kerning_list(RID p_font_rid, int p_size) const override; + virtual void font_clear_kerning_map(RID p_font_rid, int p_size) override; + virtual void font_remove_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) override; - virtual bool font_has_char(RID p_font, char32_t p_char) const override; - virtual String font_get_supported_chars(RID p_font) const override; + virtual void font_set_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) override; + virtual Vector2 font_get_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) const override; - virtual bool font_has_outline(RID p_font) const override; - virtual float font_get_base_size(RID p_font) const override; + virtual int32_t font_get_glyph_index(RID p_font_rid, int p_size, char32_t p_char, char32_t p_variation_selector = 0) const override; - virtual bool font_is_language_supported(RID p_font, const String &p_language) const override; - virtual void font_set_language_support_override(RID p_font, const String &p_language, bool p_supported) override; - virtual bool font_get_language_support_override(RID p_font, const String &p_language) override; - virtual void font_remove_language_support_override(RID p_font, const String &p_language) override; - Vector<String> font_get_language_support_overrides(RID p_font) override; + virtual bool font_has_char(RID p_font_rid, char32_t p_char) const override; + virtual String font_get_supported_chars(RID p_font_rid) const override; - virtual bool font_is_script_supported(RID p_font, const String &p_script) const override; - virtual void font_set_script_support_override(RID p_font, const String &p_script, bool p_supported) override; - virtual bool font_get_script_support_override(RID p_font, const String &p_script) override; - virtual void font_remove_script_support_override(RID p_font, const String &p_script) override; - Vector<String> font_get_script_support_overrides(RID p_font) override; + virtual void font_render_range(RID p_font, const Vector2i &p_size, char32_t p_start, char32_t p_end) override; + virtual void font_render_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_index) override; - virtual uint32_t font_get_glyph_index(RID p_font, char32_t p_char, char32_t p_variation_selector = 0x0000) const override; - virtual Vector2 font_get_glyph_advance(RID p_font, uint32_t p_index, int p_size) const override; - virtual Vector2 font_get_glyph_kerning(RID p_font, uint32_t p_index_a, uint32_t p_index_b, int p_size) const override; + virtual void font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; + virtual void font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; - virtual Vector2 font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; - virtual Vector2 font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; + virtual bool font_is_language_supported(RID p_font_rid, const String &p_language) const override; + virtual void font_set_language_support_override(RID p_font_rid, const String &p_language, bool p_supported) override; + virtual bool font_get_language_support_override(RID p_font_rid, const String &p_language) override; + virtual void font_remove_language_support_override(RID p_font_rid, const String &p_language) override; + virtual Vector<String> font_get_language_support_overrides(RID p_font_rid) override; - virtual bool font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; + virtual bool font_is_script_supported(RID p_font_rid, const String &p_script) const override; + virtual void font_set_script_support_override(RID p_font_rid, const String &p_script, bool p_supported) override; + virtual bool font_get_script_support_override(RID p_font_rid, const String &p_script) override; + virtual void font_remove_script_support_override(RID p_font_rid, const String &p_script) override; + virtual Vector<String> font_get_script_support_overrides(RID p_font_rid) override; - virtual float font_get_oversampling() const override; - virtual void font_set_oversampling(float p_oversampling) override; + virtual Dictionary font_supported_feature_list(RID p_font_rid) const override; + virtual Dictionary font_supported_variation_list(RID p_font_rid) const override; - virtual Vector<String> get_system_fonts() const override; + virtual real_t font_get_global_oversampling() const override; + virtual void font_set_global_oversampling(real_t p_oversampling) override; /* Shaped text buffer interface */ @@ -160,14 +212,14 @@ public: virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; - virtual float shaped_text_fit_to_width(RID p_shaped, float p_width, uint8_t /*JustificationFlag*/ p_jst_flags = JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA) override; - virtual float shaped_text_tab_align(RID p_shaped, const Vector<float> &p_tab_stops) override; + virtual real_t shaped_text_fit_to_width(RID p_shaped, real_t p_width, uint8_t /*JustificationFlag*/ p_jst_flags = JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA) override; + virtual real_t shaped_text_tab_align(RID p_shaped, const Vector<real_t> &p_tab_stops) override; virtual bool shaped_text_shape(RID p_shaped) override; virtual bool shaped_text_update_breaks(RID p_shaped) override; virtual bool shaped_text_update_justification_ops(RID p_shaped) override; - virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_trim_flags) override; + virtual void shaped_text_overrun_trim_to_width(RID p_shaped, real_t p_width, uint8_t p_trim_flags) override; virtual bool shaped_text_is_ready(RID p_shaped) const override; @@ -176,18 +228,18 @@ public: virtual Vector2i shaped_text_get_range(RID p_shaped) const override; virtual Vector<Glyph> shaped_text_sort_logical(RID p_shaped) override; - virtual Vector<Vector2i> shaped_text_get_line_breaks_adv(RID p_shaped, const Vector<float> &p_width, int p_start = 0, bool p_once = true, uint8_t /*TextBreakFlag*/ p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const override; - virtual Vector<Vector2i> shaped_text_get_line_breaks(RID p_shaped, float p_width, int p_start = 0, uint8_t p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const override; + virtual Vector<Vector2i> shaped_text_get_line_breaks_adv(RID p_shaped, const Vector<real_t> &p_width, int p_start = 0, bool p_once = true, uint8_t /*TextBreakFlag*/ p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const override; + virtual Vector<Vector2i> shaped_text_get_line_breaks(RID p_shaped, real_t p_width, int p_start = 0, uint8_t p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const override; virtual Vector<Vector2i> shaped_text_get_word_breaks(RID p_shaped, int p_grapheme_flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_PUNCTUATION) const override; virtual Array shaped_text_get_objects(RID p_shaped) const override; virtual Rect2 shaped_text_get_object_rect(RID p_shaped, Variant p_key) const override; virtual Size2 shaped_text_get_size(RID p_shaped) const override; - virtual float shaped_text_get_ascent(RID p_shaped) const override; - virtual float shaped_text_get_descent(RID p_shaped) const override; - virtual float shaped_text_get_width(RID p_shaped) const override; - virtual float shaped_text_get_underline_position(RID p_shaped) const override; - virtual float shaped_text_get_underline_thickness(RID p_shaped) const override; + virtual real_t shaped_text_get_ascent(RID p_shaped) const override; + virtual real_t shaped_text_get_descent(RID p_shaped) const override; + virtual real_t shaped_text_get_width(RID p_shaped) const override; + virtual real_t shaped_text_get_underline_position(RID p_shaped) const override; + virtual real_t shaped_text_get_underline_thickness(RID p_shaped) const override; virtual String format_number(const String &p_string, const String &p_language = "") const override; virtual String parse_number(const String &p_string, const String &p_language = "") const override; diff --git a/modules/gdnative/xr/SCsub b/modules/gdnative/xr/SCsub deleted file mode 100644 index 0b2db3b504..0000000000 --- a/modules/gdnative/xr/SCsub +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -Import("env") -Import("env_gdnative") - -env_gdnative.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/gdnative/xr/xr_interface_gdnative.cpp b/modules/gdnative/xr/xr_interface_gdnative.cpp deleted file mode 100644 index e51542e23d..0000000000 --- a/modules/gdnative/xr/xr_interface_gdnative.cpp +++ /dev/null @@ -1,450 +0,0 @@ -/*************************************************************************/ -/* xr_interface_gdnative.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 "xr_interface_gdnative.h" -#include "core/input/input.h" -#include "servers/rendering/rendering_server_globals.h" -#include "servers/xr/xr_positional_tracker.h" - -void XRInterfaceGDNative::_bind_methods() { - ADD_PROPERTY_DEFAULT("interface_is_initialized", false); - ADD_PROPERTY_DEFAULT("ar_is_anchor_detection_enabled", false); -} - -XRInterfaceGDNative::XRInterfaceGDNative() { - print_verbose("Construct gdnative interface\n"); - - // we won't have our data pointer until our library gets set - data = nullptr; - - interface = nullptr; -} - -XRInterfaceGDNative::~XRInterfaceGDNative() { - print_verbose("Destruct gdnative interface\n"); - - if (interface != nullptr && is_initialized()) { - uninitialize(); - }; - - // cleanup after ourselves - cleanup(); -} - -void XRInterfaceGDNative::cleanup() { - if (interface != nullptr) { - interface->destructor(data); - data = nullptr; - interface = nullptr; - } -} - -void XRInterfaceGDNative::set_interface(const godot_xr_interface_gdnative *p_interface) { - // this should only be called once, just being paranoid.. - if (interface) { - cleanup(); - interface = NULL; - } - - // validate - ERR_FAIL_NULL(p_interface); - ERR_FAIL_COND_MSG(p_interface->version.major < 4, "This is an incompatible GDNative XR plugin."); - - // bind to our interface - interface = p_interface; - - // Now we do our constructing... - data = interface->constructor((godot_object *)this); -} - -StringName XRInterfaceGDNative::get_name() const { - ERR_FAIL_COND_V(interface == nullptr, StringName()); - - godot_string result = interface->get_name(data); - - StringName name = *(String *)&result; - - godot_string_destroy(&result); - - return name; -} - -int XRInterfaceGDNative::get_capabilities() const { - int capabilities; - - ERR_FAIL_COND_V(interface == nullptr, 0); // 0 = None - - capabilities = interface->get_capabilities(data); - - return capabilities; -} - -bool XRInterfaceGDNative::get_anchor_detection_is_enabled() const { - ERR_FAIL_COND_V(interface == nullptr, false); - - return interface->get_anchor_detection_is_enabled(data); -} - -void XRInterfaceGDNative::set_anchor_detection_is_enabled(bool p_enable) { - ERR_FAIL_COND(interface == nullptr); - - interface->set_anchor_detection_is_enabled(data, p_enable); -} - -int XRInterfaceGDNative::get_camera_feed_id() { - ERR_FAIL_COND_V(interface == nullptr, 0); - - return (unsigned int)interface->get_camera_feed_id(data); -} - -uint32_t XRInterfaceGDNative::get_view_count() { - uint32_t view_count; - - ERR_FAIL_COND_V(interface == nullptr, 1); - - view_count = interface->get_view_count(data); - - return view_count; -} - -bool XRInterfaceGDNative::is_initialized() const { - ERR_FAIL_COND_V(interface == nullptr, false); - - return interface->is_initialized(data); -} - -bool XRInterfaceGDNative::initialize() { - ERR_FAIL_COND_V(interface == nullptr, false); - - bool initialized = interface->initialize(data); - - if (initialized) { - // if we successfully initialize our interface and we don't have a primary interface yet, this becomes our primary interface - - XRServer *xr_server = XRServer::get_singleton(); - if ((xr_server != nullptr) && (xr_server->get_primary_interface() == nullptr)) { - xr_server->set_primary_interface(this); - }; - }; - - return initialized; -} - -void XRInterfaceGDNative::uninitialize() { - ERR_FAIL_COND(interface == nullptr); - - XRServer *xr_server = XRServer::get_singleton(); - if (xr_server != nullptr) { - // Whatever happens, make sure this is no longer our primary interface - xr_server->clear_primary_interface_if(this); - } - - interface->uninitialize(data); -} - -Size2 XRInterfaceGDNative::get_render_targetsize() { - ERR_FAIL_COND_V(interface == nullptr, Size2()); - - godot_vector2 result = interface->get_render_targetsize(data); - Vector2 *vec = (Vector2 *)&result; - - return *vec; -} - -Transform3D XRInterfaceGDNative::get_camera_transform() { - Transform3D *ret; - - ERR_FAIL_COND_V(interface == nullptr, Transform3D()); - - godot_transform3d t = interface->get_camera_transform(data); - - ret = (Transform3D *)&t; - - return *ret; -} - -Transform3D XRInterfaceGDNative::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) { - Transform3D *ret; - - ERR_FAIL_COND_V(interface == nullptr, Transform3D()); - - godot_transform3d t = interface->get_transform_for_view(data, (int)p_view, (godot_transform3d *)&p_cam_transform); - - ret = (Transform3D *)&t; - - return *ret; -} - -CameraMatrix XRInterfaceGDNative::get_projection_for_view(uint32_t p_view, real_t p_aspect, real_t p_z_near, real_t p_z_far) { - CameraMatrix cm; - - ERR_FAIL_COND_V(interface == nullptr, CameraMatrix()); - - interface->fill_projection_for_view(data, (godot_real_t *)cm.matrix, (godot_int)p_view, p_aspect, p_z_near, p_z_far); - - return cm; -} - -Vector<BlitToScreen> XRInterfaceGDNative::commit_views(RID p_render_target, const Rect2 &p_screen_rect) { - // possibly move this as a member variable and add a callback to populate? - Vector<BlitToScreen> blit_to_screen; - - ERR_FAIL_COND_V(interface == nullptr, blit_to_screen); - - // must implement - interface->commit_views(data, (godot_rid *)&p_render_target, (godot_rect2 *)&p_screen_rect); - - return blit_to_screen; -} - -unsigned int XRInterfaceGDNative::get_external_texture_for_eye(XRInterface::Eyes p_eye) { - ERR_FAIL_COND_V(interface == nullptr, 0); - - return (unsigned int)interface->get_external_texture_for_eye(data, (godot_int)p_eye); -} - -void XRInterfaceGDNative::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) { - ERR_FAIL_COND(interface == nullptr); - - interface->commit_for_eye(data, (godot_int)p_eye, (godot_rid *)&p_render_target, (godot_rect2 *)&p_screen_rect); -} - -void XRInterfaceGDNative::process() { - ERR_FAIL_COND(interface == nullptr); - - interface->process(data); -} - -void XRInterfaceGDNative::notification(int p_what) { - ERR_FAIL_COND(interface == nullptr); - - interface->notification(data, p_what); -} - -///////////////////////////////////////////////////////////////////////////////////// -// some helper callbacks - -extern "C" { - -void GDAPI godot_xr_register_interface(const godot_xr_interface_gdnative *p_interface) { - // Must be on a version 4 plugin - ERR_FAIL_COND_MSG(p_interface->version.major < 4, "GDNative XR interfaces build for Godot 3.x are not supported."); - - Ref<XRInterfaceGDNative> new_interface; - new_interface.instantiate(); - new_interface->set_interface((const godot_xr_interface_gdnative *)p_interface); - XRServer::get_singleton()->add_interface(new_interface); -} - -godot_real_t GDAPI godot_xr_get_worldscale() { - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL_V(xr_server, 1.0); - - return xr_server->get_world_scale(); -} - -godot_transform3d GDAPI godot_xr_get_reference_frame() { - godot_transform3d reference_frame; - Transform3D *reference_frame_ptr = (Transform3D *)&reference_frame; - - XRServer *xr_server = XRServer::get_singleton(); - if (xr_server != nullptr) { - *reference_frame_ptr = xr_server->get_reference_frame(); - } else { - memnew_placement(&reference_frame, Transform3D); - } - - return reference_frame; -} - -void GDAPI godot_xr_blit(godot_int p_eye, godot_rid *p_render_target, godot_rect2 *p_rect) { - // blits out our texture as is, handy for preview display of one of the eyes that is already rendered with lens distortion on an external HMD - XRInterface::Eyes eye = (XRInterface::Eyes)p_eye; -#if 0 - RID *render_target = (RID *)p_render_target; -#endif - Rect2 screen_rect = *(Rect2 *)p_rect; - - if (eye == XRInterface::EYE_LEFT) { - screen_rect.size.x /= 2.0; - } else if (p_eye == XRInterface::EYE_RIGHT) { - screen_rect.size.x /= 2.0; - screen_rect.position.x += screen_rect.size.x; - } -#ifndef _MSC_VER -#warning this needs to be redone -#endif -#if 0 - RSG::rasterizer->blit_render_target_to_screen(*render_target, screen_rect, 0); -#endif -} - -godot_int GDAPI godot_xr_get_texid(godot_rid *p_render_target) { - // In order to send off our textures to display on our hardware we need the opengl texture ID instead of the render target RID - // This is a handy function to expose that. -#if 0 - RID *render_target = (RID *)p_render_target; - - RID eye_texture = RSG::storage->render_target_get_texture(*render_target); -#endif - -#ifndef _MSC_VER -#warning need to obtain this ID again -#endif - uint32_t texid = 0; //RS::get_singleton()->texture_get_texid(eye_texture); - - return texid; -} - -godot_int GDAPI godot_xr_add_controller(char *p_device_name, godot_int p_hand, godot_bool p_tracks_orientation, godot_bool p_tracks_position) { - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL_V(xr_server, 0); - - Input *input = Input::get_singleton(); - ERR_FAIL_NULL_V(input, 0); - - Ref<XRPositionalTracker> new_tracker; - new_tracker.instantiate(); - new_tracker->set_tracker_name(p_device_name); - new_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); - if (p_hand == 1) { - new_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); - } else if (p_hand == 2) { - new_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); - } - - // also register as joystick... - int joyid = input->get_unused_joy_id(); - if (joyid != -1) { - new_tracker->set_joy_id(joyid); - input->joy_connection_changed(joyid, true, p_device_name, ""); - } - - if (p_tracks_orientation) { - Basis orientation; - new_tracker->set_orientation(orientation); - } - if (p_tracks_position) { - Vector3 position; - new_tracker->set_position(position); - } - - // add our tracker to our server and remember its pointer - xr_server->add_tracker(new_tracker); - - // note, this ID is only unique within controllers! - return new_tracker->get_tracker_id(); -} - -void GDAPI godot_xr_remove_controller(godot_int p_controller_id) { - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL(xr_server); - - Input *input = Input::get_singleton(); - ERR_FAIL_NULL(input); - - Ref<XRPositionalTracker> remove_tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, p_controller_id); - if (remove_tracker.is_valid()) { - // unset our joystick if applicable - int joyid = remove_tracker->get_joy_id(); - if (joyid != -1) { - input->joy_connection_changed(joyid, false, "", ""); - remove_tracker->set_joy_id(-1); - } - - // remove our tracker from our server - xr_server->remove_tracker(remove_tracker); - remove_tracker.unref(); - } -} - -void GDAPI godot_xr_set_controller_transform(godot_int p_controller_id, godot_transform3d *p_transform, godot_bool p_tracks_orientation, godot_bool p_tracks_position) { - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL(xr_server); - - Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, p_controller_id); - if (tracker.is_valid()) { - Transform3D *transform = (Transform3D *)p_transform; - if (p_tracks_orientation) { - tracker->set_orientation(transform->basis); - } - if (p_tracks_position) { - tracker->set_rw_position(transform->origin); - } - } -} - -void GDAPI godot_xr_set_controller_button(godot_int p_controller_id, godot_int p_button, godot_bool p_is_pressed) { - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL(xr_server); - - Input *input = Input::get_singleton(); - ERR_FAIL_NULL(input); - - Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, p_controller_id); - if (tracker.is_valid()) { - int joyid = tracker->get_joy_id(); - if (joyid != -1) { - input->joy_button(joyid, (JoyButton)p_button, p_is_pressed); - } - } -} - -void GDAPI godot_xr_set_controller_axis(godot_int p_controller_id, godot_int p_axis, godot_real_t p_value, godot_bool p_can_be_negative) { - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL(xr_server); - - Input *input = Input::get_singleton(); - ERR_FAIL_NULL(input); - - Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, p_controller_id); - if (tracker.is_valid()) { - int joyid = tracker->get_joy_id(); - if (joyid != -1) { - Input::JoyAxisValue jx; - jx.min = p_can_be_negative ? -1 : 0; - jx.value = p_value; - input->joy_axis(joyid, (JoyAxis)p_axis, jx); - } - } -} - -godot_real_t GDAPI godot_xr_get_controller_rumble(godot_int p_controller_id) { - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL_V(xr_server, 0.0); - - Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, p_controller_id); - if (tracker.is_valid()) { - return tracker->get_rumble(); - } - - return 0.0; -} -} diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 8b9bd2659d..ab441d194a 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -471,9 +471,9 @@ void GDScriptSyntaxHighlighter::_update_cache() { } /* Autoloads. */ - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->value(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.value(); if (info.is_singleton) { keywords[info.name] = usertype_color; } diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 4589cf1a36..bc8801b8b9 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -87,6 +87,19 @@ Object *GDScriptNativeClass::instantiate() { return ClassDB::instantiate(name); } +GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) { + if (p_script->initializer) { + return p_script->initializer; + } else { + GDScript *base = p_script->_base; + if (base != nullptr) { + return _super_constructor(base); + } else { + return nullptr; + } + } +} + void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) { GDScript *base = p_script->_base; if (base != nullptr) { @@ -135,6 +148,8 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco if (p_argcount < 0) { return instance; } + + initializer = _super_constructor(this); if (initializer != nullptr) { initializer->call(instance, p_args, p_argcount, r_error); if (r_error.error != Callable::CallError::CALL_OK) { @@ -625,9 +640,9 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc String path = ""; if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) { path = c->extends_path; - if (path.is_rel_path()) { + if (path.is_relative_path()) { String base = get_path(); - if (base == "" || base.is_rel_path()) { + if (base == "" || base.is_relative_path()) { ERR_PRINT(("Could not resolve relative path for parent class: " + path).utf8().get_data()); } else { path = base.get_base_dir().plus_file(path); @@ -900,7 +915,7 @@ void GDScript::get_members(Set<StringName> *p_members) { } } -const Vector<MultiplayerAPI::RPCConfig> GDScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> GDScript::get_rpc_methods() const { return rpc_functions; } @@ -1164,8 +1179,8 @@ void GDScript::_init_rpc_methods_properties() { while (cscript) { // RPC Methods for (Map<StringName, GDScriptFunction *>::Element *E = cscript->member_functions.front(); E; E = E->next()) { - MultiplayerAPI::RPCConfig config = E->get()->get_rpc_config(); - if (config.rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + Multiplayer::RPCConfig config = E->get()->get_rpc_config(); + if (config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { config.name = E->get()->get_name(); if (rpc_functions.find(config) == -1) { rpc_functions.push_back(config); @@ -1185,7 +1200,7 @@ void GDScript::_init_rpc_methods_properties() { } // Sort so we are 100% that they are always the same. - rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>(); + rpc_functions.sort_custom<Multiplayer::SortRPCConfig>(); } GDScript::~GDScript() { @@ -1541,7 +1556,7 @@ ScriptLanguage *GDScriptInstance::get_language() { return GDScriptLanguage::get_singleton(); } -const Vector<MultiplayerAPI::RPCConfig> GDScriptInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> GDScriptInstance::get_rpc_methods() const { return script->get_rpc_methods(); } @@ -2059,7 +2074,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b if (r_icon_path) { if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) { *r_icon_path = c->icon_path; - } else if (c->icon_path.is_rel_path()) { + } else if (c->icon_path.is_relative_path()) { *r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path(); } } @@ -2087,7 +2102,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b break; } String subpath = subclass->extends_path; - if (subpath.is_rel_path()) { + if (subpath.is_relative_path()) { subpath = path.get_base_dir().plus_file(subpath).simplify_path(); } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 24809ad5fd..791f8a1431 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -85,7 +85,7 @@ class GDScript : public Script { Map<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script. Map<StringName, Ref<GDScript>> subclasses; Map<StringName, Vector<StringName>> _signals; - Vector<MultiplayerAPI::RPCConfig> rpc_functions; + Vector<Multiplayer::RPCConfig> rpc_functions; #ifdef TOOLS_ENABLED @@ -130,6 +130,7 @@ class GDScript : public Script { SelfList<GDScriptFunctionState>::List pending_func_states; + GDScriptFunction *_super_constructor(GDScript *p_script); void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error); GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); @@ -245,7 +246,7 @@ public: virtual void get_constants(Map<StringName, Variant> *p_constants) override; virtual void get_members(Set<StringName> *p_members) override; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; #ifdef TOOLS_ENABLED virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } @@ -298,7 +299,7 @@ public: void reload_members(); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const; GDScriptInstance(); ~GDScriptInstance(); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 6b7403d854..fc0bef3ba2 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -132,6 +132,76 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { return type; } +bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class) { + if (p_class->members_indices.has(p_member_name)) { + int index = p_class->members_indices[p_member_name]; + const GDScriptParser::ClassNode::Member *member = &p_class->members[index]; + + if (member->type == GDScriptParser::ClassNode::Member::VARIABLE || + member->type == GDScriptParser::ClassNode::Member::CONSTANT || + member->type == GDScriptParser::ClassNode::Member::ENUM || + member->type == GDScriptParser::ClassNode::Member::ENUM_VALUE || + member->type == GDScriptParser::ClassNode::Member::CLASS || + member->type == GDScriptParser::ClassNode::Member::SIGNAL) { + return true; + } + } + + return false; +} + +bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName &p_member_name, const StringName &p_native_type_string) { + if (ClassDB::has_signal(p_native_type_string, p_member_name)) { + return true; + } + if (ClassDB::has_property(p_native_type_string, p_member_name)) { + return true; + } + if (ClassDB::has_integer_constant(p_native_type_string, p_member_name)) { + return true; + } + + return false; +} + +Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string) { + if (has_member_name_conflict_in_native_type(p_member_name, p_native_type_string)) { + push_error(vformat(R"(Member "%s" redefined (original in native class '%s'))", p_member_name, p_native_type_string), p_member_node); + return ERR_PARSE_ERROR; + } + + if (class_exists(p_member_name)) { + push_error(vformat(R"(The class "%s" shadows a native class.)", p_member_name), p_member_node); + return ERR_PARSE_ERROR; + } + + return OK; +} + +Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node) { + const GDScriptParser::DataType *current_data_type = &p_class_node->base_type; + while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) { + GDScriptParser::ClassNode *current_class_node = current_data_type->class_type; + if (has_member_name_conflict_in_script_class(p_member_name, current_class_node)) { + push_error(vformat(R"(The member "%s" already exists in a parent class.)", p_member_name), + p_member_node); + return ERR_PARSE_ERROR; + } + current_data_type = ¤t_class_node->base_type; + } + + if (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::NATIVE) { + if (current_data_type->native_type != StringName("")) { + return check_native_member_name_conflict( + p_member_name, + p_member_node, + current_data_type->native_type); + } + } + + return OK; +} + Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { if (p_class->base_type.is_set()) { // Already resolved @@ -525,6 +595,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: { + check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); + GDScriptParser::DataType datatype; datatype.kind = GDScriptParser::DataType::VARIANT; datatype.type_source = GDScriptParser::DataType::UNDETECTED; @@ -598,6 +670,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } break; case GDScriptParser::ClassNode::Member::CONSTANT: { + check_class_member_name_conflict(p_class, member.constant->identifier->name, member.constant); + reduce_expression(member.constant->initializer); GDScriptParser::DataType specified_type; @@ -647,6 +721,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } break; case GDScriptParser::ClassNode::Member::SIGNAL: { + check_class_member_name_conflict(p_class, member.signal->identifier->name, member.signal); + for (int j = 0; j < member.signal->parameters.size(); j++) { GDScriptParser::DataType signal_type = resolve_datatype(member.signal->parameters[j]->datatype_specifier); signal_type.is_meta_type = false; @@ -666,6 +742,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } break; case GDScriptParser::ClassNode::Member::ENUM: { + check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum); + GDScriptParser::DataType enum_type; enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; enum_type.kind = GDScriptParser::DataType::ENUM; @@ -717,6 +795,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas break; case GDScriptParser::ClassNode::Member::ENUM_VALUE: { if (member.enum_value.custom_value) { + check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.custom_value); + current_enum = member.enum_value.parent_enum; reduce_expression(member.enum_value.custom_value); current_enum = nullptr; @@ -730,6 +810,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas member.enum_value.resolved = true; } } else { + check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.parent_enum); + if (member.enum_value.index > 0) { member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1; } else { @@ -742,7 +824,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas p_class->members.write[i].enum_value = member.enum_value; } break; case GDScriptParser::ClassNode::Member::CLASS: - break; // Done later. + check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class); + break; case GDScriptParser::ClassNode::Member::UNDEFINED: ERR_PRINT("Trying to resolve undefined member."); break; @@ -2411,6 +2494,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod case GDScriptParser::ClassNode::Member::VARIABLE: p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; p_identifier->variable_source = member.variable; + member.variable->usages += 1; break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); @@ -2727,7 +2811,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { } else { p_preload->resolved_path = p_preload->path->reduced_value; // TODO: Save this as script dependency. - if (p_preload->resolved_path.is_rel_path()) { + if (p_preload->resolved_path.is_relative_path()) { p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path); } p_preload->resolved_path = p_preload->resolved_path.simplify_path(); @@ -2753,6 +2837,9 @@ void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { } void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) { + if (p_subscript->base == nullptr) { + return; + } if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) { reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true); } else { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 32bf049fa1..2e17e15452 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -44,6 +44,12 @@ class GDScriptAnalyzer { const GDScriptParser::EnumNode *current_enum = nullptr; List<const GDScriptParser::LambdaNode *> lambda_stack; + // Tests for detecting invalid overloading of script members + static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node); + static _FORCE_INLINE_ bool has_member_name_conflict_in_native_type(const StringName &p_name, const StringName &p_native_type_string); + Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string); + Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node); + Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 5958326315..bed67b55f0 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -155,7 +155,7 @@ void GDScriptByteCodeGenerator::end_parameters() { function->default_arguments.reverse(); } -void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) { +void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) { function = memnew(GDScriptFunction); debug_stack = EngineDebugger::is_active(); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 3d6fb291ad..ce1a043b28 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -419,7 +419,7 @@ public: virtual void start_block() override; virtual void end_block() override; - virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override; + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override; virtual GDScriptFunction *write_end() override; #ifdef DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index ecc86c37f3..7713d13bc8 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -31,7 +31,7 @@ #ifndef GDSCRIPT_CODEGEN #define GDSCRIPT_CODEGEN -#include "core/io/multiplayer_api.h" +#include "core/multiplayer/multiplayer.h" #include "core/string/string_name.h" #include "core/variant/variant.h" #include "gdscript_function.h" @@ -80,7 +80,7 @@ public: virtual void start_block() = 0; virtual void end_block() = 0; - virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0; + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0; virtual GDScriptFunction *write_end() = 0; #ifdef DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index ddf4f281b4..736f6eae79 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1857,7 +1857,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ StringName func_name; bool is_static = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; GDScriptDataType return_type; return_type.has_type = true; return_type.kind = GDScriptDataType::BUILTIN; @@ -2086,7 +2086,7 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script); } - codegen.generator->write_start(p_script, func_name, false, MultiplayerAPI::RPCConfig(), return_type); + codegen.generator->write_start(p_script, func_name, false, Multiplayer::RPCConfig(), return_type); if (p_is_setter) { uint32_t par_addr = codegen.generator->add_parameter(p_variable->setter_parameter->name, false, _gdtype_from_datatype(p_variable->get_datatype())); @@ -2186,6 +2186,13 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->tool = parser->is_tool(); p_script->name = p_class->identifier ? p_class->identifier->name : ""; + if (p_script->name != "") { + if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) { + _set_error("The class '" + p_script->name + "' shadows a native class", p_class); + return ERR_ALREADY_EXISTS; + } + } + Ref<GDScriptNativeClass> native; GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type); @@ -2337,28 +2344,6 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar const GDScriptParser::SignalNode *signal = member.signal; StringName name = signal->identifier->name; - GDScript *c = p_script; - - while (c) { - if (c->_signals.has(name)) { - _set_error("Signal '" + name + "' redefined (in current or parent class)", p_class); - return ERR_ALREADY_EXISTS; - } - - if (c->base.is_valid()) { - c = c->base.ptr(); - } else { - c = nullptr; - } - } - - if (native.is_valid()) { - if (ClassDB::has_signal(native->get_name(), name)) { - _set_error("Signal '" + name + "' redefined (original in native class '" + String(native->get_name()) + "')", p_class); - return ERR_ALREADY_EXISTS; - } - } - Vector<StringName> parameters_names; parameters_names.resize(signal->parameters.size()); for (int j = 0; j < signal->parameters.size(); j++) { diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 372a726d71..f809a4dab8 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -731,9 +731,9 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio } // Autoload singletons - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (!info.is_singleton || info.path.get_extension().to_lower() != "gd") { continue; } @@ -1086,12 +1086,12 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool kwa++; } - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) { - if (!E->value().is_singleton) { + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + if (!E.value().is_singleton) { continue; } - ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT); + ScriptCodeCompletionOption option(E.key(), ScriptCodeCompletionOption::KIND_CONSTANT); r_result.insert(option.display, option); } @@ -1359,12 +1359,12 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); found = true; } else { - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - String name = E->key(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + String name = E.key(); if (name == which) { - String script = E->value().path; + String script = E.value().path; if (!script.begins_with("res://")) { script = "res://" + script; @@ -2660,10 +2660,10 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } // Get autoloads. - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - String path = "/root/" + E->key(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + String path = "/root/" + E.key(); ScriptCodeCompletionOption option(path.quote(quote_style), ScriptCodeCompletionOption::KIND_NODE_PATH); options.insert(option.display, option); } @@ -3078,6 +3078,15 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co r_result.class_member = p_symbol; return OK; } + } else { + List<StringName> utility_functions; + Variant::get_utility_function_list(&utility_functions); + if (utility_functions.find(p_symbol) != nullptr) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_TBD_GLOBALSCOPE; + r_result.class_name = "@GlobalScope"; + r_result.class_member = p_symbol; + return OK; + } } } } break; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 87d8c03494..b21cb47910 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -468,7 +468,7 @@ private: int _initial_line = 0; bool _static = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; GDScript *_script = nullptr; @@ -588,7 +588,7 @@ public: void disassemble(const Vector<String> &p_code_lines) const; #endif - _FORCE_INLINE_ MultiplayerAPI::RPCConfig get_rpc_config() const { return rpc_config; } + _FORCE_INLINE_ Multiplayer::RPCConfig get_rpc_config() const { return rpc_config; } GDScriptFunction(); ~GDScriptFunction(); }; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index d21caf4389..6c3d4367e4 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -133,7 +133,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); // Networking. - register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>, 4, true); + register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true); // TODO: Warning annotations. } @@ -1117,7 +1117,6 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { } item.custom_value = value; } - item.rightmost_column = previous.rightmost_column; item.index = enum_node->values.size(); enum_node->values.push_back(item); @@ -1683,6 +1682,7 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { MatchBranchNode *branch = parse_match_branch(); if (branch == nullptr) { + advance(); continue; } @@ -1746,7 +1746,9 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { push_error(R"(No pattern found for "match" branch.)"); } - consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)"); + if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + return nullptr; + } // Save continue state. bool could_continue = can_continue; @@ -1779,15 +1781,6 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ PatternNode *pattern = alloc_node<PatternNode>(); switch (current.type) { - case GDScriptTokenizer::Token::LITERAL: - advance(); - pattern->pattern_type = PatternNode::PT_LITERAL; - pattern->literal = parse_literal(); - if (pattern->literal == nullptr) { - // Error happened. - return nullptr; - } - break; case GDScriptTokenizer::Token::VAR: { // Bind. advance(); @@ -1850,44 +1843,44 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ // Dictionary. advance(); pattern->pattern_type = PatternNode::PT_DICTIONARY; - - if (!check(GDScriptTokenizer::Token::BRACE_CLOSE) && !is_at_end()) { - do { - if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { - // Rest. + do { + if (check(GDScriptTokenizer::Token::BRACE_CLOSE) || is_at_end()) { + break; + } + if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { + // Rest. + if (pattern->rest_used) { + push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else { + PatternNode *sub_pattern = alloc_node<PatternNode>(); + sub_pattern->pattern_type = PatternNode::PT_REST; + pattern->dictionary.push_back({ nullptr, sub_pattern }); + pattern->rest_used = true; + } + } else { + ExpressionNode *key = parse_expression(false); + if (key == nullptr) { + push_error(R"(Expected expression as key for dictionary pattern.)"); + } + if (match(GDScriptTokenizer::Token::COLON)) { + // Value pattern. + PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); + if (sub_pattern == nullptr) { + continue; + } if (pattern->rest_used) { push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { + push_error(R"(The ".." pattern cannot be used as a value.)"); } else { - PatternNode *sub_pattern = alloc_node<PatternNode>(); - sub_pattern->pattern_type = PatternNode::PT_REST; - pattern->dictionary.push_back({ nullptr, sub_pattern }); - pattern->rest_used = true; + pattern->dictionary.push_back({ key, sub_pattern }); } } else { - ExpressionNode *key = parse_expression(false); - if (key == nullptr) { - push_error(R"(Expected expression as key for dictionary pattern.)"); - } - if (match(GDScriptTokenizer::Token::COLON)) { - // Value pattern. - PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); - if (sub_pattern == nullptr) { - continue; - } - if (pattern->rest_used) { - push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); - } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { - push_error(R"(The ".." pattern cannot be used as a value.)"); - } else { - pattern->dictionary.push_back({ key, sub_pattern }); - } - } else { - // Key match only. - pattern->dictionary.push_back({ key, nullptr }); - } + // Key match only. + pattern->dictionary.push_back({ key, nullptr }); } - } while (match(GDScriptTokenizer::Token::COMMA)); - } + } + } while (match(GDScriptTokenizer::Token::COMMA)); consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)"); break; } @@ -1896,8 +1889,13 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ ExpressionNode *expression = parse_expression(false); if (expression == nullptr) { push_error(R"(Expected expression for match pattern.)"); + return nullptr; } else { - pattern->pattern_type = PatternNode::PT_EXPRESSION; + if (expression->type == GDScriptParser::Node::LITERAL) { + pattern->pattern_type = PatternNode::PT_LITERAL; + } else { + pattern->pattern_type = PatternNode::PT_EXPRESSION; + } pattern->expression = expression; } break; @@ -2366,6 +2364,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } assignment->assignee = p_previous_operand; assignment->assigned_value = parse_expression(false); + if (assignment->assigned_value == nullptr) { + push_error(R"(Expected an expression after "=".)"); + } #ifdef DEBUG_ENABLED if (has_operator && source_variable != nullptr && source_variable->assignments == 0) { @@ -2378,9 +2379,15 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) { AwaitNode *await = alloc_node<AwaitNode>(); - await->to_await = parse_precedence(PREC_AWAIT, false); + ExpressionNode *element = parse_precedence(PREC_AWAIT, false); + if (element == nullptr) { + push_error(R"(Expected signal or coroutine after "await".)"); + } + await->to_await = element; - current_function->is_coroutine = true; + if (current_function) { // Might be null in a getter or setter. + current_function->is_coroutine = true; + } return await; } @@ -2457,8 +2464,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode push_error(R"(Expected "=" after dictionary key.)"); } } - key->is_constant = true; - key->reduced_value = static_cast<IdentifierNode *>(key)->name; + if (key != nullptr) { + key->is_constant = true; + key->reduced_value = static_cast<IdentifierNode *>(key)->name; + } break; case DictionaryNode::PYTHON_DICT: if (!match(GDScriptTokenizer::Token::COLON)) { @@ -2951,7 +2960,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & } else { /* Syntax: - @tutorial ( The Title Here ) : http://the.url/ + @tutorial ( The Title Here ) : https://the.url/ ^ open ^ close ^ colon ^ url */ int open_bracket_pos = begin_scan, close_bracket_pos = 0; @@ -3387,55 +3396,47 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod ERR_FAIL_V_MSG(false, "Not implemented."); } -template <MultiplayerAPI::RPCMode t_mode> +template <Multiplayer::RPCMode t_mode> bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) { ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name)); - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; rpc_config.rpc_mode = t_mode; - for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { - if (i == 0) { + if (p_annotation->resolved_arguments.size()) { + int last = p_annotation->resolved_arguments.size() - 1; + if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) { + rpc_config.channel = p_annotation->resolved_arguments[last].operator int(); + last -= 1; + } + if (last > 3) { + push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation); + return false; + } + for (int i = last; i >= 0; i--) { String mode = p_annotation->resolved_arguments[i].operator String(); if (mode == "any") { - rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE; - } else if (mode == "master") { - rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_MASTER; - } else if (mode == "puppet") { - rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_PUPPET; - } else { - push_error(R"(Invalid RPC mode. Must be one of: 'any', 'master', or 'puppet')", p_annotation); - return false; - } - } else if (i == 1) { - String sync = p_annotation->resolved_arguments[i].operator String(); - if (sync == "sync") { + rpc_config.rpc_mode = Multiplayer::RPC_MODE_ANY; + } else if (mode == "auth") { + rpc_config.rpc_mode = Multiplayer::RPC_MODE_AUTHORITY; + } else if (mode == "sync") { rpc_config.sync = true; - } else if (sync == "nosync") { + } else if (mode == "nosync") { rpc_config.sync = false; - } else { - push_error(R"(Invalid RPC sync mode. Must be one of: 'sync' or 'nosync')", p_annotation); - return false; - } - } else if (i == 2) { - String mode = p_annotation->resolved_arguments[i].operator String(); - if (mode == "reliable") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; + } else if (mode == "reliable") { + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; } else if (mode == "unreliable") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE; + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE; } else if (mode == "ordered") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED; + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_ORDERED; } else { - push_error(R"(Invalid RPC transfer mode. Must be one of: 'reliable', 'unreliable', 'ordered')", p_annotation); - return false; + push_error(R"(Invalid RPC argument. Must be one of: 'sync'/'nosync' (local calls), 'any'/'auth' (permission), 'reliable'/'unreliable'/'ordered' (transfer mode).)", p_annotation); } - } else if (i == 3) { - rpc_config.channel = p_annotation->resolved_arguments[i].operator int(); } } switch (p_node->type) { case Node::FUNCTION: { FunctionNode *function = static_cast<FunctionNode *>(p_node); - if (function->rpc_config.rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + if (function->rpc_config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { push_error(R"(RPC annotations can only be used once per function.)", p_annotation); return false; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 0bce8d7ddd..4902f0d4a6 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -31,8 +31,8 @@ #ifndef GDSCRIPT_PARSER_H #define GDSCRIPT_PARSER_H -#include "core/io/multiplayer_api.h" #include "core/io/resource.h" +#include "core/multiplayer/multiplayer.h" #include "core/object/ref_counted.h" #include "core/object/script_language.h" #include "core/string/string_name.h" @@ -729,7 +729,7 @@ public: SuiteNode *body = nullptr; bool is_static = false; bool is_coroutine = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; MethodInfo info; LambdaNode *source_lambda = nullptr; #ifdef TOOLS_ENABLED @@ -1340,7 +1340,7 @@ private: template <PropertyHint t_hint, Variant::Type t_type> bool export_annotations(const AnnotationNode *p_annotation, Node *p_target); bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target); - template <MultiplayerAPI::RPCMode t_mode> + template <Multiplayer::RPCMode t_mode> bool network_annotations(const AnnotationNode *p_annotation, Node *p_target); // Statements. Node *parse_statement(); diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md new file mode 100644 index 0000000000..6e54085962 --- /dev/null +++ b/modules/gdscript/tests/README.md @@ -0,0 +1,8 @@ +# GDScript integration tests + +The `scripts/` folder contains integration tests in the form of GDScript files +and output files. + +See the +[Integration tests for GDScript documentation](https://docs.godotengine.org/en/latest/development/cpp/unit_testing.html#integration-tests-for-gdscript) +for information about creating and running GDScript integration tests. diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 03a48bf071..6225e5d1eb 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -48,11 +48,11 @@ namespace GDScriptTests { void init_autoloads() { - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); // First pass, add the constants so they exist before any script is loaded. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (info.is_singleton) { for (int i = 0; i < ScriptServer::get_language_count(); i++) { @@ -62,8 +62,8 @@ void init_autoloads() { } // Second pass, load into global constants. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (!info.is_singleton) { // Skip non-singletons since we don't have a scene tree here anyway. diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out index dc4348d9c3..2c8cc9c03f 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out +++ b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out @@ -1,3 +1,3 @@ GDTEST_OK Name is equal -True +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out index 75b002aa62..ad6beeb646 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out +++ b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out @@ -1,3 +1,3 @@ GDTEST_OK -True +true OK diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd new file mode 100644 index 0000000000..17c65ad60a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd @@ -0,0 +1,3 @@ +func test(): + var a = 0 + a = diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out new file mode 100644 index 0000000000..1369a7a0dc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected an expression after "=". diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd new file mode 100644 index 0000000000..92dfb2366d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd @@ -0,0 +1,2 @@ +func test(): + var dictionary = { hello = "world",, } diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out new file mode 100644 index 0000000000..d1dcd1cb4b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression as dictionary key. diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd new file mode 100644 index 0000000000..43b513045b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd @@ -0,0 +1,34 @@ +func foo(x): + match x: + 1 + 1: + print("1+1") + [1,2,[1,{1:2,2:var z,..}]]: + print("[1,2,[1,{1:2,2:var z,..}]]") + print(z) + 1 if true else 2: + print("1 if true else 2") + 1 < 2: + print("1 < 2") + 1 or 2 and 1: + print("1 or 2 and 1") + 6 | 1: + print("1 | 1") + 1 >> 1: + print("1 >> 1") + 1, 2 or 3, 4: + print("1, 2 or 3, 4") + _: + print("wildcard") + +func test(): + foo(6 | 1) + foo(1 >> 1) + foo(2) + foo(1) + foo(1+1) + foo(1 < 2) + foo([2, 1]) + foo(4) + foo([1, 2, [1, {1 : 2, 2:3}]]) + foo([1, 2, [1, {1 : 2, 2:[1,3,5, "123"], 4:2}]]) + foo([1, 2, [1, {1 : 2}]]) diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out new file mode 100644 index 0000000000..67c7e28046 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out @@ -0,0 +1,14 @@ +GDTEST_OK +1 | 1 +1 >> 1 +1+1 +1 if true else 2 +1+1 +1 < 2 +wildcard +1, 2 or 3, 4 +[1,2,[1,{1:2,2:var z,..}]] +3 +[1,2,[1,{1:2,2:var z,..}]] +[1, 3, 5, 123] +wildcard diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd new file mode 100644 index 0000000000..2b46f1e88a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd @@ -0,0 +1,27 @@ +func foo(x): + match x: + 1: + print("1") + 2: + print("2") + [1, 2]: + print("[1, 2]") + 3 or 4: + print("3 or 4") + 4: + print("4") + {1 : 2, 2 : 3}: + print("{1 : 2, 2 : 3}") + _: + print("wildcard") + +func test(): + foo(0) + foo(1) + foo(2) + foo([1, 2]) + foo(3) + foo(4) + foo([4,4]) + foo({1 : 2, 2 : 3}) + foo({1 : 2, 4 : 3}) diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out new file mode 100644 index 0000000000..46ee4b04da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out @@ -0,0 +1,10 @@ +GDTEST_OK +wildcard +1 +2 +[1, 2] +wildcard +4 +wildcard +{1 : 2, 2 : 3} +wildcard diff --git a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd new file mode 100644 index 0000000000..9e48a7f0da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd @@ -0,0 +1,7 @@ +func test(): + var null_var = null + var true_var: bool = true + var false_var: bool = false + print(str(null_var)) + print(str(true_var)) + print(str(false_var)) diff --git a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out new file mode 100644 index 0000000000..abba38e87c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out @@ -0,0 +1,4 @@ +GDTEST_OK +null +true +false diff --git a/modules/gltf/config.py b/modules/gltf/config.py index a4ee871eff..52a97c93aa 100644 --- a/modules/gltf/config.py +++ b/modules/gltf/config.py @@ -22,7 +22,6 @@ def get_doc_classes(): "GLTFSpecGloss", "GLTFState", "GLTFTexture", - "PackedSceneGLTF", ] diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 04c40dd752..f8e0007684 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -7,6 +7,28 @@ <tutorials> </tutorials> <methods> + <method name="import_scene"> + <return type="Node" /> + <argument index="0" name="path" type="String" /> + <argument index="1" name="flags" type="int" default="0" /> + <argument index="2" name="bake_fps" type="int" default="30" /> + <argument index="3" name="state" type="GLTFState" default="null" /> + <description> + Import a scene from glTF2 ".gltf" or ".glb" file. + </description> + </method> + <method name="save_scene"> + <return type="int" enum="Error" /> + <argument index="0" name="node" type="Node" /> + <argument index="1" name="path" type="String" /> + <argument index="2" name="src_path" type="String" /> + <argument index="3" name="flags" type="int" default="0" /> + <argument index="4" name="bake_fps" type="float" default="30" /> + <argument index="5" name="state" type="GLTFState" default="null" /> + <description> + Save a scene as a glTF2 ".glb" or ".gltf" file. + </description> + </method> </methods> <constants> </constants> diff --git a/modules/gltf/doc_classes/PackedSceneGLTF.xml b/modules/gltf/doc_classes/PackedSceneGLTF.xml deleted file mode 100644 index d0136c6402..0000000000 --- a/modules/gltf/doc_classes/PackedSceneGLTF.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="PackedSceneGLTF" inherits="PackedScene" version="4.0"> - <brief_description> - </brief_description> - <description> - </description> - <tutorials> - </tutorials> - <methods> - <method name="export_gltf"> - <return type="int" enum="Error" /> - <argument index="0" name="node" type="Node" /> - <argument index="1" name="path" type="String" /> - <argument index="2" name="flags" type="int" default="0" /> - <argument index="3" name="bake_fps" type="float" default="1000.0" /> - <description> - </description> - </method> - <method name="import_gltf_scene"> - <return type="Node" /> - <argument index="0" name="path" type="String" /> - <argument index="1" name="flags" type="int" default="0" /> - <argument index="2" name="bake_fps" type="float" default="1000.0" /> - <argument index="3" name="state" type="GLTFState" default="null" /> - <description> - </description> - </method> - <method name="pack_gltf"> - <return type="void" /> - <argument index="0" name="path" type="String" /> - <argument index="1" name="flags" type="int" default="0" /> - <argument index="2" name="bake_fps" type="float" default="1000.0" /> - <argument index="3" name="state" type="GLTFState" default="null" /> - <description> - </description> - </method> - </methods> - <members> - <member name="_bundled" type="Dictionary" setter="_set_bundled_scene" getter="_get_bundled_scene" override="true" default="{"conn_count": 0,"conns": PackedInt32Array(),"editable_instances": [],"names": PackedStringArray(),"node_count": 0,"node_paths": [],"nodes": PackedInt32Array(),"variants": [],"version": 2}" /> - </members> - <constants> - </constants> -</class> diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp index ae080bcc9a..fd9f758f10 100644 --- a/modules/gltf/editor_scene_exporter_gltf_plugin.cpp +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp @@ -30,9 +30,11 @@ #include "editor_scene_exporter_gltf_plugin.h" #include "core/config/project_settings.h" +#include "core/error/error_list.h" #include "core/object/object.h" #include "core/templates/vector.h" #include "editor/editor_file_system.h" +#include "gltf_document.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/gui/check_box.h" #include "scene/main/node.h" @@ -49,7 +51,6 @@ bool SceneExporterGLTFPlugin::has_main_screen() const { SceneExporterGLTFPlugin::SceneExporterGLTFPlugin(EditorNode *p_node) { editor = p_node; - convert_gltf2.instantiate(); file_export_lib = memnew(EditorFileDialog); editor->get_gui_base()->add_child(file_export_lib); file_export_lib->connect("file_selected", callable_mp(this, &SceneExporterGLTFPlugin::_gltf2_dialog_action)); @@ -71,8 +72,12 @@ void SceneExporterGLTFPlugin::_gltf2_dialog_action(String p_file) { return; } List<String> deps; - convert_gltf2->save_scene(root, p_file, p_file, 0, 1000.0f, &deps); - EditorFileSystem::get_singleton()->scan_changes(); + Ref<GLTFDocument> doc; + doc.instantiate(); + Error err = doc->save_scene(root, p_file, p_file, 0, 30.0f, Ref<GLTFState>()); + if (err != OK) { + ERR_PRINT(vformat("glTF2 save scene error %s.", itos(err))); + } } void SceneExporterGLTFPlugin::convert_scene_to_gltf2() { diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.h b/modules/gltf/editor_scene_exporter_gltf_plugin.h index d952894c16..c4f277fca2 100644 --- a/modules/gltf/editor_scene_exporter_gltf_plugin.h +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.h @@ -37,7 +37,6 @@ class SceneExporterGLTFPlugin : public EditorPlugin { GDCLASS(SceneExporterGLTFPlugin, EditorPlugin); - Ref<PackedSceneGLTF> convert_gltf2; EditorNode *editor = nullptr; EditorFileDialog *file_export_lib = nullptr; void _gltf2_dialog_action(String p_file); diff --git a/modules/gltf/editor_scene_importer_gltf.cpp b/modules/gltf/editor_scene_importer_gltf.cpp index eca1c85bf3..12796c41d7 100644 --- a/modules/gltf/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor_scene_importer_gltf.cpp @@ -50,9 +50,9 @@ Node *EditorSceneImporterGLTF::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { - Ref<PackedSceneGLTF> importer; - importer.instantiate(); - return importer->import_scene(p_path, p_flags, p_bake_fps, r_missing_deps, r_err, Ref<GLTFState>()); + Ref<GLTFDocument> doc; + doc.instantiate(); + return doc->import_scene_gltf(p_path, p_flags, p_bake_fps, Ref<GLTFState>(), r_missing_deps, r_err); } Ref<Animation> EditorSceneImporterGLTF::import_animation(const String &p_path, @@ -60,114 +60,3 @@ Ref<Animation> EditorSceneImporterGLTF::import_animation(const String &p_path, int p_bake_fps) { return Ref<Animation>(); } - -void PackedSceneGLTF::_bind_methods() { - ClassDB::bind_method( - D_METHOD("export_gltf", "node", "path", "flags", "bake_fps"), - &PackedSceneGLTF::export_gltf, DEFVAL(0), DEFVAL(1000.0f)); - ClassDB::bind_method(D_METHOD("pack_gltf", "path", "flags", "bake_fps", "state"), - &PackedSceneGLTF::pack_gltf, DEFVAL(0), DEFVAL(1000.0f), DEFVAL(Ref<GLTFState>())); - ClassDB::bind_method(D_METHOD("import_gltf_scene", "path", "flags", "bake_fps", "state"), - &PackedSceneGLTF::import_gltf_scene, DEFVAL(0), DEFVAL(1000.0f), DEFVAL(Ref<GLTFState>())); -} -Node *PackedSceneGLTF::import_gltf_scene(const String &p_path, uint32_t p_flags, float p_bake_fps, Ref<GLTFState> r_state) { - Error err = FAILED; - List<String> deps; - return import_scene(p_path, p_flags, p_bake_fps, &deps, &err, r_state); -} - -Node *PackedSceneGLTF::import_scene(const String &p_path, uint32_t p_flags, - int p_bake_fps, - List<String> *r_missing_deps, - Error *r_err, - Ref<GLTFState> r_state) { - if (r_state == Ref<GLTFState>()) { - r_state.instantiate(); - } - r_state->use_named_skin_binds = - p_flags & EditorSceneImporter::IMPORT_USE_NAMED_SKIN_BINDS; - - Ref<GLTFDocument> gltf_document; - gltf_document.instantiate(); - Error err = gltf_document->parse(r_state, p_path); - if (r_err) { - *r_err = err; - } - ERR_FAIL_COND_V(err != Error::OK, nullptr); - - Node3D *root = memnew(Node3D); - for (int32_t root_i = 0; root_i < r_state->root_nodes.size(); root_i++) { - gltf_document->_generate_scene_node(r_state, root, root, r_state->root_nodes[root_i]); - } - gltf_document->_process_mesh_instances(r_state, root); - if (r_state->animations.size()) { - AnimationPlayer *ap = memnew(AnimationPlayer); - root->add_child(ap); - ap->set_owner(root); - for (int i = 0; i < r_state->animations.size(); i++) { - gltf_document->_import_animation(r_state, ap, i, p_bake_fps); - } - } - - return cast_to<Node3D>(root); -} - -void PackedSceneGLTF::pack_gltf(String p_path, int32_t p_flags, - real_t p_bake_fps, Ref<GLTFState> r_state) { - Error err = FAILED; - List<String> deps; - Node *root = import_scene(p_path, p_flags, p_bake_fps, &deps, &err, r_state); - ERR_FAIL_COND(err != OK); - pack(root); -} - -void PackedSceneGLTF::save_scene(Node *p_node, const String &p_path, - const String &p_src_path, uint32_t p_flags, - int p_bake_fps, List<String> *r_missing_deps, - Error *r_err) { - Error err = FAILED; - if (r_err) { - *r_err = err; - } - Ref<GLTFDocument> gltf_document; - gltf_document.instantiate(); - Ref<GLTFState> state; - state.instantiate(); - err = gltf_document->serialize(state, p_node, p_path); - if (r_err) { - *r_err = err; - } -} - -void PackedSceneGLTF::_build_parent_hierachy(Ref<GLTFState> state) { - // build the hierarchy - for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { - for (int j = 0; j < state->nodes[node_i]->children.size(); j++) { - GLTFNodeIndex child_i = state->nodes[node_i]->children[j]; - ERR_FAIL_INDEX(child_i, state->nodes.size()); - if (state->nodes.write[child_i]->parent != -1) { - continue; - } - state->nodes.write[child_i]->parent = node_i; - } - } -} - -Error PackedSceneGLTF::export_gltf(Node *p_root, String p_path, - int32_t p_flags, - real_t p_bake_fps) { - ERR_FAIL_COND_V(!p_root, FAILED); - List<String> deps; - Error err; - String path = p_path; - int32_t flags = p_flags; - real_t baked_fps = p_bake_fps; - Ref<PackedSceneGLTF> exporter; - exporter.instantiate(); - exporter->save_scene(p_root, path, "", flags, baked_fps, &deps, &err); - int32_t error_code = err; - if (error_code != 0) { - return Error(error_code); - } - return OK; -} diff --git a/modules/gltf/editor_scene_importer_gltf.h b/modules/gltf/editor_scene_importer_gltf.h index 7bc5f594ed..eb8775b137 100644 --- a/modules/gltf/editor_scene_importer_gltf.h +++ b/modules/gltf/editor_scene_importer_gltf.h @@ -46,35 +46,9 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { public: virtual uint32_t get_import_flags() const override; virtual void get_extensions(List<String> *r_extensions) const override; - virtual Node *import_scene(const String &p_path, uint32_t p_flags, - int p_bake_fps, - List<String> *r_missing_deps = nullptr, - Error *r_err = nullptr) override; + virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) override; }; #endif - -class PackedSceneGLTF : public PackedScene { - GDCLASS(PackedSceneGLTF, PackedScene); - -protected: - static void _bind_methods(); - -public: - virtual void save_scene(Node *p_node, const String &p_path, const String &p_src_path, - uint32_t p_flags, int p_bake_fps, - List<String> *r_missing_deps, Error *r_err = nullptr); - virtual void _build_parent_hierachy(Ref<GLTFState> state); - virtual Error export_gltf(Node *p_root, String p_path, int32_t p_flags = 0, - real_t p_bake_fps = 1000.0f); - virtual Node *import_scene(const String &p_path, uint32_t p_flags, - int p_bake_fps, - List<String> *r_missing_deps, - Error *r_err, - Ref<GLTFState> r_state); - virtual Node *import_gltf_scene(const String &p_path, uint32_t p_flags, float p_bake_fps, Ref<GLTFState> r_state = Ref<GLTFState>()); - virtual void pack_gltf(String p_path, int32_t p_flags = 0, - real_t p_bake_fps = 1000.0f, Ref<GLTFState> r_state = Ref<GLTFState>()); -}; #endif // EDITOR_SCENE_IMPORTER_GLTF_H diff --git a/modules/gltf/gltf_accessor.h b/modules/gltf/gltf_accessor.h index 949a601730..57aea1026c 100644 --- a/modules/gltf/gltf_accessor.h +++ b/modules/gltf/gltf_accessor.h @@ -44,8 +44,7 @@ private: int component_type = 0; bool normalized = false; int count = 0; - GLTFDocument::GLTFType - type = GLTFDocument::TYPE_SCALAR; + GLTFDocument::GLTFType type = GLTFDocument::TYPE_SCALAR; Vector<double> min; Vector<double> max; int sparse_count = 0; diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index ff0579a11c..db324e23b7 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -48,6 +48,7 @@ #include "core/io/file_access.h" #include "core/io/json.h" #include "core/math/disjoint_set.h" +#include "core/math/vector2.h" #include "core/variant/typed_array.h" #include "core/variant/variant.h" #include "core/version.h" @@ -61,6 +62,7 @@ #include "scene/resources/surface_tool.h" #include "modules/modules_enabled.gen.h" + #ifdef MODULE_CSG_ENABLED #include "modules/csg/csg_shape.h" #endif // MODULE_CSG_ENABLED @@ -70,6 +72,7 @@ #include <stdio.h> #include <stdlib.h> +#include <cstdint> #include <limits> Error GLTFDocument::serialize(Ref<GLTFState> state, Node *p_root, const String &p_path) { @@ -2170,11 +2173,14 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { } Array array = import_mesh->get_surface_arrays(surface_i); + uint32_t format = import_mesh->get_surface_format(surface_i); + int32_t vertex_num = 0; Dictionary attributes; { Vector<Vector3> a = array[Mesh::ARRAY_VERTEX]; ERR_FAIL_COND_V(!a.size(), ERR_INVALID_DATA); attributes["POSITION"] = _encode_accessor_as_vec3(state, a, true); + vertex_num = a.size(); } { Vector<real_t> a = array[Mesh::ARRAY_TANGENT]; @@ -2217,6 +2223,58 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { attributes["TEXCOORD_1"] = _encode_accessor_as_vec2(state, a, true); } } + for (int custom_i = 0; custom_i < 3; custom_i++) { + Vector<float> a = array[Mesh::ARRAY_CUSTOM0 + custom_i]; + if (a.size()) { + int num_channels = 4; + int custom_shift = Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT + custom_i * Mesh::ARRAY_FORMAT_CUSTOM_BITS; + switch ((format >> custom_shift) & Mesh::ARRAY_FORMAT_CUSTOM_MASK) { + case Mesh::ARRAY_CUSTOM_R_FLOAT: + num_channels = 1; + break; + case Mesh::ARRAY_CUSTOM_RG_FLOAT: + num_channels = 2; + break; + case Mesh::ARRAY_CUSTOM_RGB_FLOAT: + num_channels = 3; + break; + case Mesh::ARRAY_CUSTOM_RGBA_FLOAT: + num_channels = 4; + break; + } + int texcoord_i = 2 + 2 * custom_i; + String gltf_texcoord_key; + for (int prev_texcoord_i = 0; prev_texcoord_i < texcoord_i; prev_texcoord_i++) { + gltf_texcoord_key = vformat("TEXCOORD_%d", prev_texcoord_i); + if (!attributes.has(gltf_texcoord_key)) { + Vector<Vector2> empty; + empty.resize(vertex_num); + attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(state, empty, true); + } + } + + LocalVector<Vector2> first_channel; + first_channel.resize(vertex_num); + LocalVector<Vector2> second_channel; + second_channel.resize(vertex_num); + for (int32_t vert_i = 0; vert_i < vertex_num; vert_i++) { + float u = a[vert_i * num_channels + 0]; + float v = (num_channels == 1 ? 0.0f : a[vert_i * num_channels + 1]); + first_channel[vert_i] = Vector2(u, v); + u = 0; + v = 0; + if (num_channels >= 3) { + u = a[vert_i * num_channels + 2]; + v = (num_channels == 3 ? 0.0f : a[vert_i * num_channels + 3]); + second_channel[vert_i] = Vector2(u, v); + } + } + gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i); + attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(state, first_channel, true); + gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i + 1); + attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(state, second_channel, true); + } + } { Vector<Color> a = array[Mesh::ARRAY_COLOR]; if (a.size()) { @@ -2252,13 +2310,12 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { } attributes["JOINTS_0"] = _encode_accessor_as_joints(state, attribs, true); } else if ((a.size() / (JOINT_GROUP_SIZE * 2)) >= vertex_array.size()) { - int32_t vertex_count = vertex_array.size(); Vector<Color> joints_0; - joints_0.resize(vertex_count); + joints_0.resize(vertex_num); Vector<Color> joints_1; - joints_1.resize(vertex_count); + joints_1.resize(vertex_num); int32_t weights_8_count = JOINT_GROUP_SIZE * 2; - for (int32_t vertex_i = 0; vertex_i < vertex_count; vertex_i++) { + for (int32_t vertex_i = 0; vertex_i < vertex_num; vertex_i++) { Color joint_0; joint_0.r = a[vertex_i * weights_8_count + 0]; joint_0.g = a[vertex_i * weights_8_count + 1]; @@ -2288,13 +2345,12 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { } attributes["WEIGHTS_0"] = _encode_accessor_as_weights(state, attribs, true); } else if ((a.size() / (JOINT_GROUP_SIZE * 2)) >= vertex_array.size()) { - int32_t vertex_count = vertex_array.size(); Vector<Color> weights_0; - weights_0.resize(vertex_count); + weights_0.resize(vertex_num); Vector<Color> weights_1; - weights_1.resize(vertex_count); + weights_1.resize(vertex_num); int32_t weights_8_count = JOINT_GROUP_SIZE * 2; - for (int32_t vertex_i = 0; vertex_i < vertex_count; vertex_i++) { + for (int32_t vertex_i = 0; vertex_i < vertex_num; vertex_i++) { Color weight_0; weight_0.r = a[vertex_i * weights_8_count + 0]; weight_0.g = a[vertex_i * weights_8_count + 1]; @@ -2458,7 +2514,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR); Array primitives = d["primitives"]; - const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); + const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : + Dictionary(); Ref<EditorSceneImporterMesh> import_mesh; import_mesh.instantiate(); String mesh_name = "mesh"; @@ -2468,6 +2525,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { import_mesh->set_name(_gen_unique_name(state, vformat("%s_%s", state->scene_name, mesh_name))); for (int j = 0; j < primitives.size(); j++) { + uint32_t flags = 0; Dictionary p = primitives[j]; Array array; @@ -2499,8 +2557,11 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { } ERR_FAIL_COND_V(!a.has("POSITION"), ERR_PARSE_ERROR); + int32_t vertex_num = 0; if (a.has("POSITION")) { - array[Mesh::ARRAY_VERTEX] = _decode_accessor_as_vec3(state, a["POSITION"], true); + PackedVector3Array vertices = _decode_accessor_as_vec3(state, a["POSITION"], true); + array[Mesh::ARRAY_VERTEX] = vertices; + vertex_num = vertices.size(); } if (a.has("NORMAL")) { array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(state, a["NORMAL"], true); @@ -2514,6 +2575,60 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { if (a.has("TEXCOORD_1")) { array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(state, a["TEXCOORD_1"], true); } + for (int custom_i = 0; custom_i < 3; custom_i++) { + Vector<float> cur_custom; + Vector<Vector2> texcoord_first; + Vector<Vector2> texcoord_second; + + int texcoord_i = 2 + 2 * custom_i; + String gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i); + int num_channels = 0; + if (a.has(gltf_texcoord_key)) { + texcoord_first = _decode_accessor_as_vec2(state, a[gltf_texcoord_key], true); + num_channels = 2; + } + gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i + 1); + if (a.has(gltf_texcoord_key)) { + texcoord_second = _decode_accessor_as_vec2(state, a[gltf_texcoord_key], true); + num_channels = 4; + } + if (!num_channels) { + break; + } + if (num_channels == 2 || num_channels == 4) { + cur_custom.resize(vertex_num * num_channels); + for (int32_t uv_i = 0; uv_i < texcoord_first.size() && uv_i < vertex_num; uv_i++) { + cur_custom.write[uv_i * num_channels + 0] = texcoord_first[uv_i].x; + cur_custom.write[uv_i * num_channels + 1] = texcoord_first[uv_i].y; + } + // Vector.resize seems to not zero-initialize. Ensure all unused elements are 0: + for (int32_t uv_i = texcoord_first.size(); uv_i < vertex_num; uv_i++) { + cur_custom.write[uv_i * num_channels + 0] = 0; + cur_custom.write[uv_i * num_channels + 1] = 0; + } + } + if (num_channels == 4) { + for (int32_t uv_i = 0; uv_i < texcoord_second.size() && uv_i < vertex_num; uv_i++) { + // num_channels must be 4 + cur_custom.write[uv_i * num_channels + 2] = texcoord_second[uv_i].x; + cur_custom.write[uv_i * num_channels + 3] = texcoord_second[uv_i].y; + } + // Vector.resize seems to not zero-initialize. Ensure all unused elements are 0: + for (int32_t uv_i = texcoord_second.size(); uv_i < vertex_num; uv_i++) { + cur_custom.write[uv_i * num_channels + 2] = 0; + cur_custom.write[uv_i * num_channels + 3] = 0; + } + } + if (cur_custom.size() > 0) { + array[Mesh::ARRAY_CUSTOM0 + custom_i] = cur_custom; + int custom_shift = Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT + custom_i * Mesh::ARRAY_FORMAT_CUSTOM_BITS; + if (num_channels == 2) { + flags |= Mesh::ARRAY_CUSTOM_RG_FLOAT << custom_shift; + } else { + flags |= Mesh::ARRAY_CUSTOM_RGBA_FLOAT << custom_shift; + } + } + } if (a.has("COLOR_0")) { array[Mesh::ARRAY_COLOR] = _decode_accessor_as_color(state, a["COLOR_0"], true); has_vertex_color = true; @@ -2525,10 +2640,9 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { PackedInt32Array joints_1 = _decode_accessor_as_ints(state, a["JOINTS_1"], true); ERR_FAIL_COND_V(joints_0.size() != joints_0.size(), ERR_INVALID_DATA); int32_t weight_8_count = JOINT_GROUP_SIZE * 2; - int32_t vertex_count = joints_0.size() / JOINT_GROUP_SIZE; Vector<int> joints; - joints.resize(vertex_count * weight_8_count); - for (int32_t vertex_i = 0; vertex_i < vertex_count; vertex_i++) { + joints.resize(vertex_num * weight_8_count); + for (int32_t vertex_i = 0; vertex_i < vertex_num; vertex_i++) { joints.write[vertex_i * weight_8_count + 0] = joints_0[vertex_i * JOINT_GROUP_SIZE + 0]; joints.write[vertex_i * weight_8_count + 1] = joints_0[vertex_i * JOINT_GROUP_SIZE + 1]; joints.write[vertex_i * weight_8_count + 2] = joints_0[vertex_i * JOINT_GROUP_SIZE + 2]; @@ -2567,9 +2681,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { Vector<float> weights; ERR_FAIL_COND_V(weights_0.size() != weights_1.size(), ERR_INVALID_DATA); int32_t weight_8_count = JOINT_GROUP_SIZE * 2; - int32_t vertex_count = weights_0.size() / JOINT_GROUP_SIZE; - weights.resize(vertex_count * weight_8_count); - for (int32_t vertex_i = 0; vertex_i < vertex_count; vertex_i++) { + weights.resize(vertex_num * weight_8_count); + for (int32_t vertex_i = 0; vertex_i < vertex_num; vertex_i++) { weights.write[vertex_i * weight_8_count + 0] = weights_0[vertex_i * JOINT_GROUP_SIZE + 0]; weights.write[vertex_i * weight_8_count + 1] = weights_0[vertex_i * JOINT_GROUP_SIZE + 1]; weights.write[vertex_i * weight_8_count + 2] = weights_0[vertex_i * JOINT_GROUP_SIZE + 2]; @@ -2797,7 +2910,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { mat = mat3d; } - import_mesh->add_surface(primitive, array, morphs, Dictionary(), mat, mat.is_valid() ? mat->get_name() : String()); + import_mesh->add_surface(primitive, array, morphs, Dictionary(), mat, mat.is_valid() ? mat->get_name() : String(), flags); } Vector<float> blend_weights; @@ -2953,6 +3066,7 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_pat } } } else { // Relative path to an external image file. + uri = uri.uri_decode(); uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. // ResourceLoader will rely on the file extension to use the relevant loader. // The spec says that if mimeType is defined, it should take precedence (e.g. @@ -4896,7 +5010,7 @@ GLTFMeshIndex GLTFDocument::_convert_mesh_instance(Ref<GLTFState> state, MeshIns if (p_mesh_instance->get_material_override().is_valid()) { mat = p_mesh_instance->get_material_override(); } - import_mesh->add_surface(primitive_type, arrays, blend_shape_arrays, Dictionary(), mat, surface_name); + import_mesh->add_surface(primitive_type, arrays, blend_shape_arrays, Dictionary(), mat, surface_name, godot_mesh->surface_get_format(surface_i)); } for (int32_t blend_i = 0; blend_i < blend_count; blend_i++) { blend_weights.write[blend_i] = 0.0f; @@ -6630,3 +6744,78 @@ Error GLTFDocument::_serialize_file(Ref<GLTFState> state, const String p_path) { } return err; } + +Error GLTFDocument::save_scene(Node *p_node, const String &p_path, + const String &p_src_path, uint32_t p_flags, + float p_bake_fps, Ref<GLTFState> r_state) { + Ref<GLTFDocument> gltf_document; + gltf_document.instantiate(); + if (r_state == Ref<GLTFState>()) { + r_state.instantiate(); + } + return gltf_document->serialize(r_state, p_node, p_path); +} + +Node *GLTFDocument::import_scene_gltf(const String &p_path, uint32_t p_flags, int32_t p_bake_fps, Ref<GLTFState> r_state, List<String> *r_missing_deps, Error *r_err) { + // TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire + if (r_state == Ref<GLTFState>()) { + r_state.instantiate(); + } + r_state->use_named_skin_binds = + p_flags & EditorSceneImporter::IMPORT_USE_NAMED_SKIN_BINDS; + + Ref<GLTFDocument> gltf_document; + gltf_document.instantiate(); + Error err = gltf_document->parse(r_state, p_path); + if (r_err) { + *r_err = err; + } + ERR_FAIL_COND_V(err != Error::OK, nullptr); + + Node3D *root = memnew(Node3D); + for (int32_t root_i = 0; root_i < r_state->root_nodes.size(); root_i++) { + gltf_document->_generate_scene_node(r_state, root, root, r_state->root_nodes[root_i]); + } + gltf_document->_process_mesh_instances(r_state, root); + if (r_state->animations.size()) { + AnimationPlayer *ap = memnew(AnimationPlayer); + root->add_child(ap); + ap->set_owner(root); + for (int i = 0; i < r_state->animations.size(); i++) { + gltf_document->_import_animation(r_state, ap, i, p_bake_fps); + } + } + + return root; +} + +void GLTFDocument::_bind_methods() { + ClassDB::bind_method(D_METHOD("save_scene", "node", "path", "src_path", "flags", "bake_fps", "state"), + &GLTFDocument::save_scene, DEFVAL(0), DEFVAL(30), DEFVAL(Ref<GLTFState>())); + ClassDB::bind_method(D_METHOD("import_scene", "path", "flags", "bake_fps", "state"), + &GLTFDocument::import_scene, DEFVAL(0), DEFVAL(30), DEFVAL(Ref<GLTFState>())); +} + +void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> state) { + // build the hierarchy + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { + for (int j = 0; j < state->nodes[node_i]->children.size(); j++) { + GLTFNodeIndex child_i = state->nodes[node_i]->children[j]; + ERR_FAIL_INDEX(child_i, state->nodes.size()); + if (state->nodes.write[child_i]->parent != -1) { + continue; + } + state->nodes.write[child_i]->parent = node_i; + } + } +} + +Node *GLTFDocument::import_scene(const String &p_path, uint32_t p_flags, int32_t p_bake_fps, Ref<GLTFState> r_state) { + Error err = FAILED; + List<String> deps; + Node *node = import_scene_gltf(p_path, p_flags, p_bake_fps, r_state, &deps, &err); + if (err != OK) { + return nullptr; + } + return node; +} diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index 3e8ea19045..fb798a055a 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -44,6 +44,7 @@ #include "scene/resources/texture.h" #include "modules/modules_enabled.gen.h" +#include <cstdint> class GLTFState; class GLTFSkin; @@ -102,6 +103,16 @@ public: COMPONENT_TYPE_FLOAT = 5126, }; +protected: + static void _bind_methods(); + +public: + Node *import_scene(const String &p_path, uint32_t p_flags, int32_t p_bake_fps, Ref<GLTFState> r_state); + Node *import_scene_gltf(const String &p_path, uint32_t p_flags, int32_t p_bake_fps, Ref<GLTFState> r_state, List<String> *r_missing_deps, Error *r_err = nullptr); + Error save_scene(Node *p_node, const String &p_path, + const String &p_src_path, uint32_t p_flags, + float p_bake_fps, Ref<GLTFState> r_state); + private: template <class T> static Array to_array(const Vector<T> &p_inp) { @@ -155,6 +166,7 @@ private: r_out[keys[i]] = p_inp[keys[i]]; } } + void _build_parent_hierachy(Ref<GLTFState> state); double _filter_number(double p_float); String _get_component_type_name(const uint32_t p_component); int _get_component_type_size(const int component_type); @@ -346,8 +358,8 @@ private: Error _serialize_extensions(Ref<GLTFState> state) const; public: - // http://www.itu.int/rec/R-REC-BT.601 - // http://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf + // https://www.itu.int/rec/R-REC-BT.601 + // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf static constexpr float R_BRIGHTNESS_COEFF = 0.299f; static constexpr float G_BRIGHTNESS_COEFF = 0.587f; static constexpr float B_BRIGHTNESS_COEFF = 0.114f; diff --git a/modules/gltf/gltf_node.h b/modules/gltf/gltf_node.h index 378b6da8bf..eca3acb239 100644 --- a/modules/gltf/gltf_node.h +++ b/modules/gltf/gltf_node.h @@ -37,7 +37,6 @@ class GLTFNode : public Resource { GDCLASS(GLTFNode, Resource); friend class GLTFDocument; - friend class PackedSceneGLTF; private: // matrices need to be transformed to this diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index d8209523c5..896ea5fc56 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -51,7 +51,6 @@ class GLTFState : public Resource { GDCLASS(GLTFState, Resource); friend class GLTFDocument; - friend class PackedSceneGLTF; String filename; Dictionary json; diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index 85921490d2..d6020f50f0 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -80,7 +80,6 @@ void register_gltf_types() { GDREGISTER_CLASS(GLTFLight); GDREGISTER_CLASS(GLTFState); GDREGISTER_CLASS(GLTFDocument); - GDREGISTER_CLASS(PackedSceneGLTF); #endif } diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 8e8b6f14ad..487e6deac0 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -475,7 +475,7 @@ bool GridMap::_octant_update(const OctantKey &p_key) { } Pair<Transform3D, IndexKey> p; - p.first = xform; + p.first = xform * mesh_library->get_item_mesh_transform(c.item); p.second = E->get(); multimesh_items[c.item].push_back(p); } diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index f101c43e89..c170bb107e 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -255,6 +255,12 @@ void GridMapEditor::_update_cursor_transform() { cursor_transform.basis *= node->get_cell_scale(); cursor_transform = node->get_global_transform() * cursor_transform; + if (selected_palette >= 0) { + if (node && !node->get_mesh_library().is_null()) { + cursor_transform *= node->get_mesh_library()->get_item_mesh_transform(selected_palette); + } + } + if (cursor_instance.is_valid()) { RenderingServer::get_singleton()->instance_set_transform(cursor_instance, cursor_transform); RenderingServer::get_singleton()->instance_set_visible(cursor_instance, cursor_visible); @@ -790,7 +796,7 @@ void GridMapEditor::_sbox_input(const Ref<InputEvent> &p_ie) { if (k.is_valid() && (k->get_keycode() == KEY_UP || k->get_keycode() == KEY_DOWN || k->get_keycode() == KEY_PAGEUP || k->get_keycode() == KEY_PAGEDOWN)) { // Forward the key input to the ItemList so it can be scrolled - mesh_library_palette->call("_gui_input", k); + mesh_library_palette->gui_input(k); search_box->accept_event(); } } diff --git a/modules/lightmapper_rd/SCsub b/modules/lightmapper_rd/SCsub index 2f04f1833e..5cc9d8ee8b 100644 --- a/modules/lightmapper_rd/SCsub +++ b/modules/lightmapper_rd/SCsub @@ -7,6 +7,9 @@ env_lightmapper_rd = env_modules.Clone() env_lightmapper_rd.GLSL_HEADER("lm_raster.glsl") env_lightmapper_rd.GLSL_HEADER("lm_compute.glsl") env_lightmapper_rd.GLSL_HEADER("lm_blendseams.glsl") +env_lightmapper_rd.Depends("lm_raster.glsl.gen.h", "lm_common_inc.glsl") +env_lightmapper_rd.Depends("lm_compute.glsl.gen.h", "lm_common_inc.glsl") +env_lightmapper_rd.Depends("lm_blendseams.glsl.gen.h", "lm_common_inc.glsl") # Godot source files env_lightmapper_rd.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index fe941e25e7..ba4ef3be8d 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -274,13 +274,12 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_ return BAKE_OK; } -void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) { +void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) { HashMap<Vertex, uint32_t, VertexHash> vertex_map; //fill triangles array and vertex array LocalVector<Triangle> triangles; LocalVector<Vertex> vertex_array; - LocalVector<Box> box_array; LocalVector<Seam> seams; slice_triangle_count.resize(atlas_slices); @@ -387,16 +386,13 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i } } - Box box; - box.min_bounds[0] = taabb.position.x; - box.min_bounds[1] = taabb.position.y; - box.min_bounds[2] = taabb.position.z; - box.max_bounds[0] = taabb.position.x + MAX(taabb.size.x, 0.0001); - box.max_bounds[1] = taabb.position.y + MAX(taabb.size.y, 0.0001); - box.max_bounds[2] = taabb.position.z + MAX(taabb.size.z, 0.0001); - box.pad0 = box.pad1 = 0; //make valgrind not complain - box_array.push_back(box); - + t.min_bounds[0] = taabb.position.x; + t.min_bounds[1] = taabb.position.y; + t.min_bounds[2] = taabb.position.z; + t.max_bounds[0] = taabb.position.x + MAX(taabb.size.x, 0.0001); + t.max_bounds[1] = taabb.position.y + MAX(taabb.size.y, 0.0001); + t.max_bounds[2] = taabb.position.z + MAX(taabb.size.z, 0.0001); + t.pad0 = t.pad1 = 0; //make valgrind not complain triangles.push_back(t); slice_triangle_count.write[t.slice]++; } @@ -505,9 +501,6 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i Vector<uint8_t> tb = triangles.to_byte_array(); triangle_buffer = rd->storage_buffer_create(tb.size(), tb); - Vector<uint8_t> bb = box_array.to_byte_array(); - box_buffer = rd->storage_buffer_create(bb.size(), bb); - Vector<uint8_t> tib = triangle_indices.to_byte_array(); triangle_cell_indices_buffer = rd->storage_buffer_create(tib.size(), tib); @@ -755,7 +748,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d Vector<int> slice_triangle_count; RID vertex_buffer; RID triangle_buffer; - RID box_buffer; RID lights_buffer; RID triangle_cell_indices_buffer; RID grid_texture; @@ -767,14 +759,13 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d #define FREE_BUFFERS \ rd->free(vertex_buffer); \ rd->free(triangle_buffer); \ - rd->free(box_buffer); \ rd->free(lights_buffer); \ rd->free(triangle_cell_indices_buffer); \ rd->free(grid_texture); \ rd->free(seams_buffer); \ rd->free(probe_positions_buffer); - _create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, box_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, seams_buffer, p_step_function, p_bake_userdata); + _create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, seams_buffer, p_step_function, p_bake_userdata); if (p_step_function) { p_step_function(0.47, TTR("Preparing shaders"), p_bake_userdata, true); @@ -828,62 +819,55 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; u.binding = 3; - u.ids.push_back(box_buffer); - base_uniforms.push_back(u); - } - { - RD::Uniform u; - u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.binding = 4; u.ids.push_back(triangle_cell_indices_buffer); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.binding = 5; + u.binding = 4; u.ids.push_back(lights_buffer); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.binding = 6; + u.binding = 5; u.ids.push_back(seams_buffer); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.binding = 7; + u.binding = 6; u.ids.push_back(probe_positions_buffer); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 8; + u.binding = 7; u.ids.push_back(grid_texture); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 9; + u.binding = 8; u.ids.push_back(albedo_array_tex); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 10; + u.binding = 9; u.ids.push_back(emission_array_tex); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_SAMPLER; - u.binding = 11; + u.binding = 10; u.ids.push_back(sampler); base_uniforms.push_back(u); } diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h index 7ab7f34464..a6a3740051 100644 --- a/modules/lightmapper_rd/lightmapper_rd.h +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -157,16 +157,13 @@ class LightmapperRD : public Lightmapper { } }; - struct Box { + struct Triangle { + uint32_t indices[3] = {}; + uint32_t slice = 0; float min_bounds[3] = {}; float pad0 = 0.0; float max_bounds[3] = {}; float pad1 = 0.0; - }; - - struct Triangle { - uint32_t indices[3] = {}; - uint32_t slice = 0; bool operator<(const Triangle &p_triangle) const { return slice < p_triangle.slice; } @@ -231,7 +228,7 @@ class LightmapperRD : public Lightmapper { Vector<Color> probe_values; BakeError _blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata); - void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata); + void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata); void _raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform); public: diff --git a/modules/lightmapper_rd/lm_common_inc.glsl b/modules/lightmapper_rd/lm_common_inc.glsl index 1581639036..22172d50e4 100644 --- a/modules/lightmapper_rd/lm_common_inc.glsl +++ b/modules/lightmapper_rd/lm_common_inc.glsl @@ -16,26 +16,18 @@ vertices; struct Triangle { uvec3 indices; uint slice; -}; - -layout(set = 0, binding = 2, std430) restrict readonly buffer Triangles { - Triangle data[]; -} -triangles; - -struct Box { vec3 min_bounds; uint pad0; vec3 max_bounds; uint pad1; }; -layout(set = 0, binding = 3, std430) restrict readonly buffer Boxes { - Box data[]; +layout(set = 0, binding = 2, std430) restrict readonly buffer Triangles { + Triangle data[]; } -boxes; +triangles; -layout(set = 0, binding = 4, std430) restrict readonly buffer GridIndices { +layout(set = 0, binding = 3, std430) restrict readonly buffer GridIndices { uint data[]; } grid_indices; @@ -63,7 +55,7 @@ struct Light { uint pad[3]; }; -layout(set = 0, binding = 5, std430) restrict readonly buffer Lights { +layout(set = 0, binding = 4, std430) restrict readonly buffer Lights { Light data[]; } lights; @@ -73,19 +65,19 @@ struct Seam { uvec2 b; }; -layout(set = 0, binding = 6, std430) restrict readonly buffer Seams { +layout(set = 0, binding = 5, std430) restrict readonly buffer Seams { Seam data[]; } seams; -layout(set = 0, binding = 7, std430) restrict readonly buffer Probes { +layout(set = 0, binding = 6, std430) restrict readonly buffer Probes { vec4 data[]; } probe_positions; -layout(set = 0, binding = 8) uniform utexture3D grid; +layout(set = 0, binding = 7) uniform utexture3D grid; -layout(set = 0, binding = 9) uniform texture2DArray albedo_tex; -layout(set = 0, binding = 10) uniform texture2DArray emission_tex; +layout(set = 0, binding = 8) uniform texture2DArray albedo_tex; +layout(set = 0, binding = 9) uniform texture2DArray emission_tex; -layout(set = 0, binding = 11) uniform sampler linear_sampler; +layout(set = 0, binding = 10) uniform sampler linear_sampler; diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index 9ca40535f9..a71652d5c4 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -160,18 +160,19 @@ bool trace_ray(vec3 p_from, vec3 p_to uint tidx = grid_indices.data[cell_data.y + i]; //Ray-Box test - vec3 t0 = (boxes.data[tidx].min_bounds - p_from) * inv_dir; - vec3 t1 = (boxes.data[tidx].max_bounds - p_from) * inv_dir; + Triangle triangle = triangles.data[tidx]; + vec3 t0 = (triangle.min_bounds - p_from) * inv_dir; + vec3 t1 = (triangle.max_bounds - p_from) * inv_dir; vec3 tmin = min(t0, t1), tmax = max(t0, t1); - if (max(tmin.x, max(tmin.y, tmin.z)) <= min(tmax.x, min(tmax.y, tmax.z))) { + if (max(tmin.x, max(tmin.y, tmin.z)) > min(tmax.x, min(tmax.y, tmax.z))) { continue; //ray box failed } //prepare triangle vertices - vec3 vtx0 = vertices.data[triangles.data[tidx].indices.x].position; - vec3 vtx1 = vertices.data[triangles.data[tidx].indices.y].position; - vec3 vtx2 = vertices.data[triangles.data[tidx].indices.z].position; + vec3 vtx0 = vertices.data[triangle.indices.x].position; + vec3 vtx1 = vertices.data[triangle.indices.y].position; + vec3 vtx2 = vertices.data[triangle.indices.z].position; #if defined(MODE_UNOCCLUDE) vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2))); diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index 2cc974322d..7b52ef178a 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -37,11 +37,13 @@ #include "core/io/file_access.h" -void AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { - ERR_FAIL_COND(!active); +int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { + ERR_FAIL_COND_V(!active, 0); int todo = p_frames; + int frames_mixed_this_step = p_frames; + while (todo && active) { mp3dec_frame_info_t frame_info; mp3d_sample_t *buf_frame = nullptr; @@ -60,6 +62,7 @@ void AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { seek(mp3_stream->loop_offset); loops++; } else { + frames_mixed_this_step = p_frames - todo; //fill remainder with silence for (int i = p_frames - todo; i < p_frames; i++) { p_buffer[i] = AudioFrame(0, 0); @@ -69,6 +72,7 @@ void AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { } } } + return frames_mixed_this_step; } float AudioStreamPlaybackMP3::get_stream_sampling_rate() { @@ -210,6 +214,10 @@ float AudioStreamMP3::get_length() const { return length; } +bool AudioStreamMP3::is_monophonic() const { + return false; +} + void AudioStreamMP3::_bind_methods() { ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamMP3::set_data); ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamMP3::get_data); diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h index ce001fc418..3c8bdd8c53 100644 --- a/modules/minimp3/audio_stream_mp3.h +++ b/modules/minimp3/audio_stream_mp3.h @@ -51,7 +51,7 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled { Ref<AudioStreamMP3> mp3_stream; protected: - virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) override; + virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override; virtual float get_stream_sampling_rate() override; public: @@ -103,6 +103,8 @@ public: virtual float get_length() const override; + virtual bool is_monophonic() const override; + AudioStreamMP3(); virtual ~AudioStreamMP3(); }; diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp index 590b95ab79..bbf1db689d 100644 --- a/modules/mobile_vr/mobile_vr_interface.cpp +++ b/modules/mobile_vr/mobile_vr_interface.cpp @@ -39,7 +39,7 @@ StringName MobileVRInterface::get_name() const { return "Native mobile"; }; -int MobileVRInterface::get_capabilities() const { +uint32_t MobileVRInterface::get_capabilities() const { return XRInterface::XR_STEREO; }; @@ -305,6 +305,10 @@ uint32_t MobileVRInterface::get_view_count() { return 2; }; +XRInterface::TrackingStatus MobileVRInterface::get_tracking_status() const { + return tracking_state; +} + bool MobileVRInterface::is_initialized() const { return (initialized); }; @@ -340,16 +344,16 @@ bool MobileVRInterface::initialize() { void MobileVRInterface::uninitialize() { if (initialized) { XRServer *xr_server = XRServer::get_singleton(); - if (xr_server != nullptr) { + if (xr_server != nullptr && xr_server->get_primary_interface() == this) { // no longer our primary interface - xr_server->clear_primary_interface_if(this); + xr_server->set_primary_interface(nullptr); } initialized = false; }; }; -Size2 MobileVRInterface::get_render_targetsize() { +Size2 MobileVRInterface::get_render_target_size() { _THREAD_SAFE_METHOD_ // we use half our window size @@ -429,31 +433,6 @@ CameraMatrix MobileVRInterface::get_projection_for_view(uint32_t p_view, real_t return eye; }; -void MobileVRInterface::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) { - _THREAD_SAFE_METHOD_ - - // We must have a valid render target - ERR_FAIL_COND(!p_render_target.is_valid()); - - // Because we are rendering to our device we must use our main viewport! - ERR_FAIL_COND(p_screen_rect == Rect2()); - - Rect2 dest = p_screen_rect; - Vector2 eye_center; - - // we output half a screen - dest.size.x *= 0.5; - - if (p_eye == XRInterface::EYE_LEFT) { - eye_center.x = ((-intraocular_dist / 2.0) + (display_width / 4.0)) / (display_width / 2.0); - } else if (p_eye == XRInterface::EYE_RIGHT) { - dest.position.x = dest.size.x; - eye_center.x = ((intraocular_dist / 2.0) - (display_width / 4.0)) / (display_width / 2.0); - } - // we don't offset the eye center vertically (yet) - eye_center.y = 0.0; -} - Vector<BlitToScreen> MobileVRInterface::commit_views(RID p_render_target, const Rect2 &p_screen_rect) { _THREAD_SAFE_METHOD_ @@ -476,16 +455,16 @@ Vector<BlitToScreen> MobileVRInterface::commit_views(RID p_render_target, const blit.lens_distortion.aspect_ratio = aspect; // left eye - blit.rect = p_screen_rect; - blit.rect.size.width *= 0.5; + blit.dst_rect = p_screen_rect; + blit.dst_rect.size.width *= 0.5; blit.multi_view.layer = 0; blit.lens_distortion.eye_center.x = ((-intraocular_dist / 2.0) + (display_width / 4.0)) / (display_width / 2.0); blit_to_screen.push_back(blit); // right eye - blit.rect = p_screen_rect; - blit.rect.size.width *= 0.5; - blit.rect.position.x = blit.rect.size.width; + blit.dst_rect = p_screen_rect; + blit.dst_rect.size.width *= 0.5; + blit.dst_rect.position.x = blit.dst_rect.size.width; blit.multi_view.layer = 1; blit.lens_distortion.eye_center.x = ((intraocular_dist / 2.0) - (display_width / 4.0)) / (display_width / 2.0); blit_to_screen.push_back(blit); diff --git a/modules/mobile_vr/mobile_vr_interface.h b/modules/mobile_vr/mobile_vr_interface.h index 0c05dc1ebb..48b76ec187 100644 --- a/modules/mobile_vr/mobile_vr_interface.h +++ b/modules/mobile_vr/mobile_vr_interface.h @@ -52,6 +52,7 @@ class MobileVRInterface : public XRInterface { private: bool initialized = false; + XRInterface::TrackingStatus tracking_state; Basis orientation; // Just set some defaults for these. At some point we need to look at adding a lookup table for common device + headset combos and/or support reading cardboard QR codes @@ -131,13 +132,15 @@ public: real_t get_k2() const; virtual StringName get_name() const override; - virtual int get_capabilities() const override; + virtual uint32_t get_capabilities() const override; + + virtual TrackingStatus get_tracking_status() const override; virtual bool is_initialized() const override; virtual bool initialize() override; virtual void uninitialize() override; - virtual Size2 get_render_targetsize() override; + virtual Size2 get_render_target_size() override; virtual uint32_t get_view_count() override; virtual Transform3D get_camera_transform() override; virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; @@ -145,13 +148,9 @@ public: virtual Vector<BlitToScreen> commit_views(RID p_render_target, const Rect2 &p_screen_rect) override; virtual void process() override; - virtual void notification(int p_what) override {} MobileVRInterface(); ~MobileVRInterface(); - - // deprecated - virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) override; }; #endif // !MOBILE_VR_INTERFACE_H diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 1f7f1390ea..3437dbd194 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -337,7 +337,7 @@ void CSharpLanguage::get_comment_delimiters(List<String> *p_delimiters) const { void CSharpLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("' '"); // character literal p_delimiters->push_back("\" \""); // regular string literal - // Verbatim string literals (`@" "`) don't render correctly, so don't highlight them. + p_delimiters->push_back("@\" \""); // verbatim string literal // Generic string highlighting suffices as a workaround for now. } @@ -2124,7 +2124,7 @@ bool CSharpInstance::refcount_decremented() { return ref_dying; } -const Vector<MultiplayerAPI::RPCConfig> CSharpInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> CSharpInstance::get_rpc_methods() const { return script->get_rpc_methods(); } @@ -3034,13 +3034,13 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { Vector<GDMonoMethod *> methods = top->get_all_methods(); for (int i = 0; i < methods.size(); i++) { if (!methods[i]->is_static()) { - MultiplayerAPI::RPCMode mode = p_script->_member_get_rpc_mode(methods[i]); - if (MultiplayerAPI::RPC_MODE_DISABLED != mode) { - MultiplayerAPI::RPCConfig nd; + Multiplayer::RPCMode mode = p_script->_member_get_rpc_mode(methods[i]); + if (Multiplayer::RPC_MODE_DISABLED != mode) { + Multiplayer::RPCConfig nd; nd.name = methods[i]->get_name(); nd.rpc_mode = mode; // TODO Transfer mode, channel - nd.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; + nd.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; nd.channel = 0; if (-1 == p_script->rpc_functions.find(nd)) { p_script->rpc_functions.push_back(nd); @@ -3054,7 +3054,7 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { } // Sort so we are 100% that they are always the same. - p_script->rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>(); + p_script->rpc_functions.sort_custom<Multiplayer::SortRPCConfig>(); p_script->load_script_signals(p_script->script_class, p_script->native); } @@ -3464,21 +3464,18 @@ int CSharpScript::get_member_line(const StringName &p_member) const { return -1; } -MultiplayerAPI::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const { +Multiplayer::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const { if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) { - return MultiplayerAPI::RPC_MODE_REMOTE; - } - if (p_member->has_attribute(CACHED_CLASS(MasterAttribute))) { - return MultiplayerAPI::RPC_MODE_MASTER; + return Multiplayer::RPC_MODE_ANY; } if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) { - return MultiplayerAPI::RPC_MODE_PUPPET; + return Multiplayer::RPC_MODE_AUTHORITY; } - return MultiplayerAPI::RPC_MODE_DISABLED; + return Multiplayer::RPC_MODE_DISABLED; } -const Vector<MultiplayerAPI::RPCConfig> CSharpScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> CSharpScript::get_rpc_methods() const { return rpc_functions; } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 4552f376d0..e3bbb20dec 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -136,7 +136,7 @@ private: Map<StringName, EventSignal> event_signals; bool signals_invalidated = true; - Vector<MultiplayerAPI::RPCConfig> rpc_functions; + Vector<Multiplayer::RPCConfig> rpc_functions; #ifdef TOOLS_ENABLED List<PropertyInfo> exported_members_cache; // members_cache @@ -179,7 +179,7 @@ private: static void update_script_class_info(Ref<CSharpScript> p_script); static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native); - MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const; + Multiplayer::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const; protected: static void _bind_methods(); @@ -234,7 +234,7 @@ public: int get_member_line(const StringName &p_member) const override; - const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; #ifdef TOOLS_ENABLED bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } @@ -311,7 +311,7 @@ public: void refcount_incremented() override; bool refcount_decremented() override; - const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; void notification(int p_notification) override; void _call_notification(int p_notification); diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index 9b7b422276..2bf1cb7a18 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -12,12 +12,16 @@ namespace GodotTools.BuildLogger public string Parameters { get; set; } public LoggerVerbosity Verbosity { get; set; } + private StreamWriter _logStreamWriter; + private StreamWriter _issuesStreamWriter; + private int _indent; + public void Initialize(IEventSource eventSource) { if (null == Parameters) throw new LoggerException("Log directory parameter not specified."); - var parameters = Parameters.Split(new[] { ';' }); + string[] parameters = Parameters.Split(new[] { ';' }); string logDir = parameters[0]; @@ -35,8 +39,8 @@ namespace GodotTools.BuildLogger if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir); - logStreamWriter = new StreamWriter(logFile); - issuesStreamWriter = new StreamWriter(issuesFile); + _logStreamWriter = new StreamWriter(logFile); + _issuesStreamWriter = new StreamWriter(issuesFile); } catch (Exception ex) { @@ -66,12 +70,12 @@ namespace GodotTools.BuildLogger private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) { WriteLine(e.Message); - indent++; + _indent++; } private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) { - indent--; + _indent--; WriteLine(e.Message); } @@ -87,7 +91,7 @@ namespace GodotTools.BuildLogger string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; - issuesStreamWriter.WriteLine(errorLine); + _issuesStreamWriter.WriteLine(errorLine); } private void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) @@ -102,7 +106,7 @@ namespace GodotTools.BuildLogger string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; - issuesStreamWriter.WriteLine(warningLine); + _issuesStreamWriter.WriteLine(warningLine); } private void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) @@ -136,28 +140,24 @@ namespace GodotTools.BuildLogger private void WriteLine(string line) { - for (int i = indent; i > 0; i--) + for (int i = _indent; i > 0; i--) { - logStreamWriter.Write("\t"); + _logStreamWriter.Write("\t"); } - logStreamWriter.WriteLine(line); + _logStreamWriter.WriteLine(line); } public void Shutdown() { - logStreamWriter.Close(); - issuesStreamWriter.Close(); + _logStreamWriter.Close(); + _issuesStreamWriter.Close(); } private bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity) { return Verbosity >= checkVerbosity; } - - private StreamWriter logStreamWriter; - private StreamWriter issuesStreamWriter; - private int indent; } internal static class StringExtensions diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs index 43d40f2ad9..a4d7dedbd5 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs @@ -7,7 +7,7 @@ namespace GodotTools.Core { public static class ProcessExtensions { - public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource<bool>(); diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index b217ae4bf7..60a4f297c9 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -7,6 +7,8 @@ namespace GodotTools.Core { public static class StringExtensions { + private static readonly string _driveRoot = Path.GetPathRoot(Environment.CurrentDirectory); + public static string RelativeToPath(this string path, string dir) { // Make sure the directory ends with a path separator @@ -49,13 +51,11 @@ namespace GodotTools.Core return Path.DirectorySeparatorChar + path; } - private static readonly string DriveRoot = Path.GetPathRoot(Environment.CurrentDirectory); - public static bool IsAbsolutePath(this string path) { return path.StartsWith("/", StringComparison.Ordinal) || path.StartsWith("\\", StringComparison.Ordinal) || - path.StartsWith(DriveRoot, StringComparison.Ordinal); + path.StartsWith(_driveRoot, StringComparison.Ordinal); } public static string ToSafeDirName(this string dirName, bool allowDirSeparator = false) diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs index 284e94810a..355b21d63a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs @@ -9,15 +9,40 @@ namespace GodotTools.ProjectEditor { public class DotNetSolution { - private string directoryPath; - private readonly Dictionary<string, ProjectInfo> projects = new Dictionary<string, ProjectInfo>(); + private const string _solutionTemplate = +@"Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +{0} +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution +{1} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution +{2} + EndGlobalSection +EndGlobal +"; + + private const string _projectDeclaration = +@"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""{1}"", ""{{{2}}}"" +EndProject"; + + private const string _solutionPlatformsConfig = +@" {0}|Any CPU = {0}|Any CPU"; + + private const string _projectPlatformsConfig = +@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU + {{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU"; + + private string _directoryPath; + private readonly Dictionary<string, ProjectInfo> _projects = new Dictionary<string, ProjectInfo>(); public string Name { get; } public string DirectoryPath { - get => directoryPath; - set => directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value); + get => _directoryPath; + set => _directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value); } public class ProjectInfo @@ -29,22 +54,22 @@ namespace GodotTools.ProjectEditor public void AddNewProject(string name, ProjectInfo projectInfo) { - projects[name] = projectInfo; + _projects[name] = projectInfo; } public bool HasProject(string name) { - return projects.ContainsKey(name); + return _projects.ContainsKey(name); } public ProjectInfo GetProjectInfo(string name) { - return projects[name]; + return _projects[name]; } public bool RemoveProject(string name) { - return projects.Remove(name); + return _projects.Remove(name); } public void Save() @@ -58,7 +83,7 @@ namespace GodotTools.ProjectEditor bool isFirstProject = true; - foreach (var pair in projects) + foreach (var pair in _projects) { string name = pair.Key; ProjectInfo projectInfo = pair.Value; @@ -66,7 +91,7 @@ namespace GodotTools.ProjectEditor if (!isFirstProject) projectsDecl += "\n"; - projectsDecl += string.Format(ProjectDeclaration, + projectsDecl += string.Format(_projectDeclaration, name, projectInfo.PathRelativeToSolution.Replace("/", "\\"), projectInfo.Guid); for (int i = 0; i < projectInfo.Configs.Count; i++) @@ -79,15 +104,15 @@ namespace GodotTools.ProjectEditor projPlatformsCfg += "\n"; } - slnPlatformsCfg += string.Format(SolutionPlatformsConfig, config); - projPlatformsCfg += string.Format(ProjectPlatformsConfig, projectInfo.Guid, config); + slnPlatformsCfg += string.Format(_solutionPlatformsConfig, config); + projPlatformsCfg += string.Format(_projectPlatformsConfig, projectInfo.Guid, config); } isFirstProject = false; } string solutionPath = Path.Combine(DirectoryPath, Name + ".sln"); - string content = string.Format(SolutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg); + string content = string.Format(_solutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg); File.WriteAllText(solutionPath, content, Encoding.UTF8); // UTF-8 with BOM } @@ -97,37 +122,12 @@ namespace GodotTools.ProjectEditor Name = name; } - const string SolutionTemplate = -@"Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -{0} -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution -{1} - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution -{2} - EndGlobalSection -EndGlobal -"; - - const string ProjectDeclaration = -@"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""{1}"", ""{{{2}}}"" -EndProject"; - - const string SolutionPlatformsConfig = -@" {0}|Any CPU = {0}|Any CPU"; - - const string ProjectPlatformsConfig = -@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU - {{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU"; - public static void MigrateFromOldConfigNames(string slnPath) { if (!File.Exists(slnPath)) return; - var input = File.ReadAllText(slnPath); + string input = File.ReadAllText(slnPath); if (!Regex.IsMatch(input, Regex.Escape("Tools|Any CPU"))) return; @@ -151,7 +151,7 @@ EndProject"; }; var regex = new Regex(string.Join("|", dict.Keys.Select(Regex.Escape))); - var result = regex.Replace(input, m => dict[m.Value]); + string result = regex.Replace(input, m => dict[m.Value]); if (result != input) { diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs index ed77076df3..31363df9ef 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs @@ -91,7 +91,7 @@ namespace GodotTools.ProjectEditor return identifier; } - static bool IsKeyword(string value, bool anyDoubleUnderscore) + private static bool IsKeyword(string value, bool anyDoubleUnderscore) { // Identifiers that start with double underscore are meant to be used for reserved keywords. // Only existing keywords are enforced, but it may be useful to forbid any identifier @@ -103,14 +103,14 @@ namespace GodotTools.ProjectEditor } else { - if (DoubleUnderscoreKeywords.Contains(value)) + if (_doubleUnderscoreKeywords.Contains(value)) return true; } - return Keywords.Contains(value); + return _keywords.Contains(value); } - private static readonly HashSet<string> DoubleUnderscoreKeywords = new HashSet<string> + private static readonly HashSet<string> _doubleUnderscoreKeywords = new HashSet<string> { "__arglist", "__makeref", @@ -118,7 +118,7 @@ namespace GodotTools.ProjectEditor "__refvalue", }; - private static readonly HashSet<string> Keywords = new HashSet<string> + private static readonly HashSet<string> _keywords = new HashSet<string> { "as", "do", diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs index 27737c3da0..28bf57dc21 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs @@ -13,7 +13,8 @@ namespace GodotTools.Build public string[] Targets { get; } public string Configuration { get; } public bool Restore { get; } - public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization + // TODO Use List once we have proper serialization + public Array<string> CustomProperties { get; } = new Array<string>(); public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}"); @@ -32,12 +33,12 @@ namespace GodotTools.Build unchecked { int hash = 17; - hash = hash * 29 + Solution.GetHashCode(); - hash = hash * 29 + Targets.GetHashCode(); - hash = hash * 29 + Configuration.GetHashCode(); - hash = hash * 29 + Restore.GetHashCode(); - hash = hash * 29 + CustomProperties.GetHashCode(); - hash = hash * 29 + LogsDirPath.GetHashCode(); + hash = (hash * 29) + Solution.GetHashCode(); + hash = (hash * 29) + Targets.GetHashCode(); + hash = (hash * 29) + Configuration.GetHashCode(); + hash = (hash * 29) + Restore.GetHashCode(); + hash = (hash * 29) + CustomProperties.GetHashCode(); + hash = (hash * 29) + LogsDirPath.GetHashCode(); return hash; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 2b6f972529..21bff70b15 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -32,7 +32,7 @@ namespace GodotTools.Build private static void RemoveOldIssuesFile(BuildInfo buildInfo) { - var issuesFile = GetIssuesFilePath(buildInfo); + string issuesFile = GetIssuesFilePath(buildInfo); if (!File.Exists(issuesFile)) return; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index 25e260beed..b53347fc4c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -22,14 +22,6 @@ namespace GodotTools.Build public string ProjectFile { get; set; } } - private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization - private ItemList issuesList; - private TextEdit buildLog; - private PopupMenu issuesListContextMenu; - - private readonly object pendingBuildLogTextLock = new object(); - [NotNull] private string pendingBuildLogText = string.Empty; - [Signal] public event Action BuildStateChanged; public bool HasBuildExited { get; private set; } = false; @@ -60,13 +52,21 @@ namespace GodotTools.Build } } - private BuildInfo BuildInfo { get; set; } - public bool LogVisible { - set => buildLog.Visible = value; + set => _buildLog.Visible = value; } + // TODO Use List once we have proper serialization. + private readonly Array<BuildIssue> _issues = new Array<BuildIssue>(); + private ItemList _issuesList; + private PopupMenu _issuesListContextMenu; + private TextEdit _buildLog; + private BuildInfo _buildInfo; + + private readonly object _pendingBuildLogTextLock = new object(); + [NotNull] private string _pendingBuildLogText = string.Empty; + private void LoadIssuesFromFile(string csvFile) { using (var file = new Godot.File()) @@ -107,7 +107,7 @@ namespace GodotTools.Build else ErrorCount += 1; - issues.Add(issue); + _issues.Add(issue); } } finally @@ -119,21 +119,21 @@ namespace GodotTools.Build private void IssueActivated(int idx) { - if (idx < 0 || idx >= issuesList.GetItemCount()) + if (idx < 0 || idx >= _issuesList.GetItemCount()) throw new IndexOutOfRangeException("Item list index out of range"); // Get correct issue idx from issue list - int issueIndex = (int)(long)issuesList.GetItemMetadata(idx); + int issueIndex = (int)(long)_issuesList.GetItemMetadata(idx); - if (issueIndex < 0 || issueIndex >= issues.Count) + if (issueIndex < 0 || issueIndex >= _issues.Count) throw new IndexOutOfRangeException("Issue index out of range"); - BuildIssue issue = issues[issueIndex]; + BuildIssue issue = _issues[issueIndex]; if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File)) return; - string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir(); + string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : _buildInfo.Solution.GetBaseDir(); string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath()); @@ -153,14 +153,14 @@ namespace GodotTools.Build public void UpdateIssuesList() { - issuesList.Clear(); + _issuesList.Clear(); using (var warningIcon = GetThemeIcon("Warning", "EditorIcons")) using (var errorIcon = GetThemeIcon("Error", "EditorIcons")) { - for (int i = 0; i < issues.Count; i++) + for (int i = 0; i < _issues.Count; i++) { - BuildIssue issue = issues[i]; + BuildIssue issue = _issues[i]; if (!(issue.Warning ? WarningsVisible : ErrorsVisible)) continue; @@ -191,11 +191,11 @@ namespace GodotTools.Build int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal); string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx); - issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon); + _issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon); - int index = issuesList.GetItemCount() - 1; - issuesList.SetItemTooltip(index, tooltip); - issuesList.SetItemMetadata(index, i); + int index = _issuesList.GetItemCount() - 1; + _issuesList.SetItemTooltip(index, tooltip); + _issuesList.SetItemMetadata(index, i); } } } @@ -205,12 +205,12 @@ namespace GodotTools.Build HasBuildExited = true; BuildResult = Build.BuildResult.Error; - issuesList.Clear(); + _issuesList.Clear(); var issue = new BuildIssue {Message = cause, Warning = false}; ErrorCount += 1; - issues.Add(issue); + _issues.Add(issue); UpdateIssuesList(); @@ -219,13 +219,13 @@ namespace GodotTools.Build private void BuildStarted(BuildInfo buildInfo) { - BuildInfo = buildInfo; + _buildInfo = buildInfo; HasBuildExited = false; - issues.Clear(); + _issues.Clear(); WarningCount = 0; ErrorCount = 0; - buildLog.Text = string.Empty; + _buildLog.Text = string.Empty; UpdateIssuesList(); @@ -237,7 +237,7 @@ namespace GodotTools.Build HasBuildExited = true; BuildResult = result; - LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); + LoadIssuesFromFile(Path.Combine(_buildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); UpdateIssuesList(); @@ -246,46 +246,46 @@ namespace GodotTools.Build private void UpdateBuildLogText() { - lock (pendingBuildLogTextLock) + lock (_pendingBuildLogTextLock) { - buildLog.Text += pendingBuildLogText; - pendingBuildLogText = string.Empty; + _buildLog.Text += _pendingBuildLogText; + _pendingBuildLogText = string.Empty; ScrollToLastNonEmptyLogLine(); } } private void StdOutputReceived(string text) { - lock (pendingBuildLogTextLock) + lock (_pendingBuildLogTextLock) { - if (pendingBuildLogText.Length == 0) + if (_pendingBuildLogText.Length == 0) CallDeferred(nameof(UpdateBuildLogText)); - pendingBuildLogText += text + "\n"; + _pendingBuildLogText += text + "\n"; } } private void StdErrorReceived(string text) { - lock (pendingBuildLogTextLock) + lock (_pendingBuildLogTextLock) { - if (pendingBuildLogText.Length == 0) + if (_pendingBuildLogText.Length == 0) CallDeferred(nameof(UpdateBuildLogText)); - pendingBuildLogText += text + "\n"; + _pendingBuildLogText += text + "\n"; } } private void ScrollToLastNonEmptyLogLine() { int line; - for (line = buildLog.GetLineCount(); line > 0; line--) + for (line = _buildLog.GetLineCount(); line > 0; line--) { - string lineText = buildLog.GetLine(line); + string lineText = _buildLog.GetLine(line); if (!string.IsNullOrEmpty(lineText) || !string.IsNullOrEmpty(lineText?.Trim())) break; } - buildLog.SetCaretLine(line); + _buildLog.SetCaretLine(line); } public void RestartBuild() @@ -318,11 +318,11 @@ namespace GodotTools.Build // We don't allow multi-selection but just in case that changes later... string text = null; - foreach (int issueIndex in issuesList.GetSelectedItems()) + foreach (int issueIndex in _issuesList.GetSelectedItems()) { if (text != null) text += "\n"; - text += issuesList.GetItemText(issueIndex); + text += _issuesList.GetItemText(issueIndex); } if (text != null) @@ -338,20 +338,20 @@ namespace GodotTools.Build { _ = index; // Unused - issuesListContextMenu.Clear(); - issuesListContextMenu.Size = new Vector2i(1, 1); + _issuesListContextMenu.Clear(); + _issuesListContextMenu.Size = new Vector2i(1, 1); - if (issuesList.IsAnythingSelected()) + if (_issuesList.IsAnythingSelected()) { // Add menu entries for the selected item - issuesListContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"), + _issuesListContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"), label: "Copy Error".TTR(), (int)IssuesContextMenuOption.Copy); } - if (issuesListContextMenu.GetItemCount() > 0) + if (_issuesListContextMenu.GetItemCount() > 0) { - issuesListContextMenu.Position = (Vector2i)(issuesList.RectGlobalPosition + atPosition); - issuesListContextMenu.Popup(); + _issuesListContextMenu.Position = (Vector2i)(_issuesList.RectGlobalPosition + atPosition); + _issuesListContextMenu.Popup(); } } @@ -368,27 +368,27 @@ namespace GodotTools.Build }; AddChild(hsc); - issuesList = new ItemList + _issuesList = new ItemList { SizeFlagsVertical = (int)SizeFlags.ExpandFill, SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the build log }; - issuesList.ItemActivated += IssueActivated; - issuesList.AllowRmbSelect = true; - issuesList.ItemRmbSelected += IssuesListRmbSelected; - hsc.AddChild(issuesList); + _issuesList.ItemActivated += IssueActivated; + _issuesList.AllowRmbSelect = true; + _issuesList.ItemRmbSelected += IssuesListRmbSelected; + hsc.AddChild(_issuesList); - issuesListContextMenu = new PopupMenu(); - issuesListContextMenu.IdPressed += IssuesListContextOptionPressed; - issuesList.AddChild(issuesListContextMenu); + _issuesListContextMenu = new PopupMenu(); + _issuesListContextMenu.IdPressed += IssuesListContextOptionPressed; + _issuesList.AddChild(_issuesListContextMenu); - buildLog = new TextEdit + _buildLog = new TextEdit { Editable = false, SizeFlagsVertical = (int)SizeFlags.ExpandFill, SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the issues list }; - hsc.AddChild(buildLog); + hsc.AddChild(_buildLog); AddBuildEventListeners(); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 5f35d506de..e9cf7911be 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -11,10 +11,10 @@ namespace GodotTools.Build { public BuildOutputView BuildOutputView { get; private set; } - private MenuButton buildMenuBtn; - private Button errorsBtn; - private Button warningsBtn; - private Button viewLogBtn; + private MenuButton _buildMenuBtn; + private Button _errorsBtn; + private Button _warningsBtn; + private Button _viewLogBtn; private void WarningsToggled(bool pressed) { @@ -132,16 +132,16 @@ namespace GodotTools.Build var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill }; AddChild(toolBarHBox); - buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons") }; - toolBarHBox.AddChild(buildMenuBtn); + _buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons") }; + toolBarHBox.AddChild(_buildMenuBtn); - var buildMenu = buildMenuBtn.GetPopup(); + var buildMenu = _buildMenuBtn.GetPopup(); buildMenu.AddItem("Build Solution".TTR(), (int)BuildMenuOptions.BuildSolution); buildMenu.AddItem("Rebuild Solution".TTR(), (int)BuildMenuOptions.RebuildSolution); buildMenu.AddItem("Clean Solution".TTR(), (int)BuildMenuOptions.CleanSolution); buildMenu.IdPressed += BuildMenuOptionPressed; - errorsBtn = new Button + _errorsBtn = new Button { HintTooltip = "Show Errors".TTR(), Icon = GetThemeIcon("StatusError", "EditorIcons"), @@ -150,10 +150,10 @@ namespace GodotTools.Build Pressed = true, FocusMode = FocusModeEnum.None }; - errorsBtn.Toggled += ErrorsToggled; - toolBarHBox.AddChild(errorsBtn); + _errorsBtn.Toggled += ErrorsToggled; + toolBarHBox.AddChild(_errorsBtn); - warningsBtn = new Button + _warningsBtn = new Button { HintTooltip = "Show Warnings".TTR(), Icon = GetThemeIcon("NodeWarning", "EditorIcons"), @@ -162,18 +162,18 @@ namespace GodotTools.Build Pressed = true, FocusMode = FocusModeEnum.None }; - warningsBtn.Toggled += WarningsToggled; - toolBarHBox.AddChild(warningsBtn); + _warningsBtn.Toggled += WarningsToggled; + toolBarHBox.AddChild(_warningsBtn); - viewLogBtn = new Button + _viewLogBtn = new Button { Text = "Show Output".TTR(), ToggleMode = true, Pressed = true, FocusMode = FocusModeEnum.None }; - viewLogBtn.Toggled += ViewLogToggled; - toolBarHBox.AddChild(viewLogBtn); + _viewLogBtn.Toggled += ViewLogToggled; + toolBarHBox.AddChild(_viewLogBtn); BuildOutputView = new BuildOutputView(); AddChild(BuildOutputView); @@ -185,12 +185,12 @@ namespace GodotTools.Build if (what == NotificationThemeChanged) { - if (buildMenuBtn != null) - buildMenuBtn.Icon = GetThemeIcon("Play", "EditorIcons"); - if (errorsBtn != null) - errorsBtn.Icon = GetThemeIcon("StatusError", "EditorIcons"); - if (warningsBtn != null) - warningsBtn.Icon = GetThemeIcon("NodeWarning", "EditorIcons"); + if (_buildMenuBtn != null) + _buildMenuBtn.Icon = GetThemeIcon("Play", "EditorIcons"); + if (_errorsBtn != null) + _errorsBtn.Icon = GetThemeIcon("StatusError", "EditorIcons"); + if (_warningsBtn != null) + _warningsBtn.Icon = GetThemeIcon("NodeWarning", "EditorIcons"); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index 774c49e705..a859c6f717 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -61,7 +61,7 @@ namespace GodotTools.Build } case BuildTool.JetBrainsMsBuild: { - var editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName); + string editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName); if (!File.Exists(editorPath)) throw new FileNotFoundException($"Cannot find Rider executable. Tried with path: {editorPath}"); @@ -165,7 +165,9 @@ namespace GodotTools.Build // Try to find 15.0 with vswhere - var envNames = Internal.GodotIs32Bits() ? new[] {"ProgramFiles", "ProgramW6432"} : new[] {"ProgramFiles(x86)", "ProgramFiles"}; + string[] envNames = Internal.GodotIs32Bits() ? + envNames = new[] { "ProgramFiles", "ProgramW6432" } : + envNames = new[] { "ProgramFiles(x86)", "ProgramFiles" }; string vsWherePath = null; foreach (var envName in envNames) diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs index f69307104f..37e6a34977 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -65,12 +65,12 @@ namespace GodotTools.Export if (platform == OS.Platforms.iOS) { - var architectures = GetEnablediOSArchs(features).ToArray(); + string[] architectures = GetEnablediOSArchs(features).ToArray(); CompileAssembliesForiOS(exporter, isDebug, architectures, aotOpts, aotTempDir, assembliesPrepared, bclDir); } else if (platform == OS.Platforms.Android) { - var abis = GetEnabledAndroidAbis(features).ToArray(); + string[] abis = GetEnabledAndroidAbis(features).ToArray(); CompileAssembliesForAndroid(exporter, isDebug, abis, aotOpts, aotTempDir, assembliesPrepared, bclDir); } else @@ -138,7 +138,8 @@ namespace GodotTools.Export } else { - string outputDataLibDir = Path.Combine(outputDataDir, "Mono", platform == OS.Platforms.Windows ? "bin" : "lib"); + string libDir = platform == OS.Platforms.Windows ? "bin" : "lib"; + string outputDataLibDir = Path.Combine(outputDataDir, "Mono", libDir); File.Copy(tempOutputFilePath, Path.Combine(outputDataLibDir, outputFileName)); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 0b5aa72a81..3e46a89b7c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -20,7 +20,7 @@ namespace GodotTools.Export public class ExportPlugin : EditorExportPlugin { [Flags] - enum I18NCodesets : long + private enum I18NCodesets : long { None = 0, CJK = 1, @@ -31,6 +31,8 @@ namespace GodotTools.Export All = CJK | MidEast | Other | Rare | West } + private string _maybeLastExportError; + private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string bclDir) { var codesets = (I18NCodesets)ProjectSettings.GetSetting("mono/export/i18n_codesets"); @@ -83,8 +85,6 @@ namespace GodotTools.Export GlobalDef("mono/export/aot/android_toolchain_path", ""); } - private string maybeLastExportError; - private void AddFile(string srcPath, string dstPath, bool remap = false) { // Add file to the PCK @@ -129,14 +129,14 @@ namespace GodotTools.Export } catch (Exception e) { - maybeLastExportError = e.Message; + _maybeLastExportError = e.Message; // 'maybeLastExportError' cannot be null or empty if there was an error, so we // must consider the possibility of exceptions being thrown without a message. - if (string.IsNullOrEmpty(maybeLastExportError)) - maybeLastExportError = $"Exception thrown: {e.GetType().Name}"; + if (string.IsNullOrEmpty(_maybeLastExportError)) + _maybeLastExportError = $"Exception thrown: {e.GetType().Name}"; - GD.PushError($"Failed to export project: {maybeLastExportError}"); + GD.PushError($"Failed to export project: {_maybeLastExportError}"); Console.Error.WriteLine(e); // TODO: Do something on error once _ExportBegin supports failing. } @@ -317,10 +317,10 @@ namespace GodotTools.Export Directory.Delete(aotTempDir, recursive: true); // TODO: Just a workaround until the export plugins can be made to abort with errors - if (!string.IsNullOrEmpty(maybeLastExportError)) // Check empty as well, because it's set to empty after hot-reloading + if (!string.IsNullOrEmpty(_maybeLastExportError)) // Check empty as well, because it's set to empty after hot-reloading { - string lastExportError = maybeLastExportError; - maybeLastExportError = null; + string lastExportError = _maybeLastExportError; + _maybeLastExportError = null; GodotSharpEditor.Instance.ShowErrorDialog(lastExportError, "Failed to export C# project"); } @@ -470,7 +470,7 @@ namespace GodotTools.Export private static string DetermineDataDirNameForProject() { - var appName = (string)ProjectSettings.GetSetting("application/config/name"); + string appName = (string)ProjectSettings.GetSetting("application/config/name"); string appNameSafe = appName.ToSafeDirName(); return $"data_{appNameSafe}"; } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 1faa6eeac0..73cabf8561 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -21,18 +21,19 @@ namespace GodotTools { public class GodotSharpEditor : EditorPlugin, ISerializationListener { - private EditorSettings editorSettings; + private EditorSettings _editorSettings; - private PopupMenu menuPopup; + private PopupMenu _menuPopup; - private AcceptDialog errorDialog; + private AcceptDialog _errorDialog; - private Button bottomPanelBtn; - private Button toolBarBuildButton; + private Button _bottomPanelBtn; + private Button _toolBarBuildButton; - public GodotIdeManager GodotIdeManager { get; private set; } + // TODO Use WeakReference once we have proper serialization. + private WeakRef _exportPluginWeak; - private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization + public GodotIdeManager GodotIdeManager { get; private set; } public MSBuildPanel MSBuildPanel { get; private set; } @@ -42,7 +43,7 @@ namespace GodotTools { get { - var projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); + string projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); projectAssemblyName = projectAssemblyName.ToSafeDirName(); if (string.IsNullOrEmpty(projectAssemblyName)) projectAssemblyName = "UnnamedProject"; @@ -123,9 +124,9 @@ namespace GodotTools private void _RemoveCreateSlnMenuOption() { - menuPopup.RemoveItem(menuPopup.GetItemIndex((int)MenuOptions.CreateSln)); - bottomPanelBtn.Show(); - toolBarBuildButton.Show(); + _menuPopup.RemoveItem(_menuPopup.GetItemIndex((int)MenuOptions.CreateSln)); + _bottomPanelBtn.Show(); + _toolBarBuildButton.Show(); } private void _MenuOptionPressed(int id) @@ -181,9 +182,9 @@ namespace GodotTools public void ShowErrorDialog(string message, string title = "Error") { - errorDialog.Title = title; - errorDialog.DialogText = message; - errorDialog.PopupCentered(); + _errorDialog.Title = title; + _errorDialog.DialogText = message; + _errorDialog.PopupCentered(); } private static string _vsCodePath = string.Empty; @@ -196,7 +197,7 @@ namespace GodotTools [UsedImplicitly] public Error OpenInExternalEditor(Script script, int line, int col) { - var editorId = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor"); + var editorId = (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor"); switch (editorId) { @@ -287,7 +288,7 @@ namespace GodotTools } } - var resourcePath = ProjectSettings.GlobalizePath("res://"); + string resourcePath = ProjectSettings.GlobalizePath("res://"); args.Add(resourcePath); string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); @@ -346,7 +347,7 @@ namespace GodotTools [UsedImplicitly] public bool OverridesExternalEditor() { - return (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None; + return (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None; } public override bool _Build() @@ -387,8 +388,8 @@ namespace GodotTools private void BuildStateChanged() { - if (bottomPanelBtn != null) - bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon; + if (_bottomPanelBtn != null) + _bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon; } public override void _EnablePlugin() @@ -402,29 +403,29 @@ namespace GodotTools var editorInterface = GetEditorInterface(); var editorBaseControl = editorInterface.GetBaseControl(); - editorSettings = editorInterface.GetEditorSettings(); + _editorSettings = editorInterface.GetEditorSettings(); - errorDialog = new AcceptDialog(); - editorBaseControl.AddChild(errorDialog); + _errorDialog = new AcceptDialog(); + editorBaseControl.AddChild(_errorDialog); MSBuildPanel = new MSBuildPanel(); - bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR()); + _bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR()); AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); - menuPopup = new PopupMenu(); - menuPopup.Hide(); + _menuPopup = new PopupMenu(); + _menuPopup.Hide(); - AddToolSubmenuItem("C#", menuPopup); + AddToolSubmenuItem("C#", _menuPopup); - toolBarBuildButton = new Button + _toolBarBuildButton = new Button { Text = "Build", HintTooltip = "Build solution", FocusMode = Control.FocusModeEnum.None }; - toolBarBuildButton.PressedSignal += BuildSolutionPressed; - AddControlToContainer(CustomControlContainer.Toolbar, toolBarBuildButton); + _toolBarBuildButton.PressedSignal += BuildSolutionPressed; + AddControlToContainer(CustomControlContainer.Toolbar, _toolBarBuildButton); if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) { @@ -432,12 +433,12 @@ namespace GodotTools } else { - bottomPanelBtn.Hide(); - toolBarBuildButton.Hide(); - menuPopup.AddItem("Create C# solution".TTR(), (int)MenuOptions.CreateSln); + _bottomPanelBtn.Hide(); + _toolBarBuildButton.Hide(); + _menuPopup.AddItem("Create C# solution".TTR(), (int)MenuOptions.CreateSln); } - menuPopup.IdPressed += _MenuOptionPressed; + _menuPopup.IdPressed += _MenuOptionPressed; // External editor settings EditorDef("mono/editor/external_editor", ExternalEditorId.None); @@ -465,7 +466,7 @@ namespace GodotTools $",JetBrains Rider:{(int)ExternalEditorId.Rider}"; } - editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary { ["type"] = Variant.Type.Int, ["name"] = "mono/editor/external_editor", @@ -477,7 +478,7 @@ namespace GodotTools var exportPlugin = new ExportPlugin(); AddExportPlugin(exportPlugin); exportPlugin.RegisterExportSettings(); - exportPluginWeak = WeakRef(exportPlugin); + _exportPluginWeak = WeakRef(exportPlugin); try { @@ -500,15 +501,15 @@ namespace GodotTools { base.Dispose(disposing); - if (exportPluginWeak != null) + if (_exportPluginWeak != null) { // We need to dispose our export plugin before the editor destroys EditorSettings. // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid // will be freed after EditorSettings already was, and its device polling thread // will try to access the EditorSettings singleton, resulting in null dereferencing. - (exportPluginWeak.GetRef() as ExportPlugin)?.Dispose(); + (_exportPluginWeak.GetRef() as ExportPlugin)?.Dispose(); - exportPluginWeak.Dispose(); + _exportPluginWeak.Dispose(); } GodotIdeManager?.Dispose(); diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs index b30c857c64..dd05f28af0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs +++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs @@ -6,7 +6,7 @@ namespace GodotTools { public class HotReloadAssemblyWatcher : Node { - private Timer watchTimer; + private Timer _watchTimer; public override void _Notification(int what) { @@ -27,22 +27,22 @@ namespace GodotTools public void RestartTimer() { - watchTimer.Stop(); - watchTimer.Start(); + _watchTimer.Stop(); + _watchTimer.Start(); } public override void _Ready() { base._Ready(); - watchTimer = new Timer + _watchTimer = new Timer { OneShot = false, WaitTime = (float)EditorDef("mono/assembly_watch_interval_sec", 0.5) }; - watchTimer.Timeout += TimerTimeout; - AddChild(watchTimer); - watchTimer.Start(); + _watchTimer.Timeout += TimerTimeout; + AddChild(_watchTimer); + _watchTimer.Start(); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 451ce39f5c..23339fe50b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -10,22 +10,22 @@ namespace GodotTools.Ides { public sealed class GodotIdeManager : Node, ISerializationListener { - private MessagingServer MessagingServer { get; set; } + private MessagingServer _messagingServer; - private MonoDevelop.Instance monoDevelInstance; - private MonoDevelop.Instance vsForMacInstance; + private MonoDevelop.Instance _monoDevelInstance; + private MonoDevelop.Instance _vsForMacInstance; private MessagingServer GetRunningOrNewServer() { - if (MessagingServer != null && !MessagingServer.IsDisposed) - return MessagingServer; + if (_messagingServer != null && !_messagingServer.IsDisposed) + return _messagingServer; - MessagingServer?.Dispose(); - MessagingServer = new MessagingServer(OS.GetExecutablePath(), ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir), new GodotLogger()); + _messagingServer?.Dispose(); + _messagingServer = new MessagingServer(OS.GetExecutablePath(), ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir), new GodotLogger()); - _ = MessagingServer.Listen(); + _ = _messagingServer.Listen(); - return MessagingServer; + return _messagingServer; } public override void _Ready() @@ -48,7 +48,7 @@ namespace GodotTools.Ides if (disposing) { - MessagingServer?.Dispose(); + _messagingServer?.Dispose(); } } @@ -113,14 +113,14 @@ namespace GodotTools.Ides { if (Utils.OS.IsMacOS && editorId == ExternalEditorId.VisualStudioForMac) { - vsForMacInstance = (vsForMacInstance?.IsDisposed ?? true ? null : vsForMacInstance) ?? + _vsForMacInstance = (_vsForMacInstance?.IsDisposed ?? true ? null : _vsForMacInstance) ?? new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac); - return vsForMacInstance; + return _vsForMacInstance; } - monoDevelInstance = (monoDevelInstance?.IsDisposed ?? true ? null : monoDevelInstance) ?? + _monoDevelInstance = (_monoDevelInstance?.IsDisposed ?? true ? null : _monoDevelInstance) ?? new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop); - return monoDevelInstance; + return _monoDevelInstance; } try @@ -159,15 +159,15 @@ namespace GodotTools.Ides public readonly struct EditorPick { - private readonly string identity; + private readonly string _identity; public EditorPick(string identity) { - this.identity = identity; + _identity = identity; } public bool IsAnyConnected() => - GodotSharpEditor.Instance.GodotIdeManager.GetRunningOrNewServer().IsAnyConnected(identity); + GodotSharpEditor.Instance.GodotIdeManager.GetRunningOrNewServer().IsAnyConnected(_identity); private void SendRequest<TResponse>(Request request) where TResponse : Response, new() @@ -175,7 +175,7 @@ namespace GodotTools.Ides // Logs an error if no client is connected with the specified identity GodotSharpEditor.Instance.GodotIdeManager .GetRunningOrNewServer() - .BroadcastRequest<TResponse>(identity, request); + .BroadcastRequest<TResponse>(_identity, request); } public void SendOpenFile(string file) diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs index eb34a2d0f7..6f11831b80 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs @@ -21,24 +21,26 @@ namespace GodotTools.Ides { public sealed class MessagingServer : IDisposable { - private readonly ILogger logger; + private readonly ILogger _logger; - private readonly FileStream metaFile; - private string MetaFilePath { get; } + private readonly FileStream _metaFile; + private string _metaFilePath; - private readonly SemaphoreSlim peersSem = new SemaphoreSlim(1); + private readonly SemaphoreSlim _peersSem = new SemaphoreSlim(1); - private readonly TcpListener listener; + private readonly TcpListener _listener; - private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> clientConnectedAwaiters = new Dictionary<string, Queue<NotifyAwaiter<bool>>>(); - private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> clientDisconnectedAwaiters = new Dictionary<string, Queue<NotifyAwaiter<bool>>>(); + private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> _clientConnectedAwaiters = + new Dictionary<string, Queue<NotifyAwaiter<bool>>>(); + private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> _clientDisconnectedAwaiters = + new Dictionary<string, Queue<NotifyAwaiter<bool>>>(); public async Task<bool> AwaitClientConnected(string identity) { - if (!clientConnectedAwaiters.TryGetValue(identity, out var queue)) + if (!_clientConnectedAwaiters.TryGetValue(identity, out var queue)) { queue = new Queue<NotifyAwaiter<bool>>(); - clientConnectedAwaiters.Add(identity, queue); + _clientConnectedAwaiters.Add(identity, queue); } var awaiter = new NotifyAwaiter<bool>(); @@ -48,10 +50,10 @@ namespace GodotTools.Ides public async Task<bool> AwaitClientDisconnected(string identity) { - if (!clientDisconnectedAwaiters.TryGetValue(identity, out var queue)) + if (!_clientDisconnectedAwaiters.TryGetValue(identity, out var queue)) { queue = new Queue<NotifyAwaiter<bool>>(); - clientDisconnectedAwaiters.Add(identity, queue); + _clientDisconnectedAwaiters.Add(identity, queue); } var awaiter = new NotifyAwaiter<bool>(); @@ -77,7 +79,7 @@ namespace GodotTools.Ides if (IsDisposed) return; - using (await peersSem.UseAsync()) + using (await _peersSem.UseAsync()) { if (IsDisposed) // lock may not be fair return; @@ -95,19 +97,19 @@ namespace GodotTools.Ides foreach (var connection in Peers) connection.Dispose(); Peers.Clear(); - listener?.Stop(); + _listener?.Stop(); - metaFile?.Dispose(); + _metaFile?.Dispose(); - File.Delete(MetaFilePath); + File.Delete(_metaFilePath); } } public MessagingServer(string editorExecutablePath, string projectMetadataDir, ILogger logger) { - this.logger = logger; + this._logger = logger; - MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName); + _metaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName); // Make sure the directory exists Directory.CreateDirectory(projectMetadataDir); @@ -115,13 +117,13 @@ namespace GodotTools.Ides // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing... const FileShare metaFileShare = FileShare.ReadWrite; - metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare); + _metaFile = File.Open(_metaFilePath, FileMode.Create, FileAccess.Write, metaFileShare); - listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0)); - listener.Start(); + _listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0)); + _listener.Start(); - int port = ((IPEndPoint)listener.Server.LocalEndPoint).Port; - using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8)) + int port = ((IPEndPoint)_listener.Server.LocalEndPoint).Port; + using (var metaFileWriter = new StreamWriter(_metaFile, Encoding.UTF8)) { metaFileWriter.WriteLine(port); metaFileWriter.WriteLine(editorExecutablePath); @@ -130,30 +132,30 @@ namespace GodotTools.Ides private async Task AcceptClient(TcpClient tcpClient) { - logger.LogDebug("Accept client..."); + _logger.LogDebug("Accept client..."); - using (var peer = new Peer(tcpClient, new ServerHandshake(), new ServerMessageHandler(), logger)) + using (var peer = new Peer(tcpClient, new ServerHandshake(), new ServerMessageHandler(), _logger)) { // ReSharper disable AccessToDisposedClosure peer.Connected += () => { - logger.LogInfo("Connection open with Ide Client"); + _logger.LogInfo("Connection open with Ide Client"); - if (clientConnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue)) + if (_clientConnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue)) { while (queue.Count > 0) queue.Dequeue().SetResult(true); - clientConnectedAwaiters.Remove(peer.RemoteIdentity); + _clientConnectedAwaiters.Remove(peer.RemoteIdentity); } }; peer.Disconnected += () => { - if (clientDisconnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue)) + if (_clientDisconnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue)) { while (queue.Count > 0) queue.Dequeue().SetResult(true); - clientDisconnectedAwaiters.Remove(peer.RemoteIdentity); + _clientDisconnectedAwaiters.Remove(peer.RemoteIdentity); } }; // ReSharper restore AccessToDisposedClosure @@ -162,17 +164,17 @@ namespace GodotTools.Ides { if (!await peer.DoHandshake("server")) { - logger.LogError("Handshake failed"); + _logger.LogError("Handshake failed"); return; } } catch (Exception e) { - logger.LogError("Handshake failed with unhandled exception: ", e); + _logger.LogError("Handshake failed with unhandled exception: ", e); return; } - using (await peersSem.UseAsync()) + using (await _peersSem.UseAsync()) Peers.Add(peer); try @@ -181,7 +183,7 @@ namespace GodotTools.Ides } finally { - using (await peersSem.UseAsync()) + using (await _peersSem.UseAsync()) Peers.Remove(peer); } } @@ -192,7 +194,7 @@ namespace GodotTools.Ides try { while (!IsDisposed) - _ = AcceptClient(await listener.AcceptTcpClientAsync()); + _ = AcceptClient(await _listener.AcceptTcpClientAsync()); } catch (Exception e) { @@ -204,11 +206,11 @@ namespace GodotTools.Ides public async void BroadcastRequest<TResponse>(string identity, Request request) where TResponse : Response, new() { - using (await peersSem.UseAsync()) + using (await _peersSem.UseAsync()) { if (!IsAnyConnected(identity)) { - logger.LogError("Cannot write request. No client connected to the Godot Ide Server."); + _logger.LogError("Cannot write request. No client connected to the Godot Ide Server."); return; } @@ -225,16 +227,19 @@ namespace GodotTools.Ides private class ServerHandshake : IHandshake { - private static readonly string ServerHandshakeBase = $"{Peer.ServerHandshakeName},Version={Peer.ProtocolVersionMajor}.{Peer.ProtocolVersionMinor}.{Peer.ProtocolVersionRevision}"; - private static readonly string ClientHandshakePattern = $@"{Regex.Escape(Peer.ClientHandshakeName)},Version=([0-9]+)\.([0-9]+)\.([0-9]+),([_a-zA-Z][_a-zA-Z0-9]{{0,63}})"; + private static readonly string _serverHandshakeBase = + $"{Peer.ServerHandshakeName},Version={Peer.ProtocolVersionMajor}.{Peer.ProtocolVersionMinor}.{Peer.ProtocolVersionRevision}"; - public string GetHandshakeLine(string identity) => $"{ServerHandshakeBase},{identity}"; + private static readonly string _clientHandshakePattern = + $@"{Regex.Escape(Peer.ClientHandshakeName)},Version=([0-9]+)\.([0-9]+)\.([0-9]+),([_a-zA-Z][_a-zA-Z0-9]{{0,63}})"; + + public string GetHandshakeLine(string identity) => $"{_serverHandshakeBase},{identity}"; public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger) { identity = null; - var match = Regex.Match(handshake, ClientHandshakePattern); + var match = Regex.Match(handshake, _clientHandshakePattern); if (!match.Success) return false; diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs index fd7bbd5578..3f1d5ac3ca 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs @@ -10,17 +10,17 @@ namespace GodotTools.Ides.MonoDevelop public class Instance : IDisposable { public DateTime LaunchTime { get; private set; } - private readonly string solutionFile; - private readonly EditorId editorId; + private readonly string _solutionFile; + private readonly EditorId _editorId; - private Process process; + private Process _process; - public bool IsRunning => process != null && !process.HasExited; + public bool IsRunning => _process != null && !_process.HasExited; public bool IsDisposed { get; private set; } public void Execute() { - bool newWindow = process == null || process.HasExited; + bool newWindow = _process == null || _process.HasExited; var args = new List<string>(); @@ -28,7 +28,7 @@ namespace GodotTools.Ides.MonoDevelop if (OS.IsMacOS) { - string bundleId = BundleIds[editorId]; + string bundleId = BundleIds[_editorId]; if (Internal.IsOsxAppBundleInstalled(bundleId)) { @@ -45,18 +45,18 @@ namespace GodotTools.Ides.MonoDevelop } else { - command = OS.PathWhich(ExecutableNames[editorId]); + command = OS.PathWhich(ExecutableNames[_editorId]); } } else { - command = OS.PathWhich(ExecutableNames[editorId]); + command = OS.PathWhich(ExecutableNames[_editorId]); } args.Add("--ipc-tcp"); if (newWindow) - args.Add("\"" + Path.GetFullPath(solutionFile) + "\""); + args.Add("\"" + Path.GetFullPath(_solutionFile) + "\""); if (command == null) throw new FileNotFoundException(); @@ -65,7 +65,7 @@ namespace GodotTools.Ides.MonoDevelop if (newWindow) { - process = Process.Start(new ProcessStartInfo + _process = Process.Start(new ProcessStartInfo { FileName = command, Arguments = string.Join(" ", args), @@ -88,14 +88,14 @@ namespace GodotTools.Ides.MonoDevelop if (editorId == EditorId.VisualStudioForMac && !OS.IsMacOS) throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform"); - this.solutionFile = solutionFile; - this.editorId = editorId; + _solutionFile = solutionFile; + _editorId = editorId; } public void Dispose() { IsDisposed = true; - process?.Dispose(); + _process?.Dispose(); } private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames; diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs index 821532f759..71055f0125 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs @@ -11,6 +11,7 @@ using Environment = System.Environment; using File = System.IO.File; using Path = System.IO.Path; using OS = GodotTools.Utils.OS; + // ReSharper disable UnassignedField.Local // ReSharper disable InconsistentNaming // ReSharper disable UnassignedField.Global @@ -53,10 +54,10 @@ namespace GodotTools.Ides.Rider private static RiderInfo[] CollectAllRiderPathsLinux() { var installInfos = new List<RiderInfo>(); - var home = Environment.GetEnvironmentVariable("HOME"); + string home = Environment.GetEnvironmentVariable("HOME"); if (!string.IsNullOrEmpty(home)) { - var toolboxRiderRootPath = GetToolboxBaseDir(); + string toolboxRiderRootPath = GetToolboxBaseDir(); installInfos.AddRange(CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider.sh", false) .Select(a => new RiderInfo(a, true)).ToList()); @@ -65,12 +66,12 @@ namespace GodotTools.Ides.Rider if (shortcut.Exists) { - var lines = File.ReadAllLines(shortcut.FullName); - foreach (var line in lines) + string[] lines = File.ReadAllLines(shortcut.FullName); + foreach (string line in lines) { if (!line.StartsWith("Exec=\"")) continue; - var path = line.Split('"').Where((item, index) => index == 1).SingleOrDefault(); + string path = line.Split('"').Where((item, index) => index == 1).SingleOrDefault(); if (string.IsNullOrEmpty(path)) continue; @@ -82,7 +83,7 @@ namespace GodotTools.Ides.Rider } // snap install - var snapInstallPath = "/snap/rider/current/bin/rider.sh"; + string snapInstallPath = "/snap/rider/current/bin/rider.sh"; if (new FileInfo(snapInstallPath).Exists) installInfos.Add(new RiderInfo(snapInstallPath, false)); @@ -98,15 +99,15 @@ namespace GodotTools.Ides.Rider if (folder.Exists) { installInfos.AddRange(folder.GetDirectories("*Rider*.app") - .Select(a => new RiderInfo(Path.Combine(a.FullName, "Contents/MacOS/rider"), false)) - .ToList()); + .Select(a => new RiderInfo(Path.Combine(a.FullName, "Contents/MacOS/rider"), false)) + .ToList()); } // /Users/user/Library/Application Support/JetBrains/Toolbox/apps/Rider/ch-1/181.3870.267/Rider EAP.app // should be combined with "Contents/MacOS/rider" - var toolboxRiderRootPath = GetToolboxBaseDir(); + string toolboxRiderRootPath = GetToolboxBaseDir(); var paths = CollectPathsFromToolbox(toolboxRiderRootPath, "", "Rider*.app", true) - .Select(a => new RiderInfo(Path.Combine(a, "Contents/MacOS/rider"), true)); + .Select(a => new RiderInfo(Path.Combine(a, "Contents/MacOS/rider"), true)); installInfos.AddRange(paths); return installInfos.ToArray(); @@ -134,7 +135,7 @@ namespace GodotTools.Ides.Rider { if (OS.IsWindows) { - var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); return GetToolboxRiderRootPath(localAppData); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index 60dd565ef2..ac29efb716 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -49,7 +49,7 @@ namespace GodotTools.Ides.Rider if (!paths.Any()) return; - var newPath = paths.Last().Path; + string newPath = paths.Last().Path; Globals.EditorDef(EditorPathSettingName, newPath); editorSettings.SetSetting(EditorPathSettingName, newPath); } @@ -57,7 +57,8 @@ namespace GodotTools.Ides.Rider public static bool IsExternalEditorSetToRider(EditorSettings editorSettings) { - return editorSettings.HasSetting(EditorPathSettingName) && IsRider((string)editorSettings.GetSetting(EditorPathSettingName)); + return editorSettings.HasSetting(EditorPathSettingName) && + IsRider((string)editorSettings.GetSetting(EditorPathSettingName)); } public static bool IsRider(string path) @@ -66,7 +67,7 @@ namespace GodotTools.Ides.Rider return false; var fileInfo = new FileInfo(path); - var filename = fileInfo.Name.ToLowerInvariant(); + string filename = fileInfo.Name.ToLowerInvariant(); return filename.StartsWith("rider", StringComparison.Ordinal); } @@ -83,7 +84,7 @@ namespace GodotTools.Ides.Rider if (!paths.Any()) return null; - var newPath = paths.Last().Path; + string newPath = paths.Last().Path; editorSettings.SetSetting(EditorPathSettingName, newPath); Globals.EditorDef(EditorPathSettingName, newPath); return newPath; @@ -96,8 +97,8 @@ namespace GodotTools.Ides.Rider public static void OpenFile(string slnPath, string scriptPath, int line) { - var pathFromSettings = GetRiderPathFromSettings(); - var path = CheckAndUpdatePath(pathFromSettings); + string pathFromSettings = GetRiderPathFromSettings(); + string path = CheckAndUpdatePath(pathFromSettings); var args = new List<string>(); args.Add(slnPath); diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs index 6893bc1974..5e70c399b2 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -39,45 +39,57 @@ namespace GodotTools.Internals [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResDataDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResMetadataDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResAssembliesBaseDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResAssembliesDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResConfigDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResTempDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResTempAssembliesBaseDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResTempAssembliesDir(); [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_MonoUserDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_MonoLogsDir(); #region Tools-only [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_MonoSolutionsDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_BuildLogsDirs(); [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ProjectSlnPath(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ProjectCsProjPath(); [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_DataEditorToolsDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_DataEditorPrebuiltApiDir(); #endregion [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_DataMonoEtcDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_DataMonoLibDir(); diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs index c6724ccaf7..05499339b1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs @@ -8,7 +8,7 @@ namespace GodotTools.Utils { public static class FsPathUtils { - private static readonly string ResourcePath = ProjectSettings.GlobalizePath("res://"); + private static readonly string _resourcePath = ProjectSettings.GlobalizePath("res://"); private static bool PathStartsWithAlreadyNorm(this string childPath, string parentPath) { @@ -34,7 +34,7 @@ namespace GodotTools.Utils public static string LocalizePathWithCaseChecked(string path) { string pathNorm = path.NormalizePath() + Path.DirectorySeparatorChar; - string resourcePathNorm = ResourcePath.NormalizePath() + Path.DirectorySeparatorChar; + string resourcePathNorm = _resourcePath.NormalizePath() + Path.DirectorySeparatorChar; if (!pathNorm.PathStartsWithAlreadyNorm(resourcePathNorm)) return null; diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index 4624439665..93a1360cb6 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -13,10 +13,10 @@ namespace GodotTools.Utils public static class OS { [MethodImpl(MethodImplOptions.InternalCall)] - static extern string GetPlatformName(); + private static extern string GetPlatformName(); [MethodImpl(MethodImplOptions.InternalCall)] - static extern bool UnixFileHasExecutableAccess(string filePath); + private static extern bool UnixFileHasExecutableAccess(string filePath); public static class Names { @@ -106,7 +106,10 @@ namespace GodotTools.Utils public static string PathWhich([NotNull] string name) { - return IsWindows ? PathWhichWindows(name) : PathWhichUnix(name); + if (IsWindows) + return PathWhichWindows(name); + + return PathWhichUnix(name); } private static string PathWhichWindows([NotNull] string name) @@ -129,7 +132,8 @@ namespace GodotTools.Utils } string nameExt = Path.GetExtension(name); - bool hasPathExt = !string.IsNullOrEmpty(nameExt) && windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase); + bool hasPathExt = !string.IsNullOrEmpty(nameExt) && + windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase); searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 7fdef8ff45..e03c5fd248 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -387,7 +387,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(link_target); xml_output.append("</c>"); } - } else if (link_tag == "const") { + } else if (link_tag == "constant") { if (!target_itype || !target_itype->is_object_type) { if (OS::get_singleton()->is_stdout_verbose()) { if (target_itype) { @@ -2610,7 +2610,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { Map<StringName, StringName> accessor_methods; for (const PropertyInfo &property : property_list) { - if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY) { + if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY || (property.type == Variant::NIL && property.usage & PROPERTY_USAGE_ARRAY)) { continue; } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 8a85a1acbd..51a27ee934 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -575,7 +575,7 @@ class BindingsGenerator { StaticCString::create(_STR(PackedByteArray)), StaticCString::create(_STR(PackedInt32Array)), - StaticCString::create(_STR(PackedInt64rray)), + StaticCString::create(_STR(PackedInt64Array)), StaticCString::create(_STR(PackedFloat32Array)), StaticCString::create(_STR(PackedFloat64Array)), StaticCString::create(_STR(PackedStringArray)), diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp index b7b36a92d8..7433c865f5 100644 --- a/modules/mono/editor/code_completion.cpp +++ b/modules/mono/editor/code_completion.cpp @@ -121,10 +121,10 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr case CompletionKind::NODE_PATHS: { { // AutoLoads - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : autoloads) { - const ProjectSettings::AutoloadInfo &info = E.value; + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.value(); suggestions.push_back(quoted("/root/" + String(info.name))); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs index 1a3b81487f..8b12537f7f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs @@ -20,7 +20,7 @@ namespace Godot private Vector3 _size; /// <summary> - /// Beginning corner. Typically has values lower than End. + /// Beginning corner. Typically has values lower than <see cref="End"/>. /// </summary> /// <value>Directly uses a private field.</value> public Vector3 Position @@ -30,7 +30,7 @@ namespace Godot } /// <summary> - /// Size from Position to End. Typically all components are positive. + /// Size from <see cref="Position"/> to <see cref="End"/>. Typically all components are positive. /// If the size is negative, you can use <see cref="Abs"/> to fix it. /// </summary> /// <value>Directly uses a private field.</value> @@ -44,7 +44,10 @@ namespace Godot /// Ending corner. This is calculated as <see cref="Position"/> plus /// <see cref="Size"/>. Setting this value will change the size. /// </summary> - /// <value>Getting is equivalent to `value = Position + Size`, setting is equivalent to `Size = value - Position`.</value> + /// <value> + /// Getting is equivalent to <paramref name="value"/> = <see cref="Position"/> + <see cref="Size"/>, + /// setting is equivalent to <see cref="Size"/> = <paramref name="value"/> - <see cref="Position"/> + /// </value> public Vector3 End { get { return _position + _size; } @@ -52,10 +55,10 @@ namespace Godot } /// <summary> - /// Returns an AABB with equivalent position and size, modified so that + /// Returns an <see cref="AABB"/> with equivalent position and size, modified so that /// the most-negative corner is the origin and the size is positive. /// </summary> - /// <returns>The modified AABB.</returns> + /// <returns>The modified <see cref="AABB"/>.</returns> public AABB Abs() { Vector3 end = End; @@ -64,30 +67,32 @@ namespace Godot } /// <summary> - /// Returns true if this AABB completely encloses another one. + /// Returns <see langword="true"/> if this <see cref="AABB"/> completely encloses another one. /// </summary> - /// <param name="with">The other AABB that may be enclosed.</param> - /// <returns>A bool for whether or not this AABB encloses `b`.</returns> + /// <param name="with">The other <see cref="AABB"/> that may be enclosed.</param> + /// <returns> + /// A <see langword="bool"/> for whether or not this <see cref="AABB"/> encloses <paramref name="with"/>. + /// </returns> public bool Encloses(AABB with) { - Vector3 src_min = _position; - Vector3 src_max = _position + _size; - Vector3 dst_min = with._position; - Vector3 dst_max = with._position + with._size; + Vector3 srcMin = _position; + Vector3 srcMax = _position + _size; + Vector3 dstMin = with._position; + Vector3 dstMax = with._position + with._size; - return src_min.x <= dst_min.x && - src_max.x > dst_max.x && - src_min.y <= dst_min.y && - src_max.y > dst_max.y && - src_min.z <= dst_min.z && - src_max.z > dst_max.z; + return srcMin.x <= dstMin.x && + srcMax.x > dstMax.x && + srcMin.y <= dstMin.y && + srcMax.y > dstMax.y && + srcMin.z <= dstMin.z && + srcMax.z > dstMax.z; } /// <summary> - /// Returns this AABB expanded to include a given point. + /// Returns this <see cref="AABB"/> expanded to include a given point. /// </summary> /// <param name="point">The point to include.</param> - /// <returns>The expanded AABB.</returns> + /// <returns>The expanded <see cref="AABB"/>.</returns> public AABB Expand(Vector3 point) { Vector3 begin = _position; @@ -123,7 +128,7 @@ namespace Godot } /// <summary> - /// Returns the area of the AABB. + /// Returns the area of the <see cref="AABB"/>. /// </summary> /// <returns>The area.</returns> public real_t GetArea() @@ -132,10 +137,10 @@ namespace Godot } /// <summary> - /// Gets the position of one of the 8 endpoints of the AABB. + /// Gets the position of one of the 8 endpoints of the <see cref="AABB"/>. /// </summary> /// <param name="idx">Which endpoint to get.</param> - /// <returns>An endpoint of the AABB.</returns> + /// <returns>An endpoint of the <see cref="AABB"/>.</returns> public Vector3 GetEndpoint(int idx) { switch (idx) @@ -157,26 +162,29 @@ namespace Godot case 7: return new Vector3(_position.x + _size.x, _position.y + _size.y, _position.z + _size.z); default: - throw new ArgumentOutOfRangeException(nameof(idx), String.Format("Index is {0}, but a value from 0 to 7 is expected.", idx)); + { + throw new ArgumentOutOfRangeException(nameof(idx), + $"Index is {idx}, but a value from 0 to 7 is expected."); + } } } /// <summary> - /// Returns the normalized longest axis of the AABB. + /// Returns the normalized longest axis of the <see cref="AABB"/>. /// </summary> - /// <returns>A vector representing the normalized longest axis of the AABB.</returns> + /// <returns>A vector representing the normalized longest axis of the <see cref="AABB"/>.</returns> public Vector3 GetLongestAxis() { var axis = new Vector3(1f, 0f, 0f); - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y > max_size) + if (_size.y > maxSize) { axis = new Vector3(0f, 1f, 0f); - max_size = _size.y; + maxSize = _size.y; } - if (_size.z > max_size) + if (_size.z > maxSize) { axis = new Vector3(0f, 0f, 1f); } @@ -185,21 +193,21 @@ namespace Godot } /// <summary> - /// Returns the <see cref="Vector3.Axis"/> index of the longest axis of the AABB. + /// Returns the <see cref="Vector3.Axis"/> index of the longest axis of the <see cref="AABB"/>. /// </summary> /// <returns>A <see cref="Vector3.Axis"/> index for which axis is longest.</returns> public Vector3.Axis GetLongestAxisIndex() { var axis = Vector3.Axis.X; - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y > max_size) + if (_size.y > maxSize) { axis = Vector3.Axis.Y; - max_size = _size.y; + maxSize = _size.y; } - if (_size.z > max_size) + if (_size.z > maxSize) { axis = Vector3.Axis.Z; } @@ -208,38 +216,38 @@ namespace Godot } /// <summary> - /// Returns the scalar length of the longest axis of the AABB. + /// Returns the scalar length of the longest axis of the <see cref="AABB"/>. /// </summary> - /// <returns>The scalar length of the longest axis of the AABB.</returns> + /// <returns>The scalar length of the longest axis of the <see cref="AABB"/>.</returns> public real_t GetLongestAxisSize() { - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y > max_size) - max_size = _size.y; + if (_size.y > maxSize) + maxSize = _size.y; - if (_size.z > max_size) - max_size = _size.z; + if (_size.z > maxSize) + maxSize = _size.z; - return max_size; + return maxSize; } /// <summary> - /// Returns the normalized shortest axis of the AABB. + /// Returns the normalized shortest axis of the <see cref="AABB"/>. /// </summary> - /// <returns>A vector representing the normalized shortest axis of the AABB.</returns> + /// <returns>A vector representing the normalized shortest axis of the <see cref="AABB"/>.</returns> public Vector3 GetShortestAxis() { var axis = new Vector3(1f, 0f, 0f); - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y < max_size) + if (_size.y < maxSize) { axis = new Vector3(0f, 1f, 0f); - max_size = _size.y; + maxSize = _size.y; } - if (_size.z < max_size) + if (_size.z < maxSize) { axis = new Vector3(0f, 0f, 1f); } @@ -248,21 +256,21 @@ namespace Godot } /// <summary> - /// Returns the <see cref="Vector3.Axis"/> index of the shortest axis of the AABB. + /// Returns the <see cref="Vector3.Axis"/> index of the shortest axis of the <see cref="AABB"/>. /// </summary> /// <returns>A <see cref="Vector3.Axis"/> index for which axis is shortest.</returns> public Vector3.Axis GetShortestAxisIndex() { var axis = Vector3.Axis.X; - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y < max_size) + if (_size.y < maxSize) { axis = Vector3.Axis.Y; - max_size = _size.y; + maxSize = _size.y; } - if (_size.z < max_size) + if (_size.z < maxSize) { axis = Vector3.Axis.Z; } @@ -271,20 +279,20 @@ namespace Godot } /// <summary> - /// Returns the scalar length of the shortest axis of the AABB. + /// Returns the scalar length of the shortest axis of the <see cref="AABB"/>. /// </summary> - /// <returns>The scalar length of the shortest axis of the AABB.</returns> + /// <returns>The scalar length of the shortest axis of the <see cref="AABB"/>.</returns> public real_t GetShortestAxisSize() { - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y < max_size) - max_size = _size.y; + if (_size.y < maxSize) + maxSize = _size.y; - if (_size.z < max_size) - max_size = _size.z; + if (_size.z < maxSize) + maxSize = _size.z; - return max_size; + return maxSize; } /// <summary> @@ -295,23 +303,23 @@ namespace Godot /// <returns>A vector representing the support.</returns> public Vector3 GetSupport(Vector3 dir) { - Vector3 half_extents = _size * 0.5f; - Vector3 ofs = _position + half_extents; + Vector3 halfExtents = _size * 0.5f; + Vector3 ofs = _position + halfExtents; return ofs + new Vector3( - dir.x > 0f ? -half_extents.x : half_extents.x, - dir.y > 0f ? -half_extents.y : half_extents.y, - dir.z > 0f ? -half_extents.z : half_extents.z); + dir.x > 0f ? -halfExtents.x : halfExtents.x, + dir.y > 0f ? -halfExtents.y : halfExtents.y, + dir.z > 0f ? -halfExtents.z : halfExtents.z); } /// <summary> - /// Returns a copy of the AABB grown a given amount of units towards all the sides. + /// Returns a copy of the <see cref="AABB"/> grown a given amount of units towards all the sides. /// </summary> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown AABB.</returns> + /// <returns>The grown <see cref="AABB"/>.</returns> public AABB Grow(real_t by) { - var res = this; + AABB res = this; res._position.x -= by; res._position.y -= by; @@ -324,28 +332,37 @@ namespace Godot } /// <summary> - /// Returns true if the AABB is flat or empty, or false otherwise. + /// Returns <see langword="true"/> if the <see cref="AABB"/> is flat or empty, + /// or <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool for whether or not the AABB has area.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> has area. + /// </returns> public bool HasNoArea() { return _size.x <= 0f || _size.y <= 0f || _size.z <= 0f; } /// <summary> - /// Returns true if the AABB has no surface (no size), or false otherwise. + /// Returns <see langword="true"/> if the <see cref="AABB"/> has no surface (no size), + /// or <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool for whether or not the AABB has area.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> has area. + /// </returns> public bool HasNoSurface() { return _size.x <= 0f && _size.y <= 0f && _size.z <= 0f; } /// <summary> - /// Returns true if the AABB contains a point, or false otherwise. + /// Returns <see langword="true"/> if the <see cref="AABB"/> contains a point, + /// or <see langword="false"/> otherwise. /// </summary> /// <param name="point">The point to check.</param> - /// <returns>A bool for whether or not the AABB contains `point`.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> contains <paramref name="point"/>. + /// </returns> public bool HasPoint(Vector3 point) { if (point.x < _position.x) @@ -365,56 +382,59 @@ namespace Godot } /// <summary> - /// Returns the intersection of this AABB and `b`. + /// Returns the intersection of this <see cref="AABB"/> and <paramref name="with"/>. /// </summary> - /// <param name="with">The other AABB.</param> - /// <returns>The clipped AABB.</returns> + /// <param name="with">The other <see cref="AABB"/>.</param> + /// <returns>The clipped <see cref="AABB"/>.</returns> public AABB Intersection(AABB with) { - Vector3 src_min = _position; - Vector3 src_max = _position + _size; - Vector3 dst_min = with._position; - Vector3 dst_max = with._position + with._size; + Vector3 srcMin = _position; + Vector3 srcMax = _position + _size; + Vector3 dstMin = with._position; + Vector3 dstMax = with._position + with._size; Vector3 min, max; - if (src_min.x > dst_max.x || src_max.x < dst_min.x) + if (srcMin.x > dstMax.x || srcMax.x < dstMin.x) { return new AABB(); } - min.x = src_min.x > dst_min.x ? src_min.x : dst_min.x; - max.x = src_max.x < dst_max.x ? src_max.x : dst_max.x; + min.x = srcMin.x > dstMin.x ? srcMin.x : dstMin.x; + max.x = srcMax.x < dstMax.x ? srcMax.x : dstMax.x; - if (src_min.y > dst_max.y || src_max.y < dst_min.y) + if (srcMin.y > dstMax.y || srcMax.y < dstMin.y) { return new AABB(); } - min.y = src_min.y > dst_min.y ? src_min.y : dst_min.y; - max.y = src_max.y < dst_max.y ? src_max.y : dst_max.y; + min.y = srcMin.y > dstMin.y ? srcMin.y : dstMin.y; + max.y = srcMax.y < dstMax.y ? srcMax.y : dstMax.y; - if (src_min.z > dst_max.z || src_max.z < dst_min.z) + if (srcMin.z > dstMax.z || srcMax.z < dstMin.z) { return new AABB(); } - min.z = src_min.z > dst_min.z ? src_min.z : dst_min.z; - max.z = src_max.z < dst_max.z ? src_max.z : dst_max.z; + min.z = srcMin.z > dstMin.z ? srcMin.z : dstMin.z; + max.z = srcMax.z < dstMax.z ? srcMax.z : dstMax.z; return new AABB(min, max - min); } /// <summary> - /// Returns true if the AABB overlaps with `b` + /// Returns <see langword="true"/> if the <see cref="AABB"/> overlaps with <paramref name="with"/> /// (i.e. they have at least one point in common). /// - /// If `includeBorders` is true, they will also be considered overlapping - /// if their borders touch, even without intersection. + /// If <paramref name="includeBorders"/> is <see langword="true"/>, + /// they will also be considered overlapping if their borders touch, + /// even without intersection. /// </summary> - /// <param name="with">The other AABB to check for intersections with.</param> + /// <param name="with">The other <see cref="AABB"/> to check for intersections with.</param> /// <param name="includeBorders">Whether or not to consider borders.</param> - /// <returns>A bool for whether or not they are intersecting.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not they are intersecting. + /// </returns> public bool Intersects(AABB with, bool includeBorders = false) { if (includeBorders) @@ -452,10 +472,12 @@ namespace Godot } /// <summary> - /// Returns true if the AABB is on both sides of `plane`. + /// Returns <see langword="true"/> if the <see cref="AABB"/> is on both sides of <paramref name="plane"/>. /// </summary> - /// <param name="plane">The plane to check for intersection.</param> - /// <returns>A bool for whether or not the AABB intersects the plane.</returns> + /// <param name="plane">The <see cref="Plane"/> to check for intersection.</param> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> intersects the <see cref="Plane"/>. + /// </returns> public bool IntersectsPlane(Plane plane) { Vector3[] points = @@ -489,11 +511,14 @@ namespace Godot } /// <summary> - /// Returns true if the AABB intersects the line segment between `from` and `to`. + /// Returns <see langword="true"/> if the <see cref="AABB"/> intersects + /// the line segment between <paramref name="from"/> and <paramref name="to"/>. /// </summary> /// <param name="from">The start of the line segment.</param> /// <param name="to">The end of the line segment.</param> - /// <returns>A bool for whether or not the AABB intersects the line segment.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> intersects the line segment. + /// </returns> public bool IntersectsSegment(Vector3 from, Vector3 to) { real_t min = 0f; @@ -549,10 +574,10 @@ namespace Godot } /// <summary> - /// Returns a larger AABB that contains this AABB and `b`. + /// Returns a larger <see cref="AABB"/> that contains this <see cref="AABB"/> and <paramref name="with"/>. /// </summary> - /// <param name="with">The other AABB.</param> - /// <returns>The merged AABB.</returns> + /// <param name="with">The other <see cref="AABB"/>.</param> + /// <returns>The merged <see cref="AABB"/>.</returns> public AABB Merge(AABB with) { Vector3 beg1 = _position; @@ -561,22 +586,22 @@ namespace Godot var end2 = new Vector3(with._size.x, with._size.y, with._size.z) + beg2; var min = new Vector3( - beg1.x < beg2.x ? beg1.x : beg2.x, - beg1.y < beg2.y ? beg1.y : beg2.y, - beg1.z < beg2.z ? beg1.z : beg2.z - ); + beg1.x < beg2.x ? beg1.x : beg2.x, + beg1.y < beg2.y ? beg1.y : beg2.y, + beg1.z < beg2.z ? beg1.z : beg2.z + ); var max = new Vector3( - end1.x > end2.x ? end1.x : end2.x, - end1.y > end2.y ? end1.y : end2.y, - end1.z > end2.z ? end1.z : end2.z - ); + end1.x > end2.x ? end1.x : end2.x, + end1.y > end2.y ? end1.y : end2.y, + end1.z > end2.z ? end1.z : end2.z + ); return new AABB(min, max - min); } /// <summary> - /// Constructs an AABB from a position and size. + /// Constructs an <see cref="AABB"/> from a position and size. /// </summary> /// <param name="position">The position.</param> /// <param name="size">The size, typically positive.</param> @@ -587,7 +612,8 @@ namespace Godot } /// <summary> - /// Constructs an AABB from a position, width, height, and depth. + /// Constructs an <see cref="AABB"/> from a <paramref name="position"/>, + /// <paramref name="width"/>, <paramref name="height"/>, and <paramref name="depth"/>. /// </summary> /// <param name="position">The position.</param> /// <param name="width">The width, typically positive.</param> @@ -600,7 +626,8 @@ namespace Godot } /// <summary> - /// Constructs an AABB from x, y, z, and size. + /// Constructs an <see cref="AABB"/> from <paramref name="x"/>, + /// <paramref name="y"/>, <paramref name="z"/>, and <paramref name="size"/>. /// </summary> /// <param name="x">The position's X coordinate.</param> /// <param name="y">The position's Y coordinate.</param> @@ -613,7 +640,9 @@ namespace Godot } /// <summary> - /// Constructs an AABB from x, y, z, width, height, and depth. + /// Constructs an <see cref="AABB"/> from <paramref name="x"/>, + /// <paramref name="y"/>, <paramref name="z"/>, <paramref name="width"/>, + /// <paramref name="height"/>, and <paramref name="depth"/>. /// </summary> /// <param name="x">The position's X coordinate.</param> /// <param name="y">The position's Y coordinate.</param> @@ -637,6 +666,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this AABB and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the AABB structure and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is AABB) @@ -647,32 +681,49 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this AABB and <paramref name="other"/> are equal + /// </summary> + /// <param name="other">The other AABB to compare.</param> + /// <returns>Whether or not the AABBs are equal.</returns> public bool Equals(AABB other) { return _position == other._position && _size == other._size; } /// <summary> - /// Returns true if this AABB and `other` are approximately equal, by running - /// <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component. + /// Returns <see langword="true"/> if this AABB and <paramref name="other"/> are approximately equal, + /// by running <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component. /// </summary> /// <param name="other">The other AABB to compare.</param> - /// <returns>Whether or not the AABBs are approximately equal.</returns> + /// <returns>Whether or not the AABBs structures are approximately equal.</returns> public bool IsEqualApprox(AABB other) { return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other._size); } + /// <summary> + /// Serves as the hash function for <see cref="AABB"/>. + /// </summary> + /// <returns>A hash code for this AABB.</returns> public override int GetHashCode() { return _position.GetHashCode() ^ _size.GetHashCode(); } + /// <summary> + /// Converts this <see cref="AABB"/> to a string. + /// </summary> + /// <returns>A string representation of this AABB.</returns> public override string ToString() { return $"{_position}, {_size}"; } + /// <summary> + /// Converts this <see cref="AABB"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this AABB.</returns> public string ToString(string format) { return $"{_position.ToString(format)}, {_size.ToString(format)}"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index f52a767018..a412047196 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -6,7 +6,7 @@ using System.Runtime.InteropServices; namespace Godot.Collections { - class ArraySafeHandle : SafeHandle + internal class ArraySafeHandle : SafeHandle { public ArraySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) { @@ -33,15 +33,15 @@ namespace Godot.Collections /// </summary> public class Array : IList, IDisposable { - ArraySafeHandle safeHandle; - bool disposed = false; + private ArraySafeHandle _safeHandle; + private bool _disposed = false; /// <summary> /// Constructs a new empty <see cref="Array"/>. /// </summary> public Array() { - safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor()); + _safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor()); } /// <summary> @@ -69,31 +69,31 @@ namespace Godot.Collections { throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); } - safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor_MonoArray(array)); + _safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor_MonoArray(array)); } internal Array(ArraySafeHandle handle) { - safeHandle = handle; + _safeHandle = handle; } internal Array(IntPtr handle) { - safeHandle = new ArraySafeHandle(handle); + _safeHandle = new ArraySafeHandle(handle); } internal IntPtr GetPtr() { - if (disposed) + if (_disposed) throw new ObjectDisposedException(GetType().FullName); - return safeHandle.DangerousGetHandle(); + return _safeHandle.DangerousGetHandle(); } /// <summary> /// Duplicates this <see cref="Array"/>. /// </summary> - /// <param name="deep">If true, performs a deep copy.</param> + /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> /// <returns>A new Godot Array.</returns> public Array Duplicate(bool deep = false) { @@ -136,16 +136,16 @@ namespace Godot.Collections /// </summary> public void Dispose() { - if (disposed) + if (_disposed) return; - if (safeHandle != null) + if (_safeHandle != null) { - safeHandle.Dispose(); - safeHandle = null; + _safeHandle.Dispose(); + _safeHandle = null; } - disposed = true; + _disposed = true; } // IList @@ -155,9 +155,9 @@ namespace Godot.Collections bool IList.IsFixedSize => false; /// <summary> - /// Returns the object at the given index. + /// Returns the object at the given <paramref name="index"/>. /// </summary> - /// <value>The object at the given index.</value> + /// <value>The object at the given <paramref name="index"/>.</value> public object this[int index] { get => godot_icall_Array_At(GetPtr(), index); @@ -166,7 +166,7 @@ namespace Godot.Collections /// <summary> /// Adds an object to the end of this <see cref="Array"/>. - /// This is the same as `append` or `push_back` in GDScript. + /// This is the same as <c>append</c> or <c>push_back</c> in GDScript. /// </summary> /// <param name="value">The object to add.</param> /// <returns>The new size after adding the object.</returns> @@ -203,7 +203,7 @@ namespace Godot.Collections public void Insert(int index, object value) => godot_icall_Array_Insert(GetPtr(), index, value); /// <summary> - /// Removes the first occurrence of the specified value + /// Removes the first occurrence of the specified <paramref name="value"/> /// from this <see cref="Array"/>. /// </summary> /// <param name="value">The value to remove.</param> @@ -272,67 +272,67 @@ namespace Godot.Collections } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Array_Ctor(); + internal static extern IntPtr godot_icall_Array_Ctor(); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Array_Ctor_MonoArray(System.Array array); + internal static extern IntPtr godot_icall_Array_Ctor_MonoArray(System.Array array); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_Dtor(IntPtr ptr); + internal static extern void godot_icall_Array_Dtor(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_Array_At(IntPtr ptr, int index); + internal static extern object godot_icall_Array_At(IntPtr ptr, int index); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_Array_At_Generic(IntPtr ptr, int index, int elemTypeEncoding, IntPtr elemTypeClass); + internal static extern object godot_icall_Array_At_Generic(IntPtr ptr, int index, int elemTypeEncoding, IntPtr elemTypeClass); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_SetAt(IntPtr ptr, int index, object value); + internal static extern void godot_icall_Array_SetAt(IntPtr ptr, int index, object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_Array_Count(IntPtr ptr); + internal static extern int godot_icall_Array_Count(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_Array_Add(IntPtr ptr, object item); + internal static extern int godot_icall_Array_Add(IntPtr ptr, object item); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_Clear(IntPtr ptr); + internal static extern void godot_icall_Array_Clear(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Array_Concatenate(IntPtr left, IntPtr right); + internal static extern IntPtr godot_icall_Array_Concatenate(IntPtr left, IntPtr right); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Array_Contains(IntPtr ptr, object item); + internal static extern bool godot_icall_Array_Contains(IntPtr ptr, object item); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_CopyTo(IntPtr ptr, System.Array array, int arrayIndex); + internal static extern void godot_icall_Array_CopyTo(IntPtr ptr, System.Array array, int arrayIndex); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Array_Duplicate(IntPtr ptr, bool deep); + internal static extern IntPtr godot_icall_Array_Duplicate(IntPtr ptr, bool deep); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_Array_IndexOf(IntPtr ptr, object item); + internal static extern int godot_icall_Array_IndexOf(IntPtr ptr, object item); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_Insert(IntPtr ptr, int index, object item); + internal static extern void godot_icall_Array_Insert(IntPtr ptr, int index, object item); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Array_Remove(IntPtr ptr, object item); + internal static extern bool godot_icall_Array_Remove(IntPtr ptr, object item); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_RemoveAt(IntPtr ptr, int index); + internal static extern void godot_icall_Array_RemoveAt(IntPtr ptr, int index); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static Error godot_icall_Array_Resize(IntPtr ptr, int newSize); + internal static extern Error godot_icall_Array_Resize(IntPtr ptr, int newSize); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static Error godot_icall_Array_Shuffle(IntPtr ptr); + internal static extern Error godot_icall_Array_Shuffle(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_Generic_GetElementTypeInfo(Type elemType, out int elemTypeEncoding, out IntPtr elemTypeClass); + internal static extern void godot_icall_Array_Generic_GetElementTypeInfo(Type elemType, out int elemTypeEncoding, out IntPtr elemTypeClass); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_Array_ToString(IntPtr ptr); + internal static extern string godot_icall_Array_ToString(IntPtr ptr); } /// <summary> @@ -344,7 +344,7 @@ namespace Godot.Collections /// <typeparam name="T">The type of the array.</typeparam> public class Array<T> : IList<T>, ICollection<T>, IEnumerable<T> { - Array objectArray; + private Array _objectArray; internal static int elemTypeEncoding; internal static IntPtr elemTypeClass; @@ -359,7 +359,7 @@ namespace Godot.Collections /// </summary> public Array() { - objectArray = new Array(); + _objectArray = new Array(); } /// <summary> @@ -372,7 +372,7 @@ namespace Godot.Collections if (collection == null) throw new NullReferenceException($"Parameter '{nameof(collection)} cannot be null.'"); - objectArray = new Array(collection); + _objectArray = new Array(collection); } /// <summary> @@ -386,7 +386,7 @@ namespace Godot.Collections { throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); } - objectArray = new Array(array); + _objectArray = new Array(array); } /// <summary> @@ -395,22 +395,22 @@ namespace Godot.Collections /// <param name="array">The untyped array to construct from.</param> public Array(Array array) { - objectArray = array; + _objectArray = array; } internal Array(IntPtr handle) { - objectArray = new Array(handle); + _objectArray = new Array(handle); } internal Array(ArraySafeHandle handle) { - objectArray = new Array(handle); + _objectArray = new Array(handle); } internal IntPtr GetPtr() { - return objectArray.GetPtr(); + return _objectArray.GetPtr(); } /// <summary> @@ -419,17 +419,17 @@ namespace Godot.Collections /// <param name="from">The typed array to convert.</param> public static explicit operator Array(Array<T> from) { - return from.objectArray; + return from._objectArray; } /// <summary> /// Duplicates this <see cref="Array{T}"/>. /// </summary> - /// <param name="deep">If true, performs a deep copy.</param> + /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> /// <returns>A new Godot Array.</returns> public Array<T> Duplicate(bool deep = false) { - return new Array<T>(objectArray.Duplicate(deep)); + return new Array<T>(_objectArray.Duplicate(deep)); } /// <summary> @@ -439,7 +439,7 @@ namespace Godot.Collections /// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns> public Error Resize(int newSize) { - return objectArray.Resize(newSize); + return _objectArray.Resize(newSize); } /// <summary> @@ -447,7 +447,7 @@ namespace Godot.Collections /// </summary> public void Shuffle() { - objectArray.Shuffle(); + _objectArray.Shuffle(); } /// <summary> @@ -458,19 +458,19 @@ namespace Godot.Collections /// <returns>A new Godot Array with the contents of both arrays.</returns> public static Array<T> operator +(Array<T> left, Array<T> right) { - return new Array<T>(left.objectArray + right.objectArray); + return new Array<T>(left._objectArray + right._objectArray); } // IList<T> /// <summary> - /// Returns the value at the given index. + /// Returns the value at the given <paramref name="index"/>. /// </summary> - /// <value>The value at the given index.</value> + /// <value>The value at the given <paramref name="index"/>.</value> public T this[int index] { get { return (T)Array.godot_icall_Array_At_Generic(GetPtr(), index, elemTypeEncoding, elemTypeClass); } - set { objectArray[index] = value; } + set { _objectArray[index] = value; } } /// <summary> @@ -481,7 +481,7 @@ namespace Godot.Collections /// <returns>The index of the item, or -1 if not found.</returns> public int IndexOf(T item) { - return objectArray.IndexOf(item); + return _objectArray.IndexOf(item); } /// <summary> @@ -494,7 +494,7 @@ namespace Godot.Collections /// <param name="item">The item to insert.</param> public void Insert(int index, T item) { - objectArray.Insert(index, item); + _objectArray.Insert(index, item); } /// <summary> @@ -503,7 +503,7 @@ namespace Godot.Collections /// <param name="index">The index of the element to remove.</param> public void RemoveAt(int index) { - objectArray.RemoveAt(index); + _objectArray.RemoveAt(index); } // ICollection<T> @@ -515,20 +515,20 @@ namespace Godot.Collections /// <returns>The number of elements.</returns> public int Count { - get { return objectArray.Count; } + get { return _objectArray.Count; } } bool ICollection<T>.IsReadOnly => false; /// <summary> /// Adds an item to the end of this <see cref="Array{T}"/>. - /// This is the same as `append` or `push_back` in GDScript. + /// This is the same as <c>append</c> or <c>push_back</c> in GDScript. /// </summary> /// <param name="item">The item to add.</param> /// <returns>The new size after adding the item.</returns> public void Add(T item) { - objectArray.Add(item); + _objectArray.Add(item); } /// <summary> @@ -536,7 +536,7 @@ namespace Godot.Collections /// </summary> public void Clear() { - objectArray.Clear(); + _objectArray.Clear(); } /// <summary> @@ -546,7 +546,7 @@ namespace Godot.Collections /// <returns>Whether or not this array contains the given item.</returns> public bool Contains(T item) { - return objectArray.Contains(item); + return _objectArray.Contains(item); } /// <summary> @@ -566,7 +566,7 @@ namespace Godot.Collections // TODO This may be quite slow because every element access is an internal call. // It could be moved entirely to an internal call if we find out how to do the cast there. - int count = objectArray.Count; + int count = _objectArray.Count; if (array.Length < (arrayIndex + count)) throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); @@ -583,7 +583,7 @@ namespace Godot.Collections /// from this <see cref="Array{T}"/>. /// </summary> /// <param name="item">The value to remove.</param> - /// <returns>A bool indicating success or failure.</returns> + /// <returns>A <see langword="bool"/> indicating success or failure.</returns> public bool Remove(T item) { return Array.godot_icall_Array_Remove(GetPtr(), item); @@ -597,7 +597,7 @@ namespace Godot.Collections /// <returns>An enumerator.</returns> public IEnumerator<T> GetEnumerator() { - int count = objectArray.Count; + int count = _objectArray.Count; for (int i = 0; i < count; i++) { @@ -614,6 +614,6 @@ namespace Godot.Collections /// Converts this <see cref="Array{T}"/> to a string. /// </summary> /// <returns>A string representation of this array.</returns> - public override string ToString() => objectArray.ToString(); + public override string ToString() => _objectArray.ToString(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs index ac6cffceb2..e93bc89811 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs @@ -3,7 +3,5 @@ using System; namespace Godot { [AttributeUsage(AttributeTargets.Class)] - public class DisableGodotGeneratorsAttribute : Attribute - { - } + public class DisableGodotGeneratorsAttribute : Attribute { } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs index 6cec8773b2..2dedba2be3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs @@ -3,11 +3,8 @@ using System; namespace Godot { [AttributeUsage(AttributeTargets.Method)] - public class RemoteAttribute : Attribute {} + public class RemoteAttribute : Attribute { } [AttributeUsage(AttributeTargets.Method)] - public class MasterAttribute : Attribute {} - - [AttributeUsage(AttributeTargets.Method)] - public class PuppetAttribute : Attribute {} + public class PuppetAttribute : Attribute { } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs index 39d5782db8..07a214f543 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs @@ -3,7 +3,5 @@ using System; namespace Godot { [AttributeUsage(AttributeTargets.Delegate | AttributeTargets.Event)] - public class SignalAttribute : Attribute - { - } + public class SignalAttribute : Attribute { } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ToolAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ToolAttribute.cs index d0437409af..d2344389f4 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ToolAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ToolAttribute.cs @@ -3,5 +3,5 @@ using System; namespace Godot { [AttributeUsage(AttributeTargets.Class)] - public class ToolAttribute : Attribute {} + public class ToolAttribute : Attribute { } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 968f853c2d..0fb1df6c2f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -31,7 +31,7 @@ namespace Godot /// <summary> /// The basis matrix's X vector (column 0). /// </summary> - /// <value>Equivalent to <see cref="Column0"/> and array index `[0]`.</value> + /// <value>Equivalent to <see cref="Column0"/> and array index <c>[0]</c>.</value> public Vector3 x { get => Column0; @@ -41,7 +41,7 @@ namespace Godot /// <summary> /// The basis matrix's Y vector (column 1). /// </summary> - /// <value>Equivalent to <see cref="Column1"/> and array index `[1]`.</value> + /// <value>Equivalent to <see cref="Column1"/> and array index <c>[1]</c>.</value> public Vector3 y { get => Column1; @@ -51,7 +51,7 @@ namespace Godot /// <summary> /// The basis matrix's Z vector (column 2). /// </summary> - /// <value>Equivalent to <see cref="Column2"/> and array index `[2]`.</value> + /// <value>Equivalent to <see cref="Column2"/> and array index <c>[2]</c>.</value> public Vector3 z { get => Column2; @@ -82,45 +82,45 @@ namespace Godot /// <summary> /// Column 0 of the basis matrix (the X vector). /// </summary> - /// <value>Equivalent to <see cref="x"/> and array index `[0]`.</value> + /// <value>Equivalent to <see cref="x"/> and array index <c>[0]</c>.</value> public Vector3 Column0 { get => new Vector3(Row0.x, Row1.x, Row2.x); set { - this.Row0.x = value.x; - this.Row1.x = value.y; - this.Row2.x = value.z; + Row0.x = value.x; + Row1.x = value.y; + Row2.x = value.z; } } /// <summary> /// Column 1 of the basis matrix (the Y vector). /// </summary> - /// <value>Equivalent to <see cref="y"/> and array index `[1]`.</value> + /// <value>Equivalent to <see cref="y"/> and array index <c>[1]</c>.</value> public Vector3 Column1 { get => new Vector3(Row0.y, Row1.y, Row2.y); set { - this.Row0.y = value.x; - this.Row1.y = value.y; - this.Row2.y = value.z; + Row0.y = value.x; + Row1.y = value.y; + Row2.y = value.z; } } /// <summary> /// Column 2 of the basis matrix (the Z vector). /// </summary> - /// <value>Equivalent to <see cref="z"/> and array index `[2]`.</value> + /// <value>Equivalent to <see cref="z"/> and array index <c>[2]</c>.</value> public Vector3 Column2 { get => new Vector3(Row0.z, Row1.z, Row2.z); set { - this.Row0.z = value.x; - this.Row1.z = value.y; - this.Row2.z = value.z; + Row0.z = value.x; + Row1.z = value.y; + Row2.z = value.z; } } @@ -150,9 +150,10 @@ namespace Godot } /// <summary> - /// Access whole columns in the form of Vector3. + /// Access whole columns in the form of <see cref="Vector3"/>. /// </summary> /// <param name="column">Which column vector.</param> + /// <value>The basis column.</value> public Vector3 this[int column] { get @@ -193,6 +194,7 @@ namespace Godot /// </summary> /// <param name="column">Which column, the matrix horizontal position.</param> /// <param name="row">Which row, the matrix vertical position.</param> + /// <value>The matrix element.</value> public real_t this[int column, int row] { get @@ -207,6 +209,13 @@ namespace Godot } } + /// <summary> + /// Returns the <see cref="Basis"/>'s rotation in the form of a + /// <see cref="Quaternion"/>. See <see cref="GetEuler"/> if you + /// need Euler angles, but keep in mind quaternions should generally + /// be preferred to Euler angles. + /// </summary> + /// <returns>The basis rotation.</returns> public Quaternion GetRotationQuaternion() { Basis orthonormalizedBasis = Orthonormalized(); @@ -263,10 +272,10 @@ namespace Godot /// The returned vector contains the rotation angles in /// the format (X angle, Y angle, Z angle). /// - /// Consider using the <see cref="Basis.Quaternion()"/> method instead, which + /// Consider using the <see cref="Quaternion()"/> method instead, which /// returns a <see cref="Godot.Quaternion"/> quaternion instead of Euler angles. /// </summary> - /// <returns>A Vector3 representing the basis rotation in Euler angles.</returns> + /// <returns>A <see cref="Vector3"/> representing the basis rotation in Euler angles.</returns> public Vector3 GetEuler() { Basis m = Orthonormalized(); @@ -304,7 +313,10 @@ namespace Godot /// but are more efficient for some internal calculations. /// </summary> /// <param name="index">Which row.</param> - /// <returns>One of `Row0`, `Row1`, or `Row2`.</returns> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the <paramref name="index"/> is not 0, 1 or 2. + /// </exception> + /// <returns>One of <c>Row0</c>, <c>Row1</c>, or <c>Row2</c>.</returns> public Vector3 GetRow(int index) { switch (index) @@ -326,6 +338,9 @@ namespace Godot /// </summary> /// <param name="index">Which row.</param> /// <param name="value">The vector to set the row to.</param> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the <paramref name="index"/> is not 0, 1 or 2. + /// </exception> public void SetRow(int index, Vector3 value) { switch (index) @@ -452,8 +467,8 @@ namespace Godot } /// <summary> - /// Introduce an additional rotation around the given `axis` - /// by `phi` (in radians). The axis must be a normalized vector. + /// Introduce an additional rotation around the given <paramref name="axis"/> + /// by <paramref name="phi"/> (in radians). The axis must be a normalized vector. /// </summary> /// <param name="axis">The axis to rotate around. Must be normalized.</param> /// <param name="phi">The angle to rotate, in radians.</param> @@ -504,7 +519,7 @@ namespace Godot /// <returns>The resulting dot product.</returns> public real_t Tdotx(Vector3 with) { - return this.Row0[0] * with[0] + this.Row1[0] * with[1] + this.Row2[0] * with[2]; + return Row0[0] * with[0] + Row1[0] * with[1] + Row2[0] * with[2]; } /// <summary> @@ -514,7 +529,7 @@ namespace Godot /// <returns>The resulting dot product.</returns> public real_t Tdoty(Vector3 with) { - return this.Row0[1] * with[0] + this.Row1[1] * with[1] + this.Row2[1] * with[2]; + return Row0[1] * with[0] + Row1[1] * with[1] + Row2[1] * with[2]; } /// <summary> @@ -524,7 +539,7 @@ namespace Godot /// <returns>The resulting dot product.</returns> public real_t Tdotz(Vector3 with) { - return this.Row0[2] * with[0] + this.Row1[2] * with[1] + this.Row2[2] * with[2]; + return Row0[2] * with[0] + Row1[2] * with[1] + Row2[2] * with[2]; } /// <summary> @@ -533,7 +548,7 @@ namespace Godot /// <returns>The transposed basis matrix.</returns> public Basis Transposed() { - var tr = this; + Basis tr = this; real_t temp = tr.Row0[1]; tr.Row0[1] = tr.Row1[0]; @@ -553,15 +568,16 @@ namespace Godot /// <summary> /// Returns a vector transformed (multiplied) by the basis matrix. /// </summary> + /// <seealso cref="XformInv(Vector3)"/> /// <param name="v">A vector to transform.</param> /// <returns>The transformed vector.</returns> public Vector3 Xform(Vector3 v) { return new Vector3 ( - this.Row0.Dot(v), - this.Row1.Dot(v), - this.Row2.Dot(v) + Row0.Dot(v), + Row1.Dot(v), + Row2.Dot(v) ); } @@ -571,15 +587,16 @@ namespace Godot /// Note: This results in a multiplication by the inverse of the /// basis matrix only if it represents a rotation-reflection. /// </summary> + /// <seealso cref="Xform(Vector3)"/> /// <param name="v">A vector to inversely transform.</param> /// <returns>The inversely transformed vector.</returns> public Vector3 XformInv(Vector3 v) { return new Vector3 ( - this.Row0[0] * v.x + this.Row1[0] * v.y + this.Row2[0] * v.z, - this.Row0[1] * v.x + this.Row1[1] * v.y + this.Row2[1] * v.z, - this.Row0[2] * v.x + this.Row1[2] * v.y + this.Row2[2] * v.z + Row0[0] * v.x + Row1[0] * v.y + Row2[0] * v.z, + Row0[1] * v.x + Row1[1] * v.y + Row2[1] * v.z, + Row0[2] * v.x + Row1[2] * v.y + Row2[2] * v.z ); } @@ -675,25 +692,25 @@ namespace Godot /// <summary> /// The identity basis, with no rotation or scaling applied. - /// This is used as a replacement for `Basis()` in GDScript. - /// Do not use `new Basis()` with no arguments in C#, because it sets all values to zero. + /// This is used as a replacement for <c>Basis()</c> in GDScript. + /// Do not use <c>new Basis()</c> with no arguments in C#, because it sets all values to zero. /// </summary> - /// <value>Equivalent to `new Basis(Vector3.Right, Vector3.Up, Vector3.Back)`.</value> + /// <value>Equivalent to <c>new Basis(Vector3.Right, Vector3.Up, Vector3.Back)</c>.</value> public static Basis Identity { get { return _identity; } } /// <summary> /// The basis that will flip something along the X axis when used in a transformation. /// </summary> - /// <value>Equivalent to `new Basis(Vector3.Left, Vector3.Up, Vector3.Back)`.</value> + /// <value>Equivalent to <c>new Basis(Vector3.Left, Vector3.Up, Vector3.Back)</c>.</value> public static Basis FlipX { get { return _flipX; } } /// <summary> /// The basis that will flip something along the Y axis when used in a transformation. /// </summary> - /// <value>Equivalent to `new Basis(Vector3.Right, Vector3.Down, Vector3.Back)`.</value> + /// <value>Equivalent to <c>new Basis(Vector3.Right, Vector3.Down, Vector3.Back)</c>.</value> public static Basis FlipY { get { return _flipY; } } /// <summary> /// The basis that will flip something along the Z axis when used in a transformation. /// </summary> - /// <value>Equivalent to `new Basis(Vector3.Right, Vector3.Up, Vector3.Forward)`.</value> + /// <value>Equivalent to <c>new Basis(Vector3.Right, Vector3.Up, Vector3.Forward)</c>.</value> public static Basis FlipZ { get { return _flipZ; } } /// <summary> @@ -752,8 +769,8 @@ namespace Godot } /// <summary> - /// Constructs a pure rotation basis matrix, rotated around the given `axis` - /// by `phi` (in radians). The axis must be a normalized vector. + /// Constructs a pure rotation basis matrix, rotated around the given <paramref name="axis"/> + /// by <paramref name="phi"/> (in radians). The axis must be a normalized vector. /// </summary> /// <param name="axis">The axis to rotate around. Must be normalized.</param> /// <param name="phi">The angle to rotate, in radians.</param> @@ -830,6 +847,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this basis and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the basis and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Basis) @@ -840,32 +862,49 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this basis and <paramref name="other"/> are equal + /// </summary> + /// <param name="other">The other basis to compare.</param> + /// <returns>Whether or not the bases are equal.</returns> public bool Equals(Basis other) { return Row0.Equals(other.Row0) && Row1.Equals(other.Row1) && Row2.Equals(other.Row2); } /// <summary> - /// Returns true if this basis and `other` are approximately equal, by running - /// <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component. + /// Returns <see langword="true"/> if this basis and <paramref name="other"/> are approximately equal, + /// by running <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component. /// </summary> /// <param name="other">The other basis to compare.</param> - /// <returns>Whether or not the matrices are approximately equal.</returns> + /// <returns>Whether or not the bases are approximately equal.</returns> public bool IsEqualApprox(Basis other) { return Row0.IsEqualApprox(other.Row0) && Row1.IsEqualApprox(other.Row1) && Row2.IsEqualApprox(other.Row2); } + /// <summary> + /// Serves as the hash function for <see cref="Basis"/>. + /// </summary> + /// <returns>A hash code for this basis.</returns> public override int GetHashCode() { return Row0.GetHashCode() ^ Row1.GetHashCode() ^ Row2.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Basis"/> to a string. + /// </summary> + /// <returns>A string representation of this basis.</returns> public override string ToString() { return $"[X: {x}, Y: {y}, Z: {z}]"; } + /// <summary> + /// Converts this <see cref="Basis"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this basis.</returns> public string ToString(string format) { return $"[X: {x.ToString(format)}, Y: {y.ToString(format)}, Z: {z.ToString(format)}]"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs index c85cc1884c..a28a46896b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -2,18 +2,58 @@ using System; namespace Godot { + /// <summary> + /// Callable is a first class object which can be held in variables and passed to functions. + /// It represents a given method in an Object, and is typically used for signal callbacks. + /// </summary> + /// <example> + /// <code> + /// public void PrintArgs(object ar1, object arg2, object arg3 = null) + /// { + /// GD.PrintS(arg1, arg2, arg3); + /// } + /// + /// public void Test() + /// { + /// // This Callable object will call the PrintArgs method defined above. + /// Callable callable = new Callable(this, nameof(PrintArgs)); + /// callable.Call("hello", "world"); // Prints "hello world null". + /// callable.Call(Vector2.Up, 42, callable); // Prints "(0, -1) 42 Node(Node.cs)::PrintArgs". + /// callable.Call("invalid"); // Invalid call, should have at least 2 arguments. + /// } + /// </code> + /// </example> public struct Callable { private readonly Object _target; private readonly StringName _method; private readonly Delegate _delegate; + /// <summary> + /// Object that contains the method. + /// </summary> public Object Target => _target; + /// <summary> + /// Name of the method that will be called. + /// </summary> public StringName Method => _method; + /// <summary> + /// Delegate of the method that will be called. + /// </summary> public Delegate Delegate => _delegate; + /// <summary> + /// Converts a <see cref="Delegate"/> to a <see cref="Callable"/>. + /// </summary> + /// <param name="delegate">The delegate to convert.</param> public static implicit operator Callable(Delegate @delegate) => new Callable(@delegate); + /// <summary> + /// Constructs a new <see cref="Callable"/> for the method called <paramref name="method"/> + /// in the specified <paramref name="target"/>. + /// </summary> + /// <param name="target">Object that contains the method.</param> + /// <param name="method">Name of the method that will be called.</param> public Callable(Object target, StringName method) { _target = target; @@ -21,6 +61,10 @@ namespace Godot _delegate = null; } + /// <summary> + /// Constructs a new <see cref="Callable"/> for the given <paramref name="delegate"/>. + /// </summary> + /// <param name="delegate">Delegate method that will be called.</param> public Callable(Delegate @delegate) { _target = null; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index b9a98ba9c7..2a869bc335 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -7,11 +7,11 @@ namespace Godot /// A color represented by red, green, blue, and alpha (RGBA) components. /// The alpha component is often used for transparency. /// Values are in floating-point and usually range from 0 to 1. - /// Some properties (such as CanvasItem.modulate) may accept values + /// Some properties (such as <see cref="CanvasItem.Modulate"/>) may accept values /// greater than 1 (overbright or HDR colors). /// /// If you want to supply values in a range of 0 to 255, you should use - /// <see cref="Color8"/> and the `r8`/`g8`/`b8`/`a8` properties. + /// <see cref="Color8"/> and the <c>r8</c>/<c>g8</c>/<c>b8</c>/<c>a8</c> properties. /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] @@ -127,11 +127,11 @@ namespace Godot } else if (g == max) { - h = 2 + (b - r) / delta; // Between cyan & yellow + h = 2 + ((b - r) / delta); // Between cyan & yellow } else { - h = 4 + (r - g) / delta; // Between magenta & cyan + h = 4 + ((r - g) / delta); // Between magenta & cyan } h /= 6.0f; @@ -173,7 +173,7 @@ namespace Godot /// <summary> /// The HSV value (brightness) of this color, on the range 0 to 1. /// </summary> - /// <value>Getting is equivalent to using `Max()` on the RGB components. Setting uses <see cref="FromHSV"/>.</value> + /// <value>Getting is equivalent to using <see cref="Math.Max(float, float)"/> on the RGB components. Setting uses <see cref="FromHSV"/>.</value> public float v { get @@ -189,7 +189,12 @@ namespace Godot /// <summary> /// Access color components using their index. /// </summary> - /// <value>`[0]` is equivalent to `.r`, `[1]` is equivalent to `.g`, `[2]` is equivalent to `.b`, `[3]` is equivalent to `.a`.</value> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="r"/>, + /// <c>[1]</c> is equivalent to <see cref="g"/>, + /// <c>[2]</c> is equivalent to <see cref="b"/>, + /// <c>[3]</c> is equivalent to <see cref="a"/>. + /// </value> public float this[int index] { get @@ -236,30 +241,30 @@ namespace Godot /// The second color may have a range of alpha values. /// </summary> /// <param name="over">The color to blend over.</param> - /// <returns>This color blended over `over`.</returns> + /// <returns>This color blended over <paramref name="over"/>.</returns> public Color Blend(Color over) { Color res; float sa = 1.0f - over.a; - res.a = a * sa + over.a; + res.a = (a * sa) + over.a; if (res.a == 0) { return new Color(0, 0, 0, 0); } - res.r = (r * a * sa + over.r * over.a) / res.a; - res.g = (g * a * sa + over.g * over.a) / res.a; - res.b = (b * a * sa + over.b * over.a) / res.a; + res.r = ((r * a * sa) + (over.r * over.a)) / res.a; + res.g = ((g * a * sa) + (over.g * over.a)) / res.a; + res.b = ((b * a * sa) + (over.b * over.a)) / res.a; return res; } /// <summary> /// Returns a new color with all components clamped between the - /// components of `min` and `max` using - /// <see cref="Mathf.Clamp(float, float, float)"/>. + /// components of <paramref name="min"/> and <paramref name="max"/> + /// using <see cref="Mathf.Clamp(float, float, float)"/>. /// </summary> /// <param name="min">The color with minimum allowed values.</param> /// <param name="max">The color with maximum allowed values.</param> @@ -286,14 +291,14 @@ namespace Godot public Color Darkened(float amount) { Color res = this; - res.r = res.r * (1.0f - amount); - res.g = res.g * (1.0f - amount); - res.b = res.b * (1.0f - amount); + res.r *= 1.0f - amount; + res.g *= 1.0f - amount; + res.b *= 1.0f - amount; return res; } /// <summary> - /// Returns the inverted color: `(1 - r, 1 - g, 1 - b, a)`. + /// Returns the inverted color: <c>(1 - r, 1 - g, 1 - b, a)</c>. /// </summary> /// <returns>The inverted color.</returns> public Color Inverted() @@ -315,15 +320,15 @@ namespace Godot public Color Lightened(float amount) { Color res = this; - res.r = res.r + (1.0f - res.r) * amount; - res.g = res.g + (1.0f - res.g) * amount; - res.b = res.b + (1.0f - res.b) * amount; + res.r += (1.0f - res.r) * amount; + res.g += (1.0f - res.g) * amount; + res.b += (1.0f - res.b) * amount; return res; } /// <summary> /// Returns the result of the linear interpolation between - /// this color and `to` by amount `weight`. + /// this color and <paramref name="to"/> by amount <paramref name="weight"/>. /// </summary> /// <param name="to">The destination color for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> @@ -341,7 +346,7 @@ namespace Godot /// <summary> /// Returns the result of the linear interpolation between - /// this color and `to` by color amount `weight`. + /// this color and <paramref name="to"/> by color amount <paramref name="weight"/>. /// </summary> /// <param name="to">The destination color for interpolation.</param> /// <param name="weight">A color with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param> @@ -362,7 +367,7 @@ namespace Godot /// format (each byte represents a color channel). /// ABGR is the reversed version of the default format. /// </summary> - /// <returns>A uint representing this color in ABGR32 format.</returns> + /// <returns>A <see langword="uint"/> representing this color in ABGR32 format.</returns> public uint ToAbgr32() { uint c = (byte)Math.Round(a * 255); @@ -381,7 +386,7 @@ namespace Godot /// format (each word represents a color channel). /// ABGR is the reversed version of the default format. /// </summary> - /// <returns>A ulong representing this color in ABGR64 format.</returns> + /// <returns>A <see langword="ulong"/> representing this color in ABGR64 format.</returns> public ulong ToAbgr64() { ulong c = (ushort)Math.Round(a * 65535); @@ -400,7 +405,7 @@ namespace Godot /// format (each byte represents a color channel). /// ARGB is more compatible with DirectX, but not used much in Godot. /// </summary> - /// <returns>A uint representing this color in ARGB32 format.</returns> + /// <returns>A <see langword="uint"/> representing this color in ARGB32 format.</returns> public uint ToArgb32() { uint c = (byte)Math.Round(a * 255); @@ -419,7 +424,7 @@ namespace Godot /// format (each word represents a color channel). /// ARGB is more compatible with DirectX, but not used much in Godot. /// </summary> - /// <returns>A ulong representing this color in ARGB64 format.</returns> + /// <returns>A <see langword="ulong"/> representing this color in ARGB64 format.</returns> public ulong ToArgb64() { ulong c = (ushort)Math.Round(a * 65535); @@ -438,7 +443,7 @@ namespace Godot /// format (each byte represents a color channel). /// RGBA is Godot's default and recommended format. /// </summary> - /// <returns>A uint representing this color in RGBA32 format.</returns> + /// <returns>A <see langword="uint"/> representing this color in RGBA32 format.</returns> public uint ToRgba32() { uint c = (byte)Math.Round(r * 255); @@ -457,7 +462,7 @@ namespace Godot /// format (each word represents a color channel). /// RGBA is Godot's default and recommended format. /// </summary> - /// <returns>A ulong representing this color in RGBA64 format.</returns> + /// <returns>A <see langword="ulong"/> representing this color in RGBA64 format.</returns> public ulong ToRgba64() { ulong c = (ushort)Math.Round(r * 65535); @@ -474,11 +479,13 @@ namespace Godot /// <summary> /// Returns the color's HTML hexadecimal color string in RGBA format. /// </summary> - /// <param name="includeAlpha">Whether or not to include alpha. If false, the color is RGB instead of RGBA.</param> + /// <param name="includeAlpha"> + /// Whether or not to include alpha. If <see langword="false"/>, the color is RGB instead of RGBA. + /// </param> /// <returns>A string for the HTML hexadecimal representation of this color.</returns> public string ToHTML(bool includeAlpha = true) { - var txt = string.Empty; + string txt = string.Empty; txt += ToHex32(r); txt += ToHex32(g); @@ -493,7 +500,7 @@ namespace Godot } /// <summary> - /// Constructs a color from RGBA values, typically on the range of 0 to 1. + /// Constructs a <see cref="Color"/> from RGBA values, typically on the range of 0 to 1. /// </summary> /// <param name="r">The color's red component, typically on the range of 0 to 1.</param> /// <param name="g">The color's green component, typically on the range of 0 to 1.</param> @@ -508,7 +515,7 @@ namespace Godot } /// <summary> - /// Constructs a color from an existing color and an alpha value. + /// Constructs a <see cref="Color"/> from an existing color and an alpha value. /// </summary> /// <param name="c">The color to construct from. Only its RGB values are used.</param> /// <param name="a">The color's alpha (transparency) value, typically on the range of 0 to 1. Default: 1.</param> @@ -521,10 +528,10 @@ namespace Godot } /// <summary> - /// Constructs a color from an unsigned 32-bit integer in RGBA format + /// Constructs a <see cref="Color"/> from an unsigned 32-bit integer in RGBA format /// (each byte represents a color channel). /// </summary> - /// <param name="rgba">The uint representing the color.</param> + /// <param name="rgba">The <see langword="uint"/> representing the color.</param> public Color(uint rgba) { a = (rgba & 0xFF) / 255.0f; @@ -537,10 +544,10 @@ namespace Godot } /// <summary> - /// Constructs a color from an unsigned 64-bit integer in RGBA format + /// Constructs a <see cref="Color"/> from an unsigned 64-bit integer in RGBA format /// (each word represents a color channel). /// </summary> - /// <param name="rgba">The ulong representing the color.</param> + /// <param name="rgba">The <see langword="ulong"/> representing the color.</param> public Color(ulong rgba) { a = (rgba & 0xFFFF) / 65535.0f; @@ -553,9 +560,9 @@ namespace Godot } /// <summary> - /// Constructs a color either from an HTML color code or from a - /// standardized color name. Supported - /// color names are the same as the <see cref="Colors"/> constants. + /// Constructs a <see cref="Color"/> either from an HTML color code or from a + /// standardized color name. Supported color names are the same as the + /// <see cref="Colors"/> constants. /// </summary> /// <param name="code">The HTML color code or color name to construct from.</param> public Color(string code) @@ -571,8 +578,8 @@ namespace Godot } /// <summary> - /// Constructs a color either from an HTML color code or from a - /// standardized color name, with `alpha` on the range of 0 to 1. Supported + /// Constructs a <see cref="Color"/> either from an HTML color code or from a + /// standardized color name, with <paramref name="alpha"/> on the range of 0 to 1. Supported /// color names are the same as the <see cref="Colors"/> constants. /// </summary> /// <param name="code">The HTML color code or color name to construct from.</param> @@ -584,9 +591,12 @@ namespace Godot } /// <summary> - /// Constructs a color from the HTML hexadecimal color string in RGBA format. + /// Constructs a <see cref="Color"/> from the HTML hexadecimal color string in RGBA format. /// </summary> /// <param name="rgba">A string for the HTML hexadecimal representation of this color.</param> + /// <exception name="ArgumentOutOfRangeException"> + /// Thrown when the given <paramref name="rgba"/> color code is invalid. + /// </exception> private static Color FromHTML(string rgba) { Color c; @@ -627,7 +637,8 @@ namespace Godot } else { - throw new ArgumentOutOfRangeException("Invalid color code. Length is " + rgba.Length + " but a length of 6 or 8 is expected: " + rgba); + throw new ArgumentOutOfRangeException( + $"Invalid color code. Length is {rgba.Length}, but a length of 6 or 8 is expected: {rgba}"); } c.a = 1.0f; @@ -697,11 +708,11 @@ namespace Godot /// <returns>The constructed color.</returns> private static Color Named(string name) { - name = name.Replace(" ", String.Empty); - name = name.Replace("-", String.Empty); - name = name.Replace("_", String.Empty); - name = name.Replace("'", String.Empty); - name = name.Replace(".", String.Empty); + name = name.Replace(" ", string.Empty); + name = name.Replace("-", string.Empty); + name = name.Replace("_", string.Empty); + name = name.Replace("'", string.Empty); + name = name.Replace(".", string.Empty); name = name.ToUpper(); if (!Colors.namedColors.ContainsKey(name)) @@ -715,7 +726,7 @@ namespace Godot /// <summary> /// Constructs a color from an HSV profile, with values on the /// range of 0 to 1. This is equivalent to using each of - /// the `h`/`s`/`v` properties, but much more efficient. + /// the <c>h</c>/<c>s</c>/<c>v</c> properties, but much more efficient. /// </summary> /// <param name="hue">The HSV hue, typically on the range of 0 to 1.</param> /// <param name="saturation">The HSV saturation, typically on the range of 0 to 1.</param> @@ -739,8 +750,8 @@ namespace Godot f = hue - i; p = value * (1 - saturation); - q = value * (1 - saturation * f); - t = value * (1 - saturation * (1 - f)); + q = value * (1 - (saturation * f)); + t = value * (1 - (saturation * (1 - f))); switch (i) { @@ -761,7 +772,7 @@ namespace Godot /// <summary> /// Converts a color to HSV values. This is equivalent to using each of - /// the `h`/`s`/`v` properties, but much more efficient. + /// the <c>h</c>/<c>s</c>/<c>v</c> properties, but much more efficient. /// </summary> /// <param name="hue">Output parameter for the HSV hue.</param> /// <param name="saturation">Output parameter for the HSV saturation.</param> @@ -785,22 +796,24 @@ namespace Godot } else if (g == max) { - hue = 2 + (b - r) / delta; // Between cyan & yellow + hue = 2 + ((b - r) / delta); // Between cyan & yellow } else { - hue = 4 + (r - g) / delta; // Between magenta & cyan + hue = 4 + ((r - g) / delta); // Between magenta & cyan } hue /= 6.0f; if (hue < 0) - { hue += 1.0f; - } } - saturation = max == 0 ? 0 : 1f - 1f * min / max; + if (max == 0) + saturation = 0; + else + saturation = 1 - (min / max); + value = max; } @@ -977,6 +990,11 @@ namespace Godot return left.r > right.r; } + /// <summary> + /// Returns <see langword="true"/> if this color and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the color and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Color) @@ -987,14 +1005,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this color and <paramref name="other"/> are equal + /// </summary> + /// <param name="other">The other color to compare.</param> + /// <returns>Whether or not the colors are equal.</returns> public bool Equals(Color other) { return r == other.r && g == other.g && b == other.b && a == other.a; } /// <summary> - /// Returns true if this color and `other` are approximately equal, by running - /// <see cref="Godot.Mathf.IsEqualApprox(float, float)"/> on each component. + /// Returns <see langword="true"/> if this color and <paramref name="other"/> are approximately equal, + /// by running <see cref="Mathf.IsEqualApprox(float, float)"/> on each component. /// </summary> /// <param name="other">The other color to compare.</param> /// <returns>Whether or not the colors are approximately equal.</returns> @@ -1003,16 +1026,28 @@ namespace Godot return Mathf.IsEqualApprox(r, other.r) && Mathf.IsEqualApprox(g, other.g) && Mathf.IsEqualApprox(b, other.b) && Mathf.IsEqualApprox(a, other.a); } + /// <summary> + /// Serves as the hash function for <see cref="Color"/>. + /// </summary> + /// <returns>A hash code for this color.</returns> public override int GetHashCode() { return r.GetHashCode() ^ g.GetHashCode() ^ b.GetHashCode() ^ a.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Color"/> to a string. + /// </summary> + /// <returns>A string representation of this color.</returns> public override string ToString() { return $"({r}, {g}, {b}, {a})"; } + /// <summary> + /// Converts this <see cref="Color"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this color.</returns> public string ToString(string format) { return $"({r.ToString(format)}, {g.ToString(format)}, {b.ToString(format)}, {a.ToString(format)})"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index 61a34bfc87..2dfe304aaa 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -7,7 +7,7 @@ using System.Diagnostics.CodeAnalysis; namespace Godot.Collections { - class DictionarySafeHandle : SafeHandle + internal class DictionarySafeHandle : SafeHandle { public DictionarySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) { @@ -31,19 +31,17 @@ namespace Godot.Collections /// typed elements allocated in the engine in C++. Useful when /// interfacing with the engine. /// </summary> - public class Dictionary : - IDictionary, - IDisposable + public class Dictionary : IDictionary, IDisposable { - DictionarySafeHandle safeHandle; - bool disposed = false; + private DictionarySafeHandle _safeHandle; + private bool _disposed = false; /// <summary> /// Constructs a new empty <see cref="Dictionary"/>. /// </summary> public Dictionary() { - safeHandle = new DictionarySafeHandle(godot_icall_Dictionary_Ctor()); + _safeHandle = new DictionarySafeHandle(godot_icall_Dictionary_Ctor()); } /// <summary> @@ -62,20 +60,20 @@ namespace Godot.Collections internal Dictionary(DictionarySafeHandle handle) { - safeHandle = handle; + _safeHandle = handle; } internal Dictionary(IntPtr handle) { - safeHandle = new DictionarySafeHandle(handle); + _safeHandle = new DictionarySafeHandle(handle); } internal IntPtr GetPtr() { - if (disposed) + if (_disposed) throw new ObjectDisposedException(GetType().FullName); - return safeHandle.DangerousGetHandle(); + return _safeHandle.DangerousGetHandle(); } /// <summary> @@ -83,16 +81,16 @@ namespace Godot.Collections /// </summary> public void Dispose() { - if (disposed) + if (_disposed) return; - if (safeHandle != null) + if (_safeHandle != null) { - safeHandle.Dispose(); - safeHandle = null; + _safeHandle.Dispose(); + _safeHandle = null; } - disposed = true; + _disposed = true; } /// <summary> @@ -230,17 +228,17 @@ namespace Godot.Collections private class DictionaryEnumerator : IDictionaryEnumerator { - private readonly Dictionary dictionary; - private readonly int count; - private int index = -1; - private bool dirty = true; + private readonly Dictionary _dictionary; + private readonly int _count; + private int _index = -1; + private bool _dirty = true; - private DictionaryEntry entry; + private DictionaryEntry _entry; public DictionaryEnumerator(Dictionary dictionary) { - this.dictionary = dictionary; - count = dictionary.Count; + _dictionary = dictionary; + _count = dictionary.Count; } public object Current => Entry; @@ -249,19 +247,19 @@ namespace Godot.Collections { get { - if (dirty) + if (_dirty) { UpdateEntry(); } - return entry; + return _entry; } } private void UpdateEntry() { - dirty = false; - godot_icall_Dictionary_KeyValuePairAt(dictionary.GetPtr(), index, out object key, out object value); - entry = new DictionaryEntry(key, value); + _dirty = false; + godot_icall_Dictionary_KeyValuePairAt(_dictionary.GetPtr(), _index, out object key, out object value); + _entry = new DictionaryEntry(key, value); } public object Key => Entry.Key; @@ -270,15 +268,15 @@ namespace Godot.Collections public bool MoveNext() { - index++; - dirty = true; - return index < count; + _index++; + _dirty = true; + return _index < _count; } public void Reset() { - index = -1; - dirty = true; + _index = -1; + _dirty = true; } } @@ -292,28 +290,28 @@ namespace Godot.Collections } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Dictionary_Ctor(); + internal static extern IntPtr godot_icall_Dictionary_Ctor(); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Dictionary_Dtor(IntPtr ptr); + internal static extern void godot_icall_Dictionary_Dtor(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_Dictionary_GetValue(IntPtr ptr, object key); + internal static extern object godot_icall_Dictionary_GetValue(IntPtr ptr, object key); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_Dictionary_GetValue_Generic(IntPtr ptr, object key, int valTypeEncoding, IntPtr valTypeClass); + internal static extern object godot_icall_Dictionary_GetValue_Generic(IntPtr ptr, object key, int valTypeEncoding, IntPtr valTypeClass); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Dictionary_SetValue(IntPtr ptr, object key, object value); + internal static extern void godot_icall_Dictionary_SetValue(IntPtr ptr, object key, object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Dictionary_Keys(IntPtr ptr); + internal static extern IntPtr godot_icall_Dictionary_Keys(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Dictionary_Values(IntPtr ptr); + internal static extern IntPtr godot_icall_Dictionary_Values(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_Dictionary_Count(IntPtr ptr); + internal static extern int godot_icall_Dictionary_Count(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] internal extern static int godot_icall_Dictionary_KeyValuePairs(IntPtr ptr, out IntPtr keys, out IntPtr values); @@ -325,34 +323,34 @@ namespace Godot.Collections internal extern static void godot_icall_Dictionary_Add(IntPtr ptr, object key, object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Dictionary_Clear(IntPtr ptr); + internal static extern void godot_icall_Dictionary_Clear(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_Contains(IntPtr ptr, object key, object value); + internal static extern bool godot_icall_Dictionary_Contains(IntPtr ptr, object key, object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_ContainsKey(IntPtr ptr, object key); + internal static extern bool godot_icall_Dictionary_ContainsKey(IntPtr ptr, object key); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Dictionary_Duplicate(IntPtr ptr, bool deep); + internal static extern IntPtr godot_icall_Dictionary_Duplicate(IntPtr ptr, bool deep); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_RemoveKey(IntPtr ptr, object key); + internal static extern bool godot_icall_Dictionary_RemoveKey(IntPtr ptr, object key); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_Remove(IntPtr ptr, object key, object value); + internal static extern bool godot_icall_Dictionary_Remove(IntPtr ptr, object key, object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_TryGetValue(IntPtr ptr, object key, out object value); + internal static extern bool godot_icall_Dictionary_TryGetValue(IntPtr ptr, object key, out object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_TryGetValue_Generic(IntPtr ptr, object key, out object value, int valTypeEncoding, IntPtr valTypeClass); + internal static extern bool godot_icall_Dictionary_TryGetValue_Generic(IntPtr ptr, object key, out object value, int valTypeEncoding, IntPtr valTypeClass); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Dictionary_Generic_GetValueTypeInfo(Type valueType, out int valTypeEncoding, out IntPtr valTypeClass); + internal static extern void godot_icall_Dictionary_Generic_GetValueTypeInfo(Type valueType, out int valTypeEncoding, out IntPtr valTypeClass); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_Dictionary_ToString(IntPtr ptr); + internal static extern string godot_icall_Dictionary_ToString(IntPtr ptr); } /// <summary> @@ -363,10 +361,9 @@ namespace Godot.Collections /// </summary> /// <typeparam name="TKey">The type of the dictionary's keys.</typeparam> /// <typeparam name="TValue">The type of the dictionary's values.</typeparam> - public class Dictionary<TKey, TValue> : - IDictionary<TKey, TValue> + public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue> { - private readonly Dictionary objectDict; + private readonly Dictionary _objectDict; internal static int valTypeEncoding; internal static IntPtr valTypeClass; @@ -381,7 +378,7 @@ namespace Godot.Collections /// </summary> public Dictionary() { - objectDict = new Dictionary(); + _objectDict = new Dictionary(); } /// <summary> @@ -391,7 +388,7 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(IDictionary<TKey, TValue> dictionary) { - objectDict = new Dictionary(); + _objectDict = new Dictionary(); if (dictionary == null) throw new NullReferenceException($"Parameter '{nameof(dictionary)} cannot be null.'"); @@ -413,17 +410,17 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(Dictionary dictionary) { - objectDict = dictionary; + _objectDict = dictionary; } internal Dictionary(IntPtr handle) { - objectDict = new Dictionary(handle); + _objectDict = new Dictionary(handle); } internal Dictionary(DictionarySafeHandle handle) { - objectDict = new Dictionary(handle); + _objectDict = new Dictionary(handle); } /// <summary> @@ -432,12 +429,12 @@ namespace Godot.Collections /// <param name="from">The typed dictionary to convert.</param> public static explicit operator Dictionary(Dictionary<TKey, TValue> from) { - return from.objectDict; + return from._objectDict; } internal IntPtr GetPtr() { - return objectDict.GetPtr(); + return _objectDict.GetPtr(); } /// <summary> @@ -447,7 +444,7 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary<TKey, TValue> Duplicate(bool deep = false) { - return new Dictionary<TKey, TValue>(objectDict.Duplicate(deep)); + return new Dictionary<TKey, TValue>(_objectDict.Duplicate(deep)); } // IDictionary<TKey, TValue> @@ -458,8 +455,8 @@ namespace Godot.Collections /// <value>The value at the given <paramref name="key"/>.</value> public TValue this[TKey key] { - get { return (TValue)Dictionary.godot_icall_Dictionary_GetValue_Generic(objectDict.GetPtr(), key, valTypeEncoding, valTypeClass); } - set { objectDict[key] = value; } + get { return (TValue)Dictionary.godot_icall_Dictionary_GetValue_Generic(_objectDict.GetPtr(), key, valTypeEncoding, valTypeClass); } + set { _objectDict[key] = value; } } /// <summary> @@ -469,7 +466,7 @@ namespace Godot.Collections { get { - IntPtr handle = Dictionary.godot_icall_Dictionary_Keys(objectDict.GetPtr()); + IntPtr handle = Dictionary.godot_icall_Dictionary_Keys(_objectDict.GetPtr()); return new Array<TKey>(new ArraySafeHandle(handle)); } } @@ -481,7 +478,7 @@ namespace Godot.Collections { get { - IntPtr handle = Dictionary.godot_icall_Dictionary_Values(objectDict.GetPtr()); + IntPtr handle = Dictionary.godot_icall_Dictionary_Values(_objectDict.GetPtr()); return new Array<TValue>(new ArraySafeHandle(handle)); } } @@ -500,7 +497,7 @@ namespace Godot.Collections /// <param name="value">The object to add.</param> public void Add(TKey key, TValue value) { - objectDict.Add(key, value); + _objectDict.Add(key, value); } /// <summary> @@ -510,7 +507,7 @@ namespace Godot.Collections /// <returns>Whether or not this dictionary contains the given key.</returns> public bool ContainsKey(TKey key) { - return objectDict.Contains(key); + return _objectDict.Contains(key); } /// <summary> @@ -544,14 +541,14 @@ namespace Godot.Collections /// <returns>The number of elements.</returns> public int Count { - get { return objectDict.Count; } + get { return _objectDict.Count; } } bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false; void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) { - objectDict.Add(item.Key, item.Value); + _objectDict.Add(item.Key, item.Value); } /// <summary> @@ -559,12 +556,12 @@ namespace Godot.Collections /// </summary> public void Clear() { - objectDict.Clear(); + _objectDict.Clear(); } bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { - return objectDict.Contains(new KeyValuePair<object, object>(item.Key, item.Value)); + return _objectDict.Contains(new KeyValuePair<object, object>(item.Key, item.Value)); } /// <summary> @@ -622,6 +619,6 @@ namespace Godot.Collections /// Converts this <see cref="Dictionary{TKey, TValue}"/> to a string. /// </summary> /// <returns>A string representation of this dictionary.</returns> - public override string ToString() => objectDict.ToString(); + public override string ToString() => _objectDict.ToString(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs index 0c21bcaa3f..26d5f9c796 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs @@ -7,20 +7,20 @@ using System.Runtime.CompilerServices; namespace Godot { /// <summary> - /// Represents an <see cref="Godot.Object"/> whose members can be dynamically accessed at runtime through the Variant API. + /// Represents an <see cref="Object"/> whose members can be dynamically accessed at runtime through the Variant API. /// </summary> /// <remarks> /// <para> - /// The <see cref="Godot.DynamicGodotObject"/> class enables access to the Variant - /// members of a <see cref="Godot.Object"/> instance at runtime. + /// The <see cref="DynamicGodotObject"/> class enables access to the Variant + /// members of a <see cref="Object"/> instance at runtime. /// </para> /// <para> /// This allows accessing the class members using their original names in the engine as well as the members from the - /// script attached to the <see cref="Godot.Object"/>, regardless of the scripting language it was written in. + /// script attached to the <see cref="Object"/>, regardless of the scripting language it was written in. /// </para> /// </remarks> /// <example> - /// This sample shows how to use <see cref="Godot.DynamicGodotObject"/> to dynamically access the engine members of a <see cref="Godot.Object"/>. + /// This sample shows how to use <see cref="DynamicGodotObject"/> to dynamically access the engine members of a <see cref="Object"/>. /// <code> /// dynamic sprite = GetNode("Sprite2D").DynamicGodotObject; /// sprite.add_child(this); @@ -30,7 +30,7 @@ namespace Godot /// </code> /// </example> /// <example> - /// This sample shows how to use <see cref="Godot.DynamicGodotObject"/> to dynamically access the members of the script attached to a <see cref="Godot.Object"/>. + /// This sample shows how to use <see cref="DynamicGodotObject"/> to dynamically access the members of the script attached to a <see cref="Object"/>. /// <code> /// dynamic childNode = GetNode("ChildNode").DynamicGodotObject; /// @@ -54,32 +54,34 @@ namespace Godot public class DynamicGodotObject : DynamicObject { /// <summary> - /// Gets the <see cref="Godot.Object"/> associated with this <see cref="Godot.DynamicGodotObject"/>. + /// Gets the <see cref="Object"/> associated with this <see cref="DynamicGodotObject"/>. /// </summary> public Object Value { get; } /// <summary> - /// Initializes a new instance of the <see cref="Godot.DynamicGodotObject"/> class. + /// Initializes a new instance of the <see cref="DynamicGodotObject"/> class. /// </summary> /// <param name="godotObject"> - /// The <see cref="Godot.Object"/> that will be associated with this <see cref="Godot.DynamicGodotObject"/>. + /// The <see cref="Object"/> that will be associated with this <see cref="DynamicGodotObject"/>. /// </param> - /// <exception cref="System.ArgumentNullException"> - /// Thrown when the <paramref name="godotObject"/> parameter is null. + /// <exception cref="ArgumentNullException"> + /// Thrown when the <paramref name="godotObject"/> parameter is <see langword="null"/>. /// </exception> public DynamicGodotObject(Object godotObject) { if (godotObject == null) throw new ArgumentNullException(nameof(godotObject)); - this.Value = godotObject; + Value = godotObject; } + /// <inheritdoc/> public override IEnumerable<string> GetDynamicMemberNames() { return godot_icall_DynamicGodotObject_SetMemberList(Object.GetPtr(Value)); } + /// <inheritdoc/> public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) { switch (binder.Operation) @@ -121,6 +123,7 @@ namespace Godot return base.TryBinaryOperation(binder, arg, out result); } + /// <inheritdoc/> public override bool TryConvert(ConvertBinder binder, out object result) { if (binder.Type == typeof(Object)) @@ -139,6 +142,7 @@ namespace Godot return base.TryConvert(binder, out result); } + /// <inheritdoc/> public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { if (indexes.Length == 1) @@ -152,16 +156,19 @@ namespace Godot return base.TryGetIndex(binder, indexes, out result); } + /// <inheritdoc/> public override bool TryGetMember(GetMemberBinder binder, out object result) { return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), binder.Name, out result); } + /// <inheritdoc/> public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { return godot_icall_DynamicGodotObject_InvokeMember(Object.GetPtr(Value), binder.Name, args, out result); } + /// <inheritdoc/> public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) { if (indexes.Length == 1) @@ -175,22 +182,23 @@ namespace Godot return base.TrySetIndex(binder, indexes, value); } + /// <inheritdoc/> public override bool TrySetMember(SetMemberBinder binder, object value) { return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), binder.Name, value); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string[] godot_icall_DynamicGodotObject_SetMemberList(IntPtr godotObject); + internal static extern string[] godot_icall_DynamicGodotObject_SetMemberList(IntPtr godotObject); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_DynamicGodotObject_InvokeMember(IntPtr godotObject, string name, object[] args, out object result); + internal static extern bool godot_icall_DynamicGodotObject_InvokeMember(IntPtr godotObject, string name, object[] args, out object result); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_DynamicGodotObject_GetMember(IntPtr godotObject, string name, out object result); + internal static extern bool godot_icall_DynamicGodotObject_GetMember(IntPtr godotObject, string name, out object result); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_DynamicGodotObject_SetMember(IntPtr godotObject, string name, object value); + internal static extern bool godot_icall_DynamicGodotObject_SetMember(IntPtr godotObject, string name, object value); #region We don't override these methods diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs index 5d16260f5d..658582960f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs @@ -1,42 +1,190 @@ +using System; + namespace Godot { public partial class Node { + /// <summary> + /// Fetches a node. The <see cref="NodePath"/> can be either a relative path (from + /// the current node) or an absolute path (in the scene tree) to a node. If the path + /// does not exist, a <see langword="null"/> instance is returned and an error + /// is logged. Attempts to access methods on the return value will result in an + /// "Attempt to call <method> on a null instance." error. + /// Note: Fetching absolute paths only works when the node is inside the scene tree + /// (see <see cref="IsInsideTree"/>). + /// </summary> + /// <example> + /// Example: Assume your current node is Character and the following tree: + /// <code> + /// /root + /// /root/Character + /// /root/Character/Sword + /// /root/Character/Backpack/Dagger + /// /root/MyGame + /// /root/Swamp/Alligator + /// /root/Swamp/Mosquito + /// /root/Swamp/Goblin + /// </code> + /// Possible paths are: + /// <code> + /// GetNode("Sword"); + /// GetNode("Backpack/Dagger"); + /// GetNode("../Swamp/Alligator"); + /// GetNode("/root/MyGame"); + /// </code> + /// </example> + /// <seealso cref="GetNodeOrNull{T}(NodePath)"/> + /// <param name="path">The path to the node to fetch.</param> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the fetched node can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The <see cref="Node"/> at the given <paramref name="path"/>. + /// </returns> public T GetNode<T>(NodePath path) where T : class { return (T)(object)GetNode(path); } + /// <summary> + /// Fetches a node. The <see cref="NodePath"/> can be either a relative path (from + /// the current node) or an absolute path (in the scene tree) to a node. If the path + /// does not exist, a <see langword="null"/> instance is returned and an error + /// is logged. Attempts to access methods on the return value will result in an + /// "Attempt to call <method> on a null instance." error. + /// Note: Fetching absolute paths only works when the node is inside the scene tree + /// (see <see cref="IsInsideTree"/>). + /// </summary> + /// <example> + /// Example: Assume your current node is Character and the following tree: + /// <code> + /// /root + /// /root/Character + /// /root/Character/Sword + /// /root/Character/Backpack/Dagger + /// /root/MyGame + /// /root/Swamp/Alligator + /// /root/Swamp/Mosquito + /// /root/Swamp/Goblin + /// </code> + /// Possible paths are: + /// <code> + /// GetNode("Sword"); + /// GetNode("Backpack/Dagger"); + /// GetNode("../Swamp/Alligator"); + /// GetNode("/root/MyGame"); + /// </code> + /// </example> + /// <seealso cref="GetNode{T}(NodePath)"/> + /// <param name="path">The path to the node to fetch.</param> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>/// ///////// <returns> + /// The <see cref="Node"/> at the given <paramref name="path"/>, or <see langword="null"/> if not found. + /// </returns> public T GetNodeOrNull<T>(NodePath path) where T : class { return GetNodeOrNull(path) as T; } + /// <summary> + /// Returns a child node by its index (see <see cref="GetChildCount"/>). + /// This method is often used for iterating all children of a node. + /// Negative indices access the children from the last one. + /// To access a child node via its name, use <see cref="GetNode"/>. + /// </summary> + /// <seealso cref="GetChildOrNull{T}(int)"/> + /// <param name="idx">Child index.</param> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the fetched node can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>/// ///////// <returns> + /// The child <see cref="Node"/> at the given index <paramref name="idx"/>. + /// </returns> public T GetChild<T>(int idx) where T : class { return (T)(object)GetChild(idx); } + /// <summary> + /// Returns a child node by its index (see <see cref="GetChildCount"/>). + /// This method is often used for iterating all children of a node. + /// Negative indices access the children from the last one. + /// To access a child node via its name, use <see cref="GetNode"/>. + /// </summary> + /// <seealso cref="GetChild{T}(int)"/> + /// <param name="idx">Child index.</param> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The child <see cref="Node"/> at the given index <paramref name="idx"/>, or <see langword="null"/> if not found. + /// </returns> public T GetChildOrNull<T>(int idx) where T : class { return GetChild(idx) as T; } + /// <summary> + /// The node owner. A node can have any other node as owner (as long as it is + /// a valid parent, grandparent, etc. ascending in the tree). When saving a + /// node (using <see cref="PackedScene"/>), all the nodes it owns will be saved + /// with it. This allows for the creation of complex <see cref="SceneTree"/>s, + /// with instancing and subinstancing. + /// </summary> + /// <seealso cref="GetOwnerOrNull{T}"/> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the fetched node can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The owner <see cref="Node"/>. + /// </returns> public T GetOwner<T>() where T : class { return (T)(object)Owner; } + /// <summary> + /// The node owner. A node can have any other node as owner (as long as it is + /// a valid parent, grandparent, etc. ascending in the tree). When saving a + /// node (using <see cref="PackedScene"/>), all the nodes it owns will be saved + /// with it. This allows for the creation of complex <see cref="SceneTree"/>s, + /// with instancing and subinstancing. + /// </summary> + /// <seealso cref="GetOwner{T}"/> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The owner <see cref="Node"/>, or <see langword="null"/> if there is no owner. + /// </returns> public T GetOwnerOrNull<T>() where T : class { return Owner as T; } + /// <summary> + /// Returns the parent node of the current node, or a <see langword="null"/> instance + /// if the node lacks a parent. + /// </summary> + /// <seealso cref="GetParentOrNull{T}"/> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the fetched node can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The parent <see cref="Node"/>. + /// </returns> public T GetParent<T>() where T : class { return (T)(object)GetParent(); } + /// <summary> + /// Returns the parent node of the current node, or a <see langword="null"/> instance + /// if the node lacks a parent. + /// </summary> + /// <seealso cref="GetParent{T}"/> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The parent <see cref="Node"/>, or <see langword="null"/> if the node has no parent. + /// </returns> public T GetParentOrNull<T>() where T : class { return GetParent() as T; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs index 9ef0959750..691fd85964 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs @@ -5,17 +5,37 @@ namespace Godot { public partial class Object { + /// <summary> + /// Returns whether <paramref name="instance"/> is a valid object + /// (e.g. has not been deleted from memory). + /// </summary> + /// <param name="instance">The instance to check.</param> + /// <returns>If the instance is a valid object.</returns> public static bool IsInstanceValid(Object instance) { return instance != null && instance.NativeInstance != IntPtr.Zero; } + /// <summary> + /// Returns a weak reference to an object, or <see langword="null"/> + /// if the argument is invalid. + /// A weak reference to an object is not enough to keep the object alive: + /// when the only remaining references to a referent are weak references, + /// garbage collection is free to destroy the referent and reuse its memory + /// for something else. However, until the object is actually destroyed the + /// weak reference may return the object even if there are no strong references + /// to it. + /// </summary> + /// <param name="obj">The object.</param> + /// <returns> + /// The <see cref="WeakRef"/> reference to the object or <see langword="null"/>. + /// </returns> public static WeakRef WeakRef(Object obj) { - return godot_icall_Object_weakref(Object.GetPtr(obj)); + return godot_icall_Object_weakref(GetPtr(obj)); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static WeakRef godot_icall_Object_weakref(IntPtr obj); + internal static extern WeakRef godot_icall_Object_weakref(IntPtr obj); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs index 214bbf5179..435b59d5f3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs @@ -1,3 +1,5 @@ +using System; + namespace Godot { public partial class PackedScene @@ -5,20 +7,27 @@ namespace Godot /// <summary> /// Instantiates the scene's node hierarchy, erroring on failure. /// Triggers child scene instantiation(s). Triggers a - /// `Node.NotificationInstanced` notification on the root node. + /// <see cref="Node.NotificationInstanced"/> notification on the root node. /// </summary> - /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam> + /// <seealso cref="InstantiateOrNull{T}(GenEditState)"/> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the instantiated node can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns>The instantiated scene.</returns> public T Instantiate<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class { return (T)(object)Instantiate(editState); } /// <summary> - /// Instantiates the scene's node hierarchy, returning null on failure. + /// Instantiates the scene's node hierarchy, returning <see langword="null"/> on failure. /// Triggers child scene instantiation(s). Triggers a - /// `Node.NotificationInstanced` notification on the root node. + /// <see cref="Node.NotificationInstanced"/> notification on the root node. /// </summary> - /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam> + /// <seealso cref="Instantiate{T}(GenEditState)"/> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns>The instantiated scene.</returns> public T InstantiateOrNull<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class { return Instantiate(editState) as T; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs index 74fa05d1fd..25c11d5cf6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs @@ -1,10 +1,30 @@ +using System; + namespace Godot { public static partial class ResourceLoader { - public static T Load<T>(string path, string typeHint = null, CacheMode noCache = CacheMode.Reuse) where T : class + /// <summary> + /// Loads a resource at the given <paramref name="path"/>, caching the result + /// for further access. + /// The registered <see cref="ResourceFormatLoader"/> instances are queried sequentially + /// to find the first one which can handle the file's extension, and then attempt + /// loading. If loading fails, the remaining ResourceFormatLoaders are also attempted. + /// An optional <paramref name="typeHint"/> can be used to further specify the + /// <see cref="Resource"/> type that should be handled by the <see cref="ResourceFormatLoader"/>. + /// Anything that inherits from <see cref="Resource"/> can be used as a type hint, + /// for example <see cref="Image"/>. + /// The <paramref name="cacheMode"/> property defines whether and how the cache should + /// be used or updated when loading the resource. See <see cref="CacheMode"/> for details. + /// Returns an empty resource if no <see cref="ResourceFormatLoader"/> could handle the file. + /// </summary> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the loaded resource can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Resource"/>.</typeparam> + public static T Load<T>(string path, string typeHint = null, CacheMode cacheMode = CacheMode.Reuse) where T : class { - return (T)(object)Load(path, typeHint, noCache); + return (T)(object)Load(path, typeHint, cacheMode); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs index 20b11a48dd..df130a5c77 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs @@ -6,12 +6,16 @@ namespace Godot { public partial class SceneTree { + /// <summary> + /// Returns a list of all nodes assigned to the given <paramref name="group"/>. + /// </summary> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> public Array<T> GetNodesInGroup<T>(StringName group) where T : class { - return new Array<T>(godot_icall_SceneTree_get_nodes_in_group_Generic(Object.GetPtr(this), StringName.GetPtr(group), typeof(T))); + return new Array<T>(godot_icall_SceneTree_get_nodes_in_group_Generic(GetPtr(this), StringName.GetPtr(group), typeof(T))); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_SceneTree_get_nodes_in_group_Generic(IntPtr obj, IntPtr group, Type elemType); + internal static extern IntPtr godot_icall_SceneTree_get_nodes_in_group_Generic(IntPtr obj, IntPtr group, Type elemType); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs index 71d0593916..53c02feaa2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs @@ -7,22 +7,56 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -// TODO: Add comments describing what this class does. It is not obvious. - namespace Godot { + /// <summary> + /// Godot's global functions. + /// </summary> public static partial class GD { + /// <summary> + /// Decodes a byte array back to a <c>Variant</c> value. + /// If <paramref name="allowObjects"/> is <see langword="true"/> decoding objects is allowed. + /// + /// WARNING: Deserialized object can contain code which gets executed. + /// Do not set <paramref name="allowObjects"/> to <see langword="true"/> + /// if the serialized object comes from untrusted sources to avoid + /// potential security threats (remote code execution). + /// </summary> + /// <param name="bytes">Byte array that will be decoded to a <c>Variant</c>.</param> + /// <param name="allowObjects">If objects should be decoded.</param> + /// <returns>The decoded <c>Variant</c>.</returns> public static object Bytes2Var(byte[] bytes, bool allowObjects = false) { return godot_icall_GD_bytes2var(bytes, allowObjects); } + /// <summary> + /// Converts from a <c>Variant</c> type to another in the best way possible. + /// The <paramref name="type"/> parameter uses the <see cref="Variant.Type"/> values. + /// </summary> + /// <example> + /// <code> + /// var a = new Vector2(1, 0); + /// // Prints 1 + /// GD.Print(a.Length()); + /// var b = GD.Convert(a, Variant.Type.String) + /// // Prints 6 as "(1, 0)" is 6 characters + /// GD.Print(b.Length); + /// </code> + /// </example> + /// <returns>The <c>Variant</c> converted to the given <paramref name="type"/>.</returns> public static object Convert(object what, Variant.Type type) { return godot_icall_GD_convert(what, type); } + /// <summary> + /// Converts from decibels to linear energy (audio). + /// </summary> + /// <seealso cref="Linear2Db(real_t)"/> + /// <param name="db">Decibels to convert.</param> + /// <returns>Audio volume as linear energy.</returns> public static real_t Db2Linear(real_t db) { return (real_t)Math.Exp(db * 0.11512925464970228420089957273422); @@ -38,111 +72,360 @@ namespace Godot return Array.ConvertAll(parameters, x => x?.ToString() ?? "null"); } + /// <summary> + /// Returns the integer hash of the variable passed. + /// </summary> + /// <example> + /// <code> + /// GD.Print(GD.Hash("a")); // Prints 177670 + /// </code> + /// </example> + /// <param name="var">Variable that will be hashed.</param> + /// <returns>Hash of the variable passed.</returns> public static int Hash(object var) { return godot_icall_GD_hash(var); } + /// <summary> + /// Returns the <see cref="Object"/> that corresponds to <paramref name="instanceId"/>. + /// All Objects have a unique instance ID. + /// </summary> + /// <example> + /// <code> + /// public class MyNode : Node + /// { + /// public string foo = "bar"; + /// + /// public override void _Ready() + /// { + /// ulong id = GetInstanceId(); + /// var inst = (MyNode)GD.InstanceFromId(Id); + /// GD.Print(inst.foo); // Prints bar + /// } + /// } + /// </code> + /// </example> + /// <param name="instanceId">Instance ID of the Object to retrieve.</param> + /// <returns>The <see cref="Object"/> instance.</returns> public static Object InstanceFromId(ulong instanceId) { return godot_icall_GD_instance_from_id(instanceId); } + /// <summary> + /// Converts from linear energy to decibels (audio). + /// This can be used to implement volume sliders that behave as expected (since volume isn't linear). + /// </summary> + /// <seealso cref="Db2Linear(real_t)"/> + /// <example> + /// <code> + /// // "slider" refers to a node that inherits Range such as HSlider or VSlider. + /// // Its range must be configured to go from 0 to 1. + /// // Change the bus name if you'd like to change the volume of a specific bus only. + /// AudioServer.SetBusVolumeDb(AudioServer.GetBusIndex("Master"), GD.Linear2Db(slider.value)); + /// </code> + /// </example> + /// <param name="linear">The linear energy to convert.</param> + /// <returns>Audio as decibels</returns> public static real_t Linear2Db(real_t linear) { return (real_t)(Math.Log(linear) * 8.6858896380650365530225783783321); } + /// <summary> + /// Loads a resource from the filesystem located at <paramref name="path"/>. + /// The resource is loaded on the method call (unless it's referenced already + /// elsewhere, e.g. in another script or in the scene), which might cause slight delay, + /// especially when loading scenes. To avoid unnecessary delays when loading something + /// multiple times, either store the resource in a variable. + /// + /// Note: Resource paths can be obtained by right-clicking on a resource in the FileSystem + /// dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. + /// + /// Important: The path must be absolute, a local path will just return <see langword="null"/>. + /// This method is a simplified version of <see cref="ResourceLoader.Load"/>, which can be used + /// for more advanced scenarios. + /// </summary> + /// <example> + /// <code> + /// // Load a scene called main located in the root of the project directory and cache it in a variable. + /// var main = GD.Load("res://main.tscn"); // main will contain a PackedScene resource. + /// </code> + /// </example> + /// <param name="path">Path of the <see cref="Resource"/> to load.</param> + /// <returns>The loaded <see cref="Resource"/>.</returns> public static Resource Load(string path) { return ResourceLoader.Load(path); } + /// <summary> + /// Loads a resource from the filesystem located at <paramref name="path"/>. + /// The resource is loaded on the method call (unless it's referenced already + /// elsewhere, e.g. in another script or in the scene), which might cause slight delay, + /// especially when loading scenes. To avoid unnecessary delays when loading something + /// multiple times, either store the resource in a variable. + /// + /// Note: Resource paths can be obtained by right-clicking on a resource in the FileSystem + /// dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. + /// + /// Important: The path must be absolute, a local path will just return <see langword="null"/>. + /// This method is a simplified version of <see cref="ResourceLoader.Load"/>, which can be used + /// for more advanced scenarios. + /// </summary> + /// <example> + /// <code> + /// // Load a scene called main located in the root of the project directory and cache it in a variable. + /// var main = GD.Load<PackedScene>("res://main.tscn"); // main will contain a PackedScene resource. + /// </code> + /// </example> + /// <param name="path">Path of the <see cref="Resource"/> to load.</param> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Resource"/>.</typeparam> public static T Load<T>(string path) where T : class { return ResourceLoader.Load<T>(path); } + /// <summary> + /// Pushes an error message to Godot's built-in debugger and to the OS terminal. + /// + /// Note: Errors printed this way will not pause project execution. + /// To print an error message and pause project execution in debug builds, + /// use [code]assert(false, "test error")[/code] instead. + /// </summary> + /// <example> + /// <code> + /// GD.PushError("test_error"); // Prints "test error" to debugger and terminal as error call + /// </code> + /// </example> + /// <param name="message">Error message.</param> public static void PushError(string message) { godot_icall_GD_pusherror(message); } + /// <summary> + /// Pushes a warning message to Godot's built-in debugger and to the OS terminal. + /// </summary> + /// <example> + /// GD.PushWarning("test warning"); // Prints "test warning" to debugger and terminal as warning call + /// </example> + /// <param name="message">Warning message.</param> public static void PushWarning(string message) { godot_icall_GD_pushwarning(message); } + /// <summary> + /// Converts one or more arguments of any type to string in the best way possible + /// and prints them to the console. + /// + /// Note: Consider using <see cref="PushError(string)"/> and <see cref="PushWarning(string)"/> + /// to print error and warning messages instead of <see cref="Print(object[])"/>. + /// This distinguishes them from print messages used for debugging purposes, + /// while also displaying a stack trace when an error or warning is printed. + /// </summary> + /// <example> + /// <code> + /// var a = new int[] { 1, 2, 3 }; + /// GD.Print("a", "b", a); // Prints ab[1, 2, 3] + /// </code> + /// </example> + /// <param name="what">Arguments that will be printed.</param> public static void Print(params object[] what) { godot_icall_GD_print(GetPrintParams(what)); } + /// <summary> + /// Prints the current stack trace information to the console. + /// </summary> public static void PrintStack() { Print(System.Environment.StackTrace); } + /// <summary> + /// Prints one or more arguments to strings in the best way possible to standard error line. + /// </summary> + /// <example> + /// <code> + /// GD.PrintErr("prints to stderr"); + /// </code> + /// </example> + /// <param name="what">Arguments that will be printed.</param> public static void PrintErr(params object[] what) { godot_icall_GD_printerr(GetPrintParams(what)); } + /// <summary> + /// Prints one or more arguments to strings in the best way possible to console. + /// No newline is added at the end. + /// + /// Note: Due to limitations with Godot's built-in console, this only prints to the terminal. + /// If you need to print in the editor, use another method, such as <see cref="Print(object[])"/>. + /// </summary> + /// <example> + /// <code> + /// GD.PrintRaw("A"); + /// GD.PrintRaw("B"); + /// // Prints AB + /// </code> + /// </example> + /// <param name="what">Arguments that will be printed.</param> public static void PrintRaw(params object[] what) { godot_icall_GD_printraw(GetPrintParams(what)); } + /// <summary> + /// Prints one or more arguments to the console with a space between each argument. + /// </summary> + /// <example> + /// <code> + /// GD.PrintS("A", "B", "C"); // Prints A B C + /// </code> + /// </example> + /// <param name="what">Arguments that will be printed.</param> public static void PrintS(params object[] what) { godot_icall_GD_prints(GetPrintParams(what)); } + /// <summary> + /// Prints one or more arguments to the console with a tab between each argument. + /// </summary> + /// <example> + /// <code> + /// GD.PrintT("A", "B", "C"); // Prints A B C + /// </code> + /// </example> + /// <param name="what">Arguments that will be printed.</param> public static void PrintT(params object[] what) { godot_icall_GD_printt(GetPrintParams(what)); } + /// <summary> + /// Returns a random floating point value between <c>0.0</c> and <c>1.0</c> (inclusive). + /// </summary> + /// <example> + /// <code> + /// GD.Randf(); // Returns e.g. 0.375671 + /// </code> + /// </example> + /// <returns>A random <see langword="float"/> number.</returns> public static float Randf() { return godot_icall_GD_randf(); } + /// <summary> + /// Returns a random unsigned 32-bit integer. + /// Use remainder to obtain a random value in the interval <c>[0, N - 1]</c> (where N is smaller than 2^32). + /// </summary> + /// <example> + /// <code> + /// GD.Randi(); // Returns random integer between 0 and 2^32 - 1 + /// GD.Randi() % 20; // Returns random integer between 0 and 19 + /// GD.Randi() % 100; // Returns random integer between 0 and 99 + /// GD.Randi() % 100 + 1; // Returns random integer between 1 and 100 + /// </code> + /// </example> + /// <returns>A random <see langword="uint"/> number.</returns> public static uint Randi() { return godot_icall_GD_randi(); } + /// <summary> + /// Randomizes the seed (or the internal state) of the random number generator. + /// Current implementation reseeds using a number based on time. + /// + /// Note: This method is called automatically when the project is run. + /// If you need to fix the seed to have reproducible results, use <see cref="Seed(ulong)"/> + /// to initialize the random number generator. + /// </summary> public static void Randomize() { godot_icall_GD_randomize(); } + /// <summary> + /// Returns a random floating point value on the interval between <paramref name="from"/> + /// and <paramref name="to"/> (inclusive). + /// </summary> + /// <example> + /// <code> + /// GD.PrintS(GD.RandRange(-10.0, 10.0), GD.RandRange(-10.0, 10.0)); // Prints e.g. -3.844535 7.45315 + /// </code> + /// </example> + /// <returns>A random <see langword="double"/> number inside the given range.</returns> public static double RandRange(double from, double to) { return godot_icall_GD_randf_range(from, to); } + /// <summary> + /// Returns a random signed 32-bit integer between <paramref name="from"/> + /// and <paramref name="to"/> (inclusive). If <paramref name="to"/> is lesser than + /// <paramref name="from"/>, they are swapped. + /// </summary> + /// <example> + /// <code> + /// GD.Print(GD.RandRange(0, 1)); // Prints 0 or 1 + /// GD.Print(GD.RangeRange(-10, 1000)); // Prints any number from -10 to 1000 + /// </code> + /// </example> + /// <returns>A random <see langword="int"/> number inside the given range.</returns> public static int RandRange(int from, int to) { return godot_icall_GD_randi_range(from, to); } + /// <summary> + /// Returns a random unsigned 32-bit integer, using the given <paramref name="seed"/>. + /// </summary> + /// <param name="seed"> + /// Seed to use to generate the random number. + /// If a different seed is used, its value will be modfied. + /// </param> + /// <returns>A random <see langword="uint"/> number.</returns> public static uint RandFromSeed(ref ulong seed) { return godot_icall_GD_rand_seed(seed, out seed); } + /// <summary> + /// Returns a <see cref="IEnumerable{T}"/> that iterates from + /// <c>0</c> to <paramref name="end"/> in steps of <c>1</c>. + /// </summary> + /// <param name="end">The last index.</param> public static IEnumerable<int> Range(int end) { return Range(0, end, 1); } + /// <summary> + /// Returns a <see cref="IEnumerable{T}"/> that iterates from + /// <paramref name="start"/> to <paramref name="end"/> in steps of <c>1</c>. + /// </summary> + /// <param name="start">The first index.</param> + /// <param name="end">The last index.</param> public static IEnumerable<int> Range(int start, int end) { return Range(start, end, 1); } + /// <summary> + /// Returns a <see cref="IEnumerable{T}"/> that iterates from + /// <paramref name="start"/> to <paramref name="end"/> in steps of <paramref name="step"/>. + /// </summary> + /// <param name="start">The first index.</param> + /// <param name="end">The last index.</param> + /// <param name="step">The amount by which to increment the index on each iteration.</param> public static IEnumerable<int> Range(int start, int end, int step) { if (end < start && step > 0) @@ -163,109 +446,164 @@ namespace Godot } } + /// <summary> + /// Sets seed for the random number generator. + /// </summary> + /// <param name="seed">Seed that will be used.</param> public static void Seed(ulong seed) { godot_icall_GD_seed(seed); } + /// <summary> + /// Converts one or more arguments of any type to string in the best way possible. + /// </summary> + /// <param name="what">Arguments that will converted to string.</param> + /// <returns>The string formed by the given arguments.</returns> public static string Str(params object[] what) { return godot_icall_GD_str(what); } + /// <summary> + /// Converts a formatted string that was returned by <see cref="Var2Str(object)"/> to the original value. + /// </summary> + /// <example> + /// <code> + /// string a = "{\"a\": 1, \"b\": 2 }"; + /// var b = (Godot.Collections.Dictionary)GD.Str2Var(a); + /// GD.Print(b["a"]); // Prints 1 + /// </code> + /// </example> + /// <param name="str">String that will be converted to Variant.</param> + /// <returns>The decoded <c>Variant</c>.</returns> public static object Str2Var(string str) { return godot_icall_GD_str2var(str); } + /// <summary> + /// Returns whether the given class exists in <see cref="ClassDB"/>. + /// </summary> + /// <returns>If the class exists in <see cref="ClassDB"/>.</returns> public static bool TypeExists(StringName type) { return godot_icall_GD_type_exists(StringName.GetPtr(type)); } + /// <summary> + /// Encodes a <c>Variant</c> value to a byte array. + /// If <paramref name="fullObjects"/> is <see langword="true"/> encoding objects is allowed + /// (and can potentially include code). + /// Deserialization can be done with <see cref="Bytes2Var(byte[], bool)"/>. + /// </summary> + /// <param name="var">Variant that will be encoded.</param> + /// <param name="fullObjects">If objects should be serialized.</param> + /// <returns>The <c>Variant</c> encoded as an array of bytes.</returns> public static byte[] Var2Bytes(object var, bool fullObjects = false) { return godot_icall_GD_var2bytes(var, fullObjects); } + /// <summary> + /// Converts a <c>Variant</c> <paramref name="var"/> to a formatted string that + /// can later be parsed using <see cref="Str2Var(string)"/>. + /// </summary> + /// <example> + /// <code> + /// var a = new Godot.Collections.Dictionary { ["a"] = 1, ["b"] = 2 }; + /// GD.Print(GD.Var2Str(a)); + /// // Prints + /// // { + /// // "a": 1, + /// // "b": 2 + /// // } + /// </code> + /// </example> + /// <param name="var">Variant that will be converted to string.</param> + /// <returns>The <c>Variant</c> encoded as a string.</returns> public static string Var2Str(object var) { return godot_icall_GD_var2str(var); } + /// <summary> + /// Get the <see cref="Variant.Type"/> that corresponds for the given <see cref="Type"/>. + /// </summary> + /// <returns>The <see cref="Variant.Type"/> for the given <paramref name="type"/>.</returns> public static Variant.Type TypeToVariantType(Type type) { return godot_icall_TypeToVariantType(type); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_GD_bytes2var(byte[] bytes, bool allowObjects); + internal static extern object godot_icall_GD_bytes2var(byte[] bytes, bool allowObjects); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_GD_convert(object what, Variant.Type type); + internal static extern object godot_icall_GD_convert(object what, Variant.Type type); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_GD_hash(object var); + internal static extern int godot_icall_GD_hash(object var); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static Object godot_icall_GD_instance_from_id(ulong instanceId); + internal static extern Object godot_icall_GD_instance_from_id(ulong instanceId); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_print(object[] what); + internal static extern void godot_icall_GD_print(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_printerr(object[] what); + internal static extern void godot_icall_GD_printerr(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_printraw(object[] what); + internal static extern void godot_icall_GD_printraw(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_prints(object[] what); + internal static extern void godot_icall_GD_prints(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_printt(object[] what); + internal static extern void godot_icall_GD_printt(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static float godot_icall_GD_randf(); + internal static extern float godot_icall_GD_randf(); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static uint godot_icall_GD_randi(); + internal static extern uint godot_icall_GD_randi(); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_randomize(); + internal static extern void godot_icall_GD_randomize(); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static double godot_icall_GD_randf_range(double from, double to); + internal static extern double godot_icall_GD_randf_range(double from, double to); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_GD_randi_range(int from, int to); + internal static extern int godot_icall_GD_randi_range(int from, int to); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static uint godot_icall_GD_rand_seed(ulong seed, out ulong newSeed); + internal static extern uint godot_icall_GD_rand_seed(ulong seed, out ulong newSeed); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_seed(ulong seed); + internal static extern void godot_icall_GD_seed(ulong seed); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_GD_str(object[] what); + internal static extern string godot_icall_GD_str(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_GD_str2var(string str); + internal static extern object godot_icall_GD_str2var(string str); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_GD_type_exists(IntPtr type); + internal static extern bool godot_icall_GD_type_exists(IntPtr type); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static byte[] godot_icall_GD_var2bytes(object what, bool fullObjects); + internal static extern byte[] godot_icall_GD_var2bytes(object what, bool fullObjects); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_GD_var2str(object var); + internal static extern string godot_icall_GD_var2str(object var); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_pusherror(string type); + internal static extern void godot_icall_GD_pusherror(string type); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_pushwarning(string type); + internal static extern void godot_icall_GD_pushwarning(string type); [MethodImpl(MethodImplOptions.InternalCall)] private static extern Variant.Type godot_icall_TypeToVariantType(Type type); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs index 4b5e3f8761..c01c926e82 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs @@ -6,7 +6,8 @@ namespace Godot { public class GodotSynchronizationContext : SynchronizationContext { - private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); + private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue = + new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); public override void Post(SendOrPostCallback d, object state) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs index a566b53659..9ccac1faaf 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs @@ -24,7 +24,7 @@ namespace Godot try { - var stackTrace = new StackTrace(true).ToString(); + string stackTrace = new StackTrace(true).ToString(); GD.PrintErr(stackTrace); } catch diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs index f508211f68..3051bcedc7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs @@ -3,60 +3,135 @@ using System.Collections.Generic; namespace Godot { - static class MarshalUtils + internal static class MarshalUtils { /// <summary> /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="Godot.Collections.Array{T}"/>; otherwise returns <see langword="false"/>. + /// is <see cref="Collections.Array{T}"/>; otherwise returns <see langword="false"/>. /// </summary> - /// <exception cref="System.InvalidOperationException"> - /// <paramref name="type"/> is not a generic type. That is, IsGenericType returns false. + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. /// </exception> - static bool TypeIsGenericArray(Type type) => - type.GetGenericTypeDefinition() == typeof(Godot.Collections.Array<>); + private static bool TypeIsGenericArray(Type type) => + type.GetGenericTypeDefinition() == typeof(Collections.Array<>); /// <summary> /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="Godot.Collections.Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. + /// is <see cref="Collections.Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. /// </summary> - /// <exception cref="System.InvalidOperationException"> - /// <paramref name="type"/> is not a generic type. That is, IsGenericType returns false. + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. /// </exception> - static bool TypeIsGenericDictionary(Type type) => - type.GetGenericTypeDefinition() == typeof(Godot.Collections.Dictionary<,>); + private static bool TypeIsGenericDictionary(Type type) => + type.GetGenericTypeDefinition() == typeof(Collections.Dictionary<,>); - static bool TypeIsSystemGenericList(Type type) => - type.GetGenericTypeDefinition() == typeof(System.Collections.Generic.List<>); + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="List{T}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static bool TypeIsSystemGenericList(Type type) => + type.GetGenericTypeDefinition() == typeof(List<>); - static bool TypeIsSystemGenericDictionary(Type type) => - type.GetGenericTypeDefinition() == typeof(System.Collections.Generic.Dictionary<,>); + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static bool TypeIsSystemGenericDictionary(Type type) => + type.GetGenericTypeDefinition() == typeof(Dictionary<,>); - static bool TypeIsGenericIEnumerable(Type type) => type.GetGenericTypeDefinition() == typeof(IEnumerable<>); + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="IEnumerable{T}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static bool TypeIsGenericIEnumerable(Type type) => type.GetGenericTypeDefinition() == typeof(IEnumerable<>); - static bool TypeIsGenericICollection(Type type) => type.GetGenericTypeDefinition() == typeof(ICollection<>); + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="ICollection{T}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static bool TypeIsGenericICollection(Type type) => type.GetGenericTypeDefinition() == typeof(ICollection<>); - static bool TypeIsGenericIDictionary(Type type) => type.GetGenericTypeDefinition() == typeof(IDictionary<,>); + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="IDictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static bool TypeIsGenericIDictionary(Type type) => type.GetGenericTypeDefinition() == typeof(IDictionary<,>); - static void ArrayGetElementType(Type arrayType, out Type elementType) + /// <summary> + /// Gets the element type for the given <paramref name="arrayType"/>. + /// </summary> + /// <param name="arrayType">Type for the generic array.</param> + /// <param name="elementType">Element type for the generic array.</param> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="arrayType"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static void ArrayGetElementType(Type arrayType, out Type elementType) { elementType = arrayType.GetGenericArguments()[0]; } - static void DictionaryGetKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType) + /// <summary> + /// Gets the key type and the value type for the given <paramref name="dictionaryType"/>. + /// </summary> + /// <param name="dictionaryType">The type for the generic dictionary.</param> + /// <param name="keyType">Key type for the generic dictionary.</param> + /// <param name="valueType">Value type for the generic dictionary.</param> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="dictionaryType"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static void DictionaryGetKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType) { var genericArgs = dictionaryType.GetGenericArguments(); keyType = genericArgs[0]; valueType = genericArgs[1]; } - static Type MakeGenericArrayType(Type elemType) + /// <summary> + /// Constructs a new <see cref="Type"/> from <see cref="Collections.Array{T}"/> + /// where the generic type for the elements is <paramref name="elemType"/>. + /// </summary> + /// <param name="elemType">Element type for the array.</param> + /// <returns>The generic array type with the specified element type.</returns> + private static Type MakeGenericArrayType(Type elemType) { - return typeof(Godot.Collections.Array<>).MakeGenericType(elemType); + return typeof(Collections.Array<>).MakeGenericType(elemType); } - static Type MakeGenericDictionaryType(Type keyType, Type valueType) + /// <summary> + /// Constructs a new <see cref="Type"/> from <see cref="Collections.Dictionary{TKey, TValue}"/> + /// where the generic type for the keys is <paramref name="keyType"/> and + /// for the values is <paramref name="valueType"/>. + /// </summary> + /// <param name="keyType">Key type for the dictionary.</param> + /// <param name="valueType">Key type for the dictionary.</param> + /// <returns>The generic dictionary type with the specified key and value types.</returns> + private static Type MakeGenericDictionaryType(Type keyType, Type valueType) { - return typeof(Godot.Collections.Dictionary<,>).MakeGenericType(keyType, valueType); + return typeof(Collections.Dictionary<,>).MakeGenericType(keyType, valueType); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index 213f84ad73..6f7fac7429 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -7,6 +7,9 @@ using System; namespace Godot { + /// <summary> + /// Provides constants and static methods for common mathematical functions. + /// </summary> public static partial class Mathf { // Define constants with Decimal precision and cast down to double or float. @@ -19,121 +22,120 @@ namespace Godot /// <summary> /// Constant that represents how many times the diameter of a circle - /// fits around its perimeter. This is equivalent to `Mathf.Tau / 2`. + /// fits around its perimeter. This is equivalent to <c>Mathf.Tau / 2</c>. /// </summary> // 3.1415927f and 3.14159265358979 public const real_t Pi = (real_t)3.1415926535897932384626433833M; /// <summary> - /// Positive infinity. For negative infinity, use `-Mathf.Inf`. + /// Positive infinity. For negative infinity, use <c>-Mathf.Inf</c>. /// </summary> public const real_t Inf = real_t.PositiveInfinity; /// <summary> - /// "Not a Number", an invalid value. `NaN` has special properties, including + /// "Not a Number", an invalid value. <c>NaN</c> has special properties, including /// that it is not equal to itself. It is output by some invalid operations, /// such as dividing zero by zero. /// </summary> public const real_t NaN = real_t.NaN; // 0.0174532924f and 0.0174532925199433 - private const real_t Deg2RadConst = (real_t)0.0174532925199432957692369077M; + private const real_t _deg2RadConst = (real_t)0.0174532925199432957692369077M; // 57.29578f and 57.2957795130823 - private const real_t Rad2DegConst = (real_t)57.295779513082320876798154814M; + private const real_t _rad2DegConst = (real_t)57.295779513082320876798154814M; /// <summary> - /// Returns the absolute value of `s` (i.e. positive value). + /// Returns the absolute value of <paramref name="s"/> (i.e. positive value). /// </summary> /// <param name="s">The input number.</param> - /// <returns>The absolute value of `s`.</returns> + /// <returns>The absolute value of <paramref name="s"/>.</returns> public static int Abs(int s) { return Math.Abs(s); } /// <summary> - /// Returns the absolute value of `s` (i.e. positive value). + /// Returns the absolute value of <paramref name="s"/> (i.e. positive value). /// </summary> /// <param name="s">The input number.</param> - /// <returns>The absolute value of `s`.</returns> + /// <returns>The absolute value of <paramref name="s"/>.</returns> public static real_t Abs(real_t s) { return Math.Abs(s); } /// <summary> - /// Returns the arc cosine of `s` in radians. Use to get the angle of cosine s. + /// Returns the arc cosine of <paramref name="s"/> in radians. + /// Use to get the angle of cosine <paramref name="s"/>. /// </summary> /// <param name="s">The input cosine value. Must be on the range of -1.0 to 1.0.</param> - /// <returns>An angle that would result in the given cosine value. On the range `0` to `Tau/2`.</returns> + /// <returns> + /// An angle that would result in the given cosine value. On the range <c>0</c> to <c>Tau/2</c>. + /// </returns> public static real_t Acos(real_t s) { return (real_t)Math.Acos(s); } /// <summary> - /// Returns the arc sine of `s` in radians. Use to get the angle of sine s. + /// Returns the arc sine of <paramref name="s"/> in radians. + /// Use to get the angle of sine <paramref name="s"/>. /// </summary> /// <param name="s">The input sine value. Must be on the range of -1.0 to 1.0.</param> - /// <returns>An angle that would result in the given sine value. On the range `-Tau/4` to `Tau/4`.</returns> + /// <returns> + /// An angle that would result in the given sine value. On the range <c>-Tau/4</c> to <c>Tau/4</c>. + /// </returns> public static real_t Asin(real_t s) { return (real_t)Math.Asin(s); } /// <summary> - /// Returns the arc tangent of `s` in radians. Use to get the angle of tangent s. + /// Returns the arc tangent of <paramref name="s"/> in radians. + /// Use to get the angle of tangent <paramref name="s"/>. /// /// The method cannot know in which quadrant the angle should fall. - /// See <see cref="Atan2(real_t, real_t)"/> if you have both `y` and `x`. + /// See <see cref="Atan2(real_t, real_t)"/> if you have both <c>y</c> and <c>x</c>. /// </summary> /// <param name="s">The input tangent value.</param> - /// <returns>An angle that would result in the given tangent value. On the range `-Tau/4` to `Tau/4`.</returns> + /// <returns> + /// An angle that would result in the given tangent value. On the range <c>-Tau/4</c> to <c>Tau/4</c>. + /// </returns> public static real_t Atan(real_t s) { return (real_t)Math.Atan(s); } /// <summary> - /// Returns the arc tangent of `y` and `x` in radians. Use to get the angle - /// of the tangent of `y/x`. To compute the value, the method takes into + /// Returns the arc tangent of <paramref name="y"/> and <paramref name="x"/> in radians. + /// Use to get the angle of the tangent of <c>y/x</c>. To compute the value, the method takes into /// account the sign of both arguments in order to determine the quadrant. /// /// Important note: The Y coordinate comes first, by convention. /// </summary> /// <param name="y">The Y coordinate of the point to find the angle to.</param> /// <param name="x">The X coordinate of the point to find the angle to.</param> - /// <returns>An angle that would result in the given tangent value. On the range `-Tau/2` to `Tau/2`.</returns> + /// <returns> + /// An angle that would result in the given tangent value. On the range <c>-Tau/2</c> to <c>Tau/2</c>. + /// </returns> public static real_t Atan2(real_t y, real_t x) { return (real_t)Math.Atan2(y, x); } /// <summary> - /// Converts a 2D point expressed in the cartesian coordinate - /// system (X and Y axis) to the polar coordinate system - /// (a distance from the origin and an angle). - /// </summary> - /// <param name="x">The input X coordinate.</param> - /// <param name="y">The input Y coordinate.</param> - /// <returns>A <see cref="Vector2"/> with X representing the distance and Y representing the angle.</returns> - public static Vector2 Cartesian2Polar(real_t x, real_t y) - { - return new Vector2(Sqrt(x * x + y * y), Atan2(y, x)); - } - - /// <summary> - /// Rounds `s` upward (towards positive infinity). + /// Rounds <paramref name="s"/> upward (towards positive infinity). /// </summary> /// <param name="s">The number to ceil.</param> - /// <returns>The smallest whole number that is not less than `s`.</returns> + /// <returns>The smallest whole number that is not less than <paramref name="s"/>.</returns> public static real_t Ceil(real_t s) { return (real_t)Math.Ceiling(s); } /// <summary> - /// Clamps a `value` so that it is not less than `min` and not more than `max`. + /// Clamps a <paramref name="value"/> so that it is not less than <paramref name="min"/> + /// and not more than <paramref name="max"/>. /// </summary> /// <param name="value">The value to clamp.</param> /// <param name="min">The minimum allowed value.</param> @@ -145,7 +147,8 @@ namespace Godot } /// <summary> - /// Clamps a `value` so that it is not less than `min` and not more than `max`. + /// Clamps a <paramref name="value"/> so that it is not less than <paramref name="min"/> + /// and not more than <paramref name="max"/>. /// </summary> /// <param name="value">The value to clamp.</param> /// <param name="min">The minimum allowed value.</param> @@ -157,7 +160,7 @@ namespace Godot } /// <summary> - /// Returns the cosine of angle `s` in radians. + /// Returns the cosine of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The cosine of that angle.</returns> @@ -167,7 +170,7 @@ namespace Godot } /// <summary> - /// Returns the hyperbolic cosine of angle `s` in radians. + /// Returns the hyperbolic cosine of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The hyperbolic cosine of that angle.</returns> @@ -183,16 +186,18 @@ namespace Godot /// <returns>The same angle expressed in radians.</returns> public static real_t Deg2Rad(real_t deg) { - return deg * Deg2RadConst; + return deg * _deg2RadConst; } /// <summary> - /// Easing function, based on exponent. The curve values are: - /// `0` is constant, `1` is linear, `0` to `1` is ease-in, `1` or more is ease-out. + /// Easing function, based on exponent. The <paramref name="curve"/> values are: + /// <c>0</c> is constant, <c>1</c> is linear, <c>0</c> to <c>1</c> is ease-in, <c>1</c> or more is ease-out. /// Negative values are in-out/out-in. /// </summary> /// <param name="s">The value to ease.</param> - /// <param name="curve">`0` is constant, `1` is linear, `0` to `1` is ease-in, `1` or more is ease-out.</param> + /// <param name="curve"> + /// <c>0</c> is constant, <c>1</c> is linear, <c>0</c> to <c>1</c> is ease-in, <c>1</c> or more is ease-out. + /// </param> /// <returns>The eased value.</returns> public static real_t Ease(real_t s, real_t curve) { @@ -222,7 +227,7 @@ namespace Godot return Pow(s * 2.0f, -curve) * 0.5f; } - return (1.0f - Pow(1.0f - (s - 0.5f) * 2.0f, -curve)) * 0.5f + 0.5f; + return ((1.0f - Pow(1.0f - ((s - 0.5f) * 2.0f), -curve)) * 0.5f) + 0.5f; } return 0f; @@ -230,20 +235,20 @@ namespace Godot /// <summary> /// The natural exponential function. It raises the mathematical - /// constant `e` to the power of `s` and returns it. + /// constant <c>e</c> to the power of <paramref name="s"/> and returns it. /// </summary> - /// <param name="s">The exponent to raise `e` to.</param> - /// <returns>`e` raised to the power of `s`.</returns> + /// <param name="s">The exponent to raise <c>e</c> to.</param> + /// <returns><c>e</c> raised to the power of <paramref name="s"/>.</returns> public static real_t Exp(real_t s) { return (real_t)Math.Exp(s); } /// <summary> - /// Rounds `s` downward (towards negative infinity). + /// Rounds <paramref name="s"/> downward (towards negative infinity). /// </summary> /// <param name="s">The number to floor.</param> - /// <returns>The largest whole number that is not more than `s`.</returns> + /// <returns>The largest whole number that is not more than <paramref name="s"/>.</returns> public static real_t Floor(real_t s) { return (real_t)Math.Floor(s); @@ -263,12 +268,13 @@ namespace Godot } /// <summary> - /// Returns true if `a` and `b` are approximately equal to each other. + /// Returns <see langword="true"/> if <paramref name="a"/> and <paramref name="b"/> are approximately equal + /// to each other. /// The comparison is done using a tolerance calculation with <see cref="Epsilon"/>. /// </summary> /// <param name="a">One of the values.</param> /// <param name="b">The other value.</param> - /// <returns>A bool for whether or not the two values are approximately equal.</returns> + /// <returns>A <see langword="bool"/> for whether or not the two values are approximately equal.</returns> public static bool IsEqualApprox(real_t a, real_t b) { // Check for exact equality first, required to handle "infinity" values. @@ -286,33 +292,33 @@ namespace Godot } /// <summary> - /// Returns whether `s` is an infinity value (either positive infinity or negative infinity). + /// Returns whether <paramref name="s"/> is an infinity value (either positive infinity or negative infinity). /// </summary> /// <param name="s">The value to check.</param> - /// <returns>A bool for whether or not the value is an infinity value.</returns> + /// <returns>A <see langword="bool"/> for whether or not the value is an infinity value.</returns> public static bool IsInf(real_t s) { return real_t.IsInfinity(s); } /// <summary> - /// Returns whether `s` is a `NaN` ("Not a Number" or invalid) value. + /// Returns whether <paramref name="s"/> is a <c>NaN</c> ("Not a Number" or invalid) value. /// </summary> /// <param name="s">The value to check.</param> - /// <returns>A bool for whether or not the value is a `NaN` value.</returns> + /// <returns>A <see langword="bool"/> for whether or not the value is a <c>NaN</c> value.</returns> public static bool IsNaN(real_t s) { return real_t.IsNaN(s); } /// <summary> - /// Returns true if `s` is approximately zero. + /// Returns <see langword="true"/> if <paramref name="s"/> is approximately zero. /// The comparison is done using a tolerance calculation with <see cref="Epsilon"/>. /// /// This method is faster than using <see cref="IsEqualApprox(real_t, real_t)"/> with one value as zero. /// </summary> /// <param name="s">The value to check.</param> - /// <returns>A bool for whether or not the value is nearly zero.</returns> + /// <returns>A <see langword="bool"/> for whether or not the value is nearly zero.</returns> public static bool IsZeroApprox(real_t s) { return Abs(s) < Epsilon; @@ -328,7 +334,7 @@ namespace Godot /// <returns>The resulting value of the interpolation.</returns> public static real_t Lerp(real_t from, real_t to, real_t weight) { - return from + (to - from) * weight; + return from + ((to - from) * weight); } /// <summary> @@ -345,7 +351,7 @@ namespace Godot { real_t difference = (to - from) % Mathf.Tau; real_t distance = ((2 * difference) % Mathf.Tau) - difference; - return from + distance * weight; + return from + (distance * weight); } /// <summary> @@ -354,7 +360,7 @@ namespace Godot /// Note: This is not the same as the "log" function on most calculators, which uses a base 10 logarithm. /// </summary> /// <param name="s">The input value.</param> - /// <returns>The natural log of `s`.</returns> + /// <returns>The natural log of <paramref name="s"/>.</returns> public static real_t Log(real_t s) { return (real_t)Math.Log(s); @@ -405,9 +411,9 @@ namespace Godot } /// <summary> - /// Moves `from` toward `to` by the `delta` value. + /// Moves <paramref name="from"/> toward <paramref name="to"/> by the <paramref name="delta"/> value. /// - /// Use a negative delta value to move away. + /// Use a negative <paramref name="delta"/> value to move away. /// </summary> /// <param name="from">The start value.</param> /// <param name="to">The value to move towards.</param> @@ -415,11 +421,14 @@ namespace Godot /// <returns>The value after moving.</returns> public static real_t MoveToward(real_t from, real_t to, real_t delta) { - return Abs(to - from) <= delta ? to : from + Sign(to - from) * delta; + if (Abs(to - from) <= delta) + return to; + + return from + (Sign(to - from) * delta); } /// <summary> - /// Returns the nearest larger power of 2 for the integer `value`. + /// Returns the nearest larger power of 2 for the integer <paramref name="value"/>. /// </summary> /// <param name="value">The input value.</param> /// <returns>The nearest larger power of 2.</returns> @@ -436,23 +445,10 @@ namespace Godot } /// <summary> - /// Converts a 2D point expressed in the polar coordinate - /// system (a distance from the origin `r` and an angle `th`) - /// to the cartesian coordinate system (X and Y axis). - /// </summary> - /// <param name="r">The distance from the origin.</param> - /// <param name="th">The angle of the point.</param> - /// <returns>A <see cref="Vector2"/> representing the cartesian coordinate.</returns> - public static Vector2 Polar2Cartesian(real_t r, real_t th) - { - return new Vector2(r * Cos(th), r * Sin(th)); - } - - /// <summary> - /// Performs a canonical Modulus operation, where the output is on the range `[0, b)`. + /// Performs a canonical Modulus operation, where the output is on the range [0, <paramref name="b"/>). /// </summary> /// <param name="a">The dividend, the primary input.</param> - /// <param name="b">The divisor. The output is on the range `[0, b)`.</param> + /// <param name="b">The divisor. The output is on the range [0, <paramref name="b"/>).</param> /// <returns>The resulting output.</returns> public static int PosMod(int a, int b) { @@ -465,10 +461,10 @@ namespace Godot } /// <summary> - /// Performs a canonical Modulus operation, where the output is on the range `[0, b)`. + /// Performs a canonical Modulus operation, where the output is on the range [0, <paramref name="b"/>). /// </summary> /// <param name="a">The dividend, the primary input.</param> - /// <param name="b">The divisor. The output is on the range `[0, b)`.</param> + /// <param name="b">The divisor. The output is on the range [0, <paramref name="b"/>).</param> /// <returns>The resulting output.</returns> public static real_t PosMod(real_t a, real_t b) { @@ -481,11 +477,11 @@ namespace Godot } /// <summary> - /// Returns the result of `x` raised to the power of `y`. + /// Returns the result of <paramref name="x"/> raised to the power of <paramref name="y"/>. /// </summary> /// <param name="x">The base.</param> /// <param name="y">The exponent.</param> - /// <returns>`x` raised to the power of `y`.</returns> + /// <returns><paramref name="x"/> raised to the power of <paramref name="y"/>.</returns> public static real_t Pow(real_t x, real_t y) { return (real_t)Math.Pow(x, y); @@ -498,11 +494,11 @@ namespace Godot /// <returns>The same angle expressed in degrees.</returns> public static real_t Rad2Deg(real_t rad) { - return rad * Rad2DegConst; + return rad * _rad2DegConst; } /// <summary> - /// Rounds `s` to the nearest whole number, + /// Rounds <paramref name="s"/> to the nearest whole number, /// with halfway cases rounded towards the nearest multiple of two. /// </summary> /// <param name="s">The number to round.</param> @@ -513,10 +509,11 @@ namespace Godot } /// <summary> - /// Returns the sign of `s`: `-1` or `1`. Returns `0` if `s` is `0`. + /// Returns the sign of <paramref name="s"/>: <c>-1</c> or <c>1</c>. + /// Returns <c>0</c> if <paramref name="s"/> is <c>0</c>. /// </summary> /// <param name="s">The input number.</param> - /// <returns>One of three possible values: `1`, `-1`, or `0`.</returns> + /// <returns>One of three possible values: <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public static int Sign(int s) { if (s == 0) @@ -525,10 +522,11 @@ namespace Godot } /// <summary> - /// Returns the sign of `s`: `-1` or `1`. Returns `0` if `s` is `0`. + /// Returns the sign of <paramref name="s"/>: <c>-1</c> or <c>1</c>. + /// Returns <c>0</c> if <paramref name="s"/> is <c>0</c>. /// </summary> /// <param name="s">The input number.</param> - /// <returns>One of three possible values: `1`, `-1`, or `0`.</returns> + /// <returns>One of three possible values: <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public static int Sign(real_t s) { if (s == 0) @@ -537,7 +535,7 @@ namespace Godot } /// <summary> - /// Returns the sine of angle `s` in radians. + /// Returns the sine of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The sine of that angle.</returns> @@ -547,7 +545,7 @@ namespace Godot } /// <summary> - /// Returns the hyperbolic sine of angle `s` in radians. + /// Returns the hyperbolic sine of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The hyperbolic sine of that angle.</returns> @@ -557,8 +555,8 @@ namespace Godot } /// <summary> - /// Returns a number smoothly interpolated between `from` and `to`, - /// based on the `weight`. Similar to <see cref="Lerp(real_t, real_t, real_t)"/>, + /// Returns a number smoothly interpolated between <paramref name="from"/> and <paramref name="to"/>, + /// based on the <paramref name="weight"/>. Similar to <see cref="Lerp(real_t, real_t, real_t)"/>, /// but interpolates faster at the beginning and slower at the end. /// </summary> /// <param name="from">The start value for interpolation.</param> @@ -572,16 +570,16 @@ namespace Godot return from; } real_t x = Clamp((weight - from) / (to - from), (real_t)0.0, (real_t)1.0); - return x * x * (3 - 2 * x); + return x * x * (3 - (2 * x)); } /// <summary> - /// Returns the square root of `s`, where `s` is a non-negative number. + /// Returns the square root of <paramref name="s"/>, where <paramref name="s"/> is a non-negative number. /// - /// If you need negative inputs, use `System.Numerics.Complex`. + /// If you need negative inputs, use <see cref="System.Numerics.Complex"/>. /// </summary> /// <param name="s">The input number. Must not be negative.</param> - /// <returns>The square root of `s`.</returns> + /// <returns>The square root of <paramref name="s"/>.</returns> public static real_t Sqrt(real_t s) { return (real_t)Math.Sqrt(s); @@ -596,7 +594,8 @@ namespace Godot /// <returns>The position of the first non-zero digit.</returns> public static int StepDecimals(real_t step) { - double[] sd = new double[] { + double[] sd = new double[] + { 0.9999, 0.09999, 0.009999, @@ -607,7 +606,7 @@ namespace Godot 0.00000009999, 0.000000009999, }; - double abs = Mathf.Abs(step); + double abs = Abs(step); double decs = abs - (int)abs; // Strip away integer part for (int i = 0; i < sd.Length; i++) { @@ -620,9 +619,8 @@ namespace Godot } /// <summary> - /// Snaps float value `s` to a given `step`. - /// This can also be used to round a floating point - /// number to an arbitrary number of decimals. + /// Snaps float value <paramref name="s"/> to a given <paramref name="step"/>. + /// This can also be used to round a floating point number to an arbitrary number of decimals. /// </summary> /// <param name="s">The value to snap.</param> /// <param name="step">The step size to snap to.</param> @@ -631,14 +629,14 @@ namespace Godot { if (step != 0f) { - return Floor(s / step + 0.5f) * step; + return Floor((s / step) + 0.5f) * step; } return s; } /// <summary> - /// Returns the tangent of angle `s` in radians. + /// Returns the tangent of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The tangent of that angle.</returns> @@ -648,7 +646,7 @@ namespace Godot } /// <summary> - /// Returns the hyperbolic tangent of angle `s` in radians. + /// Returns the hyperbolic tangent of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The hyperbolic tangent of that angle.</returns> @@ -658,8 +656,9 @@ namespace Godot } /// <summary> - /// Wraps `value` between `min` and `max`. Usable for creating loop-alike - /// behavior or infinite surfaces. If `min` is `0`, this is equivalent + /// Wraps <paramref name="value"/> between <paramref name="min"/> and <paramref name="max"/>. + /// Usable for creating loop-alike behavior or infinite surfaces. + /// If <paramref name="min"/> is <c>0</c>, this is equivalent /// to <see cref="PosMod(int, int)"/>, so prefer using that instead. /// </summary> /// <param name="value">The value to wrap.</param> @@ -669,12 +668,16 @@ namespace Godot public static int Wrap(int value, int min, int max) { int range = max - min; - return range == 0 ? min : min + ((value - min) % range + range) % range; + if (range == 0) + return min; + + return min + ((((value - min) % range) + range) % range); } /// <summary> - /// Wraps `value` between `min` and `max`. Usable for creating loop-alike - /// behavior or infinite surfaces. If `min` is `0`, this is equivalent + /// Wraps <paramref name="value"/> between <paramref name="min"/> and <paramref name="max"/>. + /// Usable for creating loop-alike behavior or infinite surfaces. + /// If <paramref name="min"/> is <c>0</c>, this is equivalent /// to <see cref="PosMod(real_t, real_t)"/>, so prefer using that instead. /// </summary> /// <param name="value">The value to wrap.</param> @@ -684,7 +687,11 @@ namespace Godot public static real_t Wrap(real_t value, real_t min, real_t max) { real_t range = max - min; - return IsZeroApprox(range) ? min : min + ((value - min) % range + range) % range; + if (IsZeroApprox(range)) + { + return min; + } + return min + ((((value - min) % range) + range) % range); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs index 0888e33090..9bb73ce7dd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs @@ -12,7 +12,7 @@ namespace Godot // Define constants with Decimal precision and cast down to double or float. /// <summary> - /// The natural number `e`. + /// The natural number <c>e</c>. /// </summary> public const real_t E = (real_t)2.7182818284590452353602874714M; // 2.7182817f and 2.718281828459045 @@ -23,7 +23,7 @@ namespace Godot /// <summary> /// A very small number used for float comparison with error tolerance. - /// 1e-06 with single-precision floats, but 1e-14 if `REAL_T_IS_DOUBLE`. + /// 1e-06 with single-precision floats, but 1e-14 if <c>REAL_T_IS_DOUBLE</c>. /// </summary> #if REAL_T_IS_DOUBLE public const real_t Epsilon = 1e-14; // Epsilon size should depend on the precision used. @@ -44,7 +44,7 @@ namespace Godot /// <summary> /// Returns the amount of digits after the decimal place. /// </summary> - /// <param name="s">The input <see cref="System.Decimal"/> value.</param> + /// <param name="s">The input <see cref="decimal"/> value.</param> /// <returns>The amount of digits.</returns> public static int DecimalCount(decimal s) { @@ -52,48 +52,51 @@ namespace Godot } /// <summary> - /// Rounds `s` upward (towards positive infinity). + /// Rounds <paramref name="s"/> upward (towards positive infinity). /// - /// This is the same as <see cref="Ceil(real_t)"/>, but returns an `int`. + /// This is the same as <see cref="Ceil(real_t)"/>, but returns an <c>int</c>. /// </summary> /// <param name="s">The number to ceil.</param> - /// <returns>The smallest whole number that is not less than `s`.</returns> + /// <returns>The smallest whole number that is not less than <paramref name="s"/>.</returns> public static int CeilToInt(real_t s) { return (int)Math.Ceiling(s); } /// <summary> - /// Rounds `s` downward (towards negative infinity). + /// Rounds <paramref name="s"/> downward (towards negative infinity). /// - /// This is the same as <see cref="Floor(real_t)"/>, but returns an `int`. + /// This is the same as <see cref="Floor(real_t)"/>, but returns an <c>int</c>. /// </summary> /// <param name="s">The number to floor.</param> - /// <returns>The largest whole number that is not more than `s`.</returns> + /// <returns>The largest whole number that is not more than <paramref name="s"/>.</returns> public static int FloorToInt(real_t s) { return (int)Math.Floor(s); } /// <summary> + /// Rounds <paramref name="s"/> to the nearest whole number. /// + /// This is the same as <see cref="Round(real_t)"/>, but returns an <c>int</c>. /// </summary> - /// <param name="s"></param> - /// <returns></returns> + /// <param name="s">The number to round.</param> + /// <returns>The rounded number.</returns> public static int RoundToInt(real_t s) { return (int)Math.Round(s); } /// <summary> - /// Returns true if `a` and `b` are approximately equal to each other. + /// Returns <see langword="true"/> if <paramref name="a"/> and <paramref name="b"/> are approximately + /// equal to each other. /// The comparison is done using the provided tolerance value. /// If you want the tolerance to be calculated for you, use <see cref="IsEqualApprox(real_t, real_t)"/>. /// </summary> /// <param name="a">One of the values.</param> /// <param name="b">The other value.</param> /// <param name="tolerance">The pre-calculated tolerance value.</param> - /// <returns>A bool for whether or not the two values are equal.</returns> + /// <returns>A <see langword="bool"/> for whether or not the two values are equal.</returns> public static bool IsEqualApprox(real_t a, real_t b, real_t tolerance) { // Check for exact equality first, required to handle "infinity" values. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs index 4ecc55f94e..f53b5dc904 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs @@ -3,9 +3,45 @@ using System.Runtime.CompilerServices; namespace Godot { + /// <summary> + /// A pre-parsed relative or absolute path in a scene tree, + /// for use with <see cref="Node.GetNode(NodePath)"/> and similar functions. + /// It can reference a node, a resource within a node, or a property + /// of a node or resource. + /// For instance, <c>"Path2D/PathFollow2D/Sprite2D:texture:size"</c> + /// would refer to the <c>size</c> property of the <c>texture</c> + /// resource on the node named <c>"Sprite2D"</c> which is a child of + /// the other named nodes in the path. + /// You will usually just pass a string to <see cref="Node.GetNode(NodePath)"/> + /// and it will be automatically converted, but you may occasionally + /// want to parse a path ahead of time with NodePath. + /// Exporting a NodePath variable will give you a node selection widget + /// in the properties panel of the editor, which can often be useful. + /// A NodePath is composed of a list of slash-separated node names + /// (like a filesystem path) and an optional colon-separated list of + /// "subnames" which can be resources or properties. + /// + /// Note: In the editor, NodePath properties are automatically updated when moving, + /// renaming or deleting a node in the scene tree, but they are never updated at runtime. + /// </summary> + /// <example> + /// Some examples of NodePaths include the following: + /// <code> + /// // No leading slash means it is relative to the current node. + /// new NodePath("A"); // Immediate child A. + /// new NodePath("A/B"); // A's child B. + /// new NodePath("."); // The current node. + /// new NodePath(".."); // The parent node. + /// new NodePath("../C"); // A sibling node C. + /// // A leading slash means it is absolute from the SceneTree. + /// new NodePath("/root"); // Equivalent to GetTree().Root + /// new NodePath("/root/Main"); // If your main scene's root node were named "Main". + /// new NodePath("/root/MyAutoload"); // If you have an autoloaded node or scene. + /// </code> + /// </example> public sealed partial class NodePath : IDisposable { - private bool disposed = false; + private bool _disposed = false; private IntPtr ptr; @@ -14,7 +50,7 @@ namespace Godot if (instance == null) throw new NullReferenceException($"The instance of type {nameof(NodePath)} is null."); - if (instance.disposed) + if (instance._disposed) throw new ObjectDisposedException(instance.GetType().FullName); return instance.ptr; @@ -25,6 +61,9 @@ namespace Godot Dispose(false); } + /// <summary> + /// Disposes of this <see cref="NodePath"/>. + /// </summary> public void Dispose() { Dispose(true); @@ -33,7 +72,7 @@ namespace Godot private void Dispose(bool disposing) { - if (disposed) + if (_disposed) return; if (ptr != IntPtr.Zero) @@ -42,7 +81,7 @@ namespace Godot ptr = IntPtr.Zero; } - disposed = true; + _disposed = true; } internal NodePath(IntPtr ptr) @@ -50,57 +89,168 @@ namespace Godot this.ptr = ptr; } - public NodePath() : this(string.Empty) {} - + /// <summary> + /// Constructs an empty <see cref="NodePath"/>. + /// </summary> + public NodePath() : this(string.Empty) { } + + /// <summary> + /// Constructs a <see cref="NodePath"/> from a string <paramref name="path"/>, + /// e.g.: <c>"Path2D/PathFollow2D/Sprite2D:texture:size"</c>. + /// A path is absolute if it starts with a slash. Absolute paths + /// are only valid in the global scene tree, not within individual + /// scenes. In a relative path, <c>"."</c> and <c>".."</c> indicate + /// the current node and its parent. + /// The "subnames" optionally included after the path to the target + /// node can point to resources or properties, and can also be nested. + /// </summary> + /// <example> + /// Examples of valid NodePaths (assuming that those nodes exist and + /// have the referenced resources or properties): + /// <code> + /// // Points to the Sprite2D node. + /// "Path2D/PathFollow2D/Sprite2D" + /// // Points to the Sprite2D node and its "texture" resource. + /// // GetNode() would retrieve "Sprite2D", while GetNodeAndResource() + /// // would retrieve both the Sprite2D node and the "texture" resource. + /// "Path2D/PathFollow2D/Sprite2D:texture" + /// // Points to the Sprite2D node and its "position" property. + /// "Path2D/PathFollow2D/Sprite2D:position" + /// // Points to the Sprite2D node and the "x" component of its "position" property. + /// "Path2D/PathFollow2D/Sprite2D:position:x" + /// // Absolute path (from "root") + /// "/root/Level/Path2D" + /// </code> + /// </example> + /// <param name="path"></param> public NodePath(string path) { ptr = godot_icall_NodePath_Ctor(path); } + /// <summary> + /// Converts a string to a <see cref="NodePath"/>. + /// </summary> + /// <param name="from">The string to convert.</param> public static implicit operator NodePath(string from) => new NodePath(from); + /// <summary> + /// Converts this <see cref="NodePath"/> to a string. + /// </summary> + /// <param name="from">The <see cref="NodePath"/> to convert.</param> public static implicit operator string(NodePath from) => from.ToString(); + /// <summary> + /// Converts this <see cref="NodePath"/> to a string. + /// </summary> + /// <returns>A string representation of this <see cref="NodePath"/>.</returns> public override string ToString() { return godot_icall_NodePath_operator_String(GetPtr(this)); } + /// <summary> + /// Returns a node path with a colon character (<c>:</c>) prepended, + /// transforming it to a pure property path with no node name (defaults + /// to resolving from the current node). + /// </summary> + /// <example> + /// <code> + /// // This will be parsed as a node path to the "x" property in the "position" node. + /// var nodePath = new NodePath("position:x"); + /// // This will be parsed as a node path to the "x" component of the "position" property in the current node. + /// NodePath propertyPath = nodePath.GetAsPropertyPath(); + /// GD.Print(propertyPath); // :position:x + /// </code> + /// </example> + /// <returns>The <see cref="NodePath"/> as a pure property path.</returns> public NodePath GetAsPropertyPath() { return new NodePath(godot_icall_NodePath_get_as_property_path(GetPtr(this))); } + /// <summary> + /// Returns all subnames concatenated with a colon character (<c>:</c>) + /// as separator, i.e. the right side of the first colon in a node path. + /// </summary> + /// <example> + /// <code> + /// var nodepath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path"); + /// GD.Print(nodepath.GetConcatenatedSubnames()); // texture:load_path + /// </code> + /// </example> + /// <returns>The subnames concatenated with <c>:</c>.</returns> public string GetConcatenatedSubnames() { return godot_icall_NodePath_get_concatenated_subnames(GetPtr(this)); } + /// <summary> + /// Gets the node name indicated by <paramref name="idx"/> (0 to <see cref="GetNameCount"/>). + /// </summary> + /// <example> + /// <code> + /// var nodePath = new NodePath("Path2D/PathFollow2D/Sprite2D"); + /// GD.Print(nodePath.GetName(0)); // Path2D + /// GD.Print(nodePath.GetName(1)); // PathFollow2D + /// GD.Print(nodePath.GetName(2)); // Sprite + /// </code> + /// </example> + /// <param name="idx">The name index.</param> + /// <returns>The name at the given index <paramref name="idx"/>.</returns> public string GetName(int idx) { return godot_icall_NodePath_get_name(GetPtr(this), idx); } + /// <summary> + /// Gets the number of node names which make up the path. + /// Subnames (see <see cref="GetSubnameCount"/>) are not included. + /// For example, <c>"Path2D/PathFollow2D/Sprite2D"</c> has 3 names. + /// </summary> + /// <returns>The number of node names which make up the path.</returns> public int GetNameCount() { return godot_icall_NodePath_get_name_count(GetPtr(this)); } + /// <summary> + /// Gets the resource or property name indicated by <paramref name="idx"/> (0 to <see cref="GetSubnameCount"/>). + /// </summary> + /// <param name="idx">The subname index.</param> + /// <returns>The subname at the given index <paramref name="idx"/>.</returns> public string GetSubname(int idx) { return godot_icall_NodePath_get_subname(GetPtr(this), idx); } + /// <summary> + /// Gets the number of resource or property names ("subnames") in the path. + /// Each subname is listed after a colon character (<c>:</c>) in the node path. + /// For example, <c>"Path2D/PathFollow2D/Sprite2D:texture:load_path"</c> has 2 subnames. + /// </summary> + /// <returns>The number of subnames in the path.</returns> public int GetSubnameCount() { return godot_icall_NodePath_get_subname_count(GetPtr(this)); } + /// <summary> + /// Returns <see langword="true"/> if the node path is absolute (as opposed to relative), + /// which means that it starts with a slash character (<c>/</c>). Absolute node paths can + /// be used to access the root node (<c>"/root"</c>) or autoloads (e.g. <c>"/global"</c> + /// if a "global" autoload was registered). + /// </summary> + /// <returns>If the <see cref="NodePath"/> is an absolute path.</returns> public bool IsAbsolute() { return godot_icall_NodePath_is_absolute(GetPtr(this)); } + /// <summary> + /// Returns <see langword="true"/> if the node path is empty. + /// </summary> + /// <returns>If the <see cref="NodePath"/> is empty.</returns> public bool IsEmpty() { return godot_icall_NodePath_is_empty(GetPtr(this)); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs index d486d79557..8fe08e7e1d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs @@ -5,13 +5,16 @@ namespace Godot { public partial class Object : IDisposable { - private bool disposed = false; + private bool _disposed = false; private static StringName nativeName = "Object"; internal IntPtr ptr; internal bool memoryOwn; + /// <summary> + /// Constructs a new <see cref="Object"/>. + /// </summary> public Object() : this(false) { if (ptr == IntPtr.Zero) @@ -29,6 +32,9 @@ namespace Godot this.memoryOwn = memoryOwn; } + /// <summary> + /// The pointer to the native instance of this <see cref="Object"/>. + /// </summary> public IntPtr NativeInstance { get { return ptr; } @@ -39,7 +45,7 @@ namespace Godot if (instance == null) return IntPtr.Zero; - if (instance.disposed) + if (instance._disposed) throw new ObjectDisposedException(instance.GetType().FullName); return instance.ptr; @@ -50,15 +56,21 @@ namespace Godot Dispose(false); } + /// <summary> + /// Disposes of this <see cref="Object"/>. + /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } + /// <summary> + /// Disposes implementation of this <see cref="Object"/>. + /// </summary> protected virtual void Dispose(bool disposing) { - if (disposed) + if (_disposed) return; if (ptr != IntPtr.Zero) @@ -73,19 +85,23 @@ namespace Godot godot_icall_Object_Disposed(this, ptr); } - this.ptr = IntPtr.Zero; + ptr = IntPtr.Zero; } - disposed = true; + _disposed = true; } + /// <summary> + /// Converts this <see cref="Object"/> to a string. + /// </summary> + /// <returns>A string representation of this object.</returns> public override string ToString() { return godot_icall_Object_ToString(GetPtr(this)); } /// <summary> - /// Returns a new <see cref="Godot.SignalAwaiter"/> awaiter configured to complete when the instance + /// Returns a new <see cref="SignalAwaiter"/> awaiter configured to complete when the instance /// <paramref name="source"/> emits the signal specified by the <paramref name="signal"/> parameter. /// </summary> /// <param name="source"> @@ -107,13 +123,17 @@ namespace Godot /// } /// </code> /// </example> + /// <returns> + /// A <see cref="SignalAwaiter"/> that completes when + /// <paramref name="source"/> emits the <paramref name="signal"/>. + /// </returns> public SignalAwaiter ToSignal(Object source, StringName signal) { return new SignalAwaiter(source, signal, this); } /// <summary> - /// Gets a new <see cref="Godot.DynamicGodotObject"/> associated with this instance. + /// Gets a new <see cref="DynamicGodotObject"/> associated with this instance. /// </summary> public dynamic DynamicObject => new DynamicGodotObject(this); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs index 6972102730..66f7b745f7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs @@ -21,10 +21,10 @@ namespace Godot /// <summary> /// The normal of the plane, which must be normalized. - /// In the scalar equation of the plane `ax + by + cz = d`, this is - /// the vector `(a, b, c)`, where `d` is the <see cref="D"/> property. + /// In the scalar equation of the plane <c>ax + by + cz = d</c>, this is + /// the vector <c>(a, b, c)</c>, where <c>d</c> is the <see cref="D"/> property. /// </summary> - /// <value>Equivalent to `x`, `y`, and `z`.</value> + /// <value>Equivalent to <see cref="x"/>, <see cref="y"/>, and <see cref="z"/>.</value> public Vector3 Normal { get { return _normal; } @@ -82,8 +82,8 @@ namespace Godot /// <summary> /// The distance from the origin to the plane (in the direction of /// <see cref="Normal"/>). This value is typically non-negative. - /// In the scalar equation of the plane `ax + by + cz = d`, - /// this is `d`, while the `(a, b, c)` coordinates are represented + /// In the scalar equation of the plane <c>ax + by + cz = d</c>, + /// this is <c>d</c>, while the <c>(a, b, c)</c> coordinates are represented /// by the <see cref="Normal"/> property. /// </summary> /// <value>The plane's distance from the origin.</value> @@ -92,7 +92,7 @@ namespace Godot /// <summary> /// The center of the plane, the point where the normal line intersects the plane. /// </summary> - /// <value>Equivalent to <see cref="Normal"/> multiplied by `D`.</value> + /// <value>Equivalent to <see cref="Normal"/> multiplied by <see cref="D"/>.</value> public Vector3 Center { get @@ -107,7 +107,7 @@ namespace Godot } /// <summary> - /// Returns the shortest distance from this plane to the position `point`. + /// Returns the shortest distance from this plane to the position <paramref name="point"/>. /// </summary> /// <param name="point">The position to use for the calculation.</param> /// <returns>The shortest distance.</returns> @@ -117,12 +117,12 @@ namespace Godot } /// <summary> - /// Returns true if point is inside the plane. + /// Returns <see langword="true"/> if point is inside the plane. /// Comparison uses a custom minimum epsilon threshold. /// </summary> /// <param name="point">The point to check.</param> /// <param name="epsilon">The tolerance threshold.</param> - /// <returns>A bool for whether or not the plane has the point.</returns> + /// <returns>A <see langword="bool"/> for whether or not the plane has the point.</returns> public bool HasPoint(Vector3 point, real_t epsilon = Mathf.Epsilon) { real_t dist = _normal.Dot(point) - D; @@ -130,12 +130,12 @@ namespace Godot } /// <summary> - /// Returns the intersection point of the three planes: `b`, `c`, - /// and this plane. If no intersection is found, `null` is returned. + /// Returns the intersection point of the three planes: <paramref name="b"/>, <paramref name="c"/>, + /// and this plane. If no intersection is found, <see langword="null"/> is returned. /// </summary> /// <param name="b">One of the three planes to use in the calculation.</param> /// <param name="c">One of the three planes to use in the calculation.</param> - /// <returns>The intersection, or `null` if none is found.</returns> + /// <returns>The intersection, or <see langword="null"/> if none is found.</returns> public Vector3? Intersect3(Plane b, Plane c) { real_t denom = _normal.Cross(b._normal).Dot(c._normal); @@ -145,21 +145,21 @@ namespace Godot return null; } - Vector3 result = b._normal.Cross(c._normal) * D + - c._normal.Cross(_normal) * b.D + - _normal.Cross(b._normal) * c.D; + Vector3 result = (b._normal.Cross(c._normal) * D) + + (c._normal.Cross(_normal) * b.D) + + (_normal.Cross(b._normal) * c.D); return result / denom; } /// <summary> - /// Returns the intersection point of a ray consisting of the - /// position `from` and the direction normal `dir` with this plane. - /// If no intersection is found, `null` is returned. + /// Returns the intersection point of a ray consisting of the position <paramref name="from"/> + /// and the direction normal <paramref name="dir"/> with this plane. + /// If no intersection is found, <see langword="null"/> is returned. /// </summary> /// <param name="from">The start of the ray.</param> /// <param name="dir">The direction of the ray, normalized.</param> - /// <returns>The intersection, or `null` if none is found.</returns> + /// <returns>The intersection, or <see langword="null"/> if none is found.</returns> public Vector3? IntersectRay(Vector3 from, Vector3 dir) { real_t den = _normal.Dot(dir); @@ -182,12 +182,12 @@ namespace Godot /// <summary> /// Returns the intersection point of a line segment from - /// position `begin` to position `end` with this plane. - /// If no intersection is found, `null` is returned. + /// position <paramref name="begin"/> to position <paramref name="end"/> with this plane. + /// If no intersection is found, <see langword="null"/> is returned. /// </summary> /// <param name="begin">The start of the line segment.</param> /// <param name="end">The end of the line segment.</param> - /// <returns>The intersection, or `null` if none is found.</returns> + /// <returns>The intersection, or <see langword="null"/> if none is found.</returns> public Vector3? IntersectSegment(Vector3 begin, Vector3 end) { Vector3 segment = begin - end; @@ -210,10 +210,10 @@ namespace Godot } /// <summary> - /// Returns true if `point` is located above the plane. + /// Returns <see langword="true"/> if <paramref name="point"/> is located above the plane. /// </summary> /// <param name="point">The point to check.</param> - /// <returns>A bool for whether or not the point is above the plane.</returns> + /// <returns>A <see langword="bool"/> for whether or not the point is above the plane.</returns> public bool IsPointOver(Vector3 point) { return _normal.Dot(point) > D; @@ -236,13 +236,13 @@ namespace Godot } /// <summary> - /// Returns the orthogonal projection of `point` into the plane. + /// Returns the orthogonal projection of <paramref name="point"/> into the plane. /// </summary> /// <param name="point">The point to project.</param> /// <returns>The projected point.</returns> public Vector3 Project(Vector3 point) { - return point - _normal * DistanceTo(point); + return point - (_normal * DistanceTo(point)); } // Constants @@ -251,27 +251,28 @@ namespace Godot private static readonly Plane _planeXY = new Plane(0, 0, 1, 0); /// <summary> - /// A plane that extends in the Y and Z axes (normal vector points +X). + /// A <see cref="Plane"/> that extends in the Y and Z axes (normal vector points +X). /// </summary> - /// <value>Equivalent to `new Plane(1, 0, 0, 0)`.</value> + /// <value>Equivalent to <c>new Plane(1, 0, 0, 0)</c>.</value> public static Plane PlaneYZ { get { return _planeYZ; } } /// <summary> - /// A plane that extends in the X and Z axes (normal vector points +Y). + /// A <see cref="Plane"/> that extends in the X and Z axes (normal vector points +Y). /// </summary> - /// <value>Equivalent to `new Plane(0, 1, 0, 0)`.</value> + /// <value>Equivalent to <c>new Plane(0, 1, 0, 0)</c>.</value> public static Plane PlaneXZ { get { return _planeXZ; } } /// <summary> - /// A plane that extends in the X and Y axes (normal vector points +Z). + /// A <see cref="Plane"/> that extends in the X and Y axes (normal vector points +Z). /// </summary> - /// <value>Equivalent to `new Plane(0, 0, 1, 0)`.</value> + /// <value>Equivalent to <c>new Plane(0, 0, 1, 0)</c>.</value> public static Plane PlaneXY { get { return _planeXY; } } /// <summary> - /// Constructs a plane from four values. `a`, `b` and `c` become the + /// Constructs a <see cref="Plane"/> from four values. + /// <paramref name="a"/>, <paramref name="b"/> and <paramref name="c"/> become the /// components of the resulting plane's <see cref="Normal"/> vector. - /// `d` becomes the plane's distance from the origin. + /// <paramref name="d"/> becomes the plane's distance from the origin. /// </summary> /// <param name="a">The X component of the plane's normal vector.</param> /// <param name="b">The Y component of the plane's normal vector.</param> @@ -280,22 +281,23 @@ namespace Godot public Plane(real_t a, real_t b, real_t c, real_t d) { _normal = new Vector3(a, b, c); - this.D = d; + D = d; } /// <summary> - /// Constructs a plane from a normal vector and the plane's distance to the origin. + /// Constructs a <see cref="Plane"/> from a <paramref name="normal"/> vector and + /// the plane's distance to the origin <paramref name="d"/>. /// </summary> /// <param name="normal">The normal of the plane, must be normalized.</param> /// <param name="d">The plane's distance from the origin. This value is typically non-negative.</param> public Plane(Vector3 normal, real_t d) { - this._normal = normal; - this.D = d; + _normal = normal; + D = d; } /// <summary> - /// Constructs a plane from the three points, given in clockwise order. + /// Constructs a <see cref="Plane"/> from the three points, given in clockwise order. /// </summary> /// <param name="v1">The first point.</param> /// <param name="v2">The second point.</param> @@ -322,6 +324,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this plane and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the plane and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Plane) @@ -332,14 +339,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this plane and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other plane to compare.</param> + /// <returns>Whether or not the planes are equal.</returns> public bool Equals(Plane other) { return _normal == other._normal && D == other.D; } /// <summary> - /// Returns true if this plane and `other` are approximately equal, by running - /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. + /// Returns <see langword="true"/> if this plane and <paramref name="other"/> are + /// approximately equal, by running <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. /// </summary> /// <param name="other">The other plane to compare.</param> /// <returns>Whether or not the planes are approximately equal.</returns> @@ -348,16 +360,28 @@ namespace Godot return _normal.IsEqualApprox(other._normal) && Mathf.IsEqualApprox(D, other.D); } + /// <summary> + /// Serves as the hash function for <see cref="Plane"/>. + /// </summary> + /// <returns>A hash code for this plane.</returns> public override int GetHashCode() { return _normal.GetHashCode() ^ D.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Plane"/> to a string. + /// </summary> + /// <returns>A string representation of this plane.</returns> public override string ToString() { return $"{_normal}, {D}"; } + /// <summary> + /// Converts this <see cref="Plane"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this plane.</returns> public string ToString(string format) { return $"{_normal.ToString(format)}, {D.ToString(format)}"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index 0fed55cc30..1694ac0320 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -12,10 +12,10 @@ namespace Godot /// A unit quaternion used for representing 3D rotations. /// Quaternions need to be normalized to be used for rotation. /// - /// It is similar to Basis, which implements matrix representation of - /// rotations, and can be parametrized using both an axis-angle pair - /// or Euler angles. Basis stores rotation, scale, and shearing, - /// while Quaternion only stores rotation. + /// It is similar to <see cref="Basis"/>, which implements matrix + /// representation of rotations, and can be parametrized using both + /// an axis-angle pair or Euler angles. Basis stores rotation, scale, + /// and shearing, while Quaternion only stores rotation. /// /// Due to its compactness and the way it is stored in memory, certain /// operations (obtaining axis-angle and performing SLERP, in particular) @@ -26,19 +26,19 @@ namespace Godot public struct Quaternion : IEquatable<Quaternion> { /// <summary> - /// X component of the quaternion (imaginary `i` axis part). + /// X component of the quaternion (imaginary <c>i</c> axis part). /// Quaternion components should usually not be manipulated directly. /// </summary> public real_t x; /// <summary> - /// Y component of the quaternion (imaginary `j` axis part). + /// Y component of the quaternion (imaginary <c>j</c> axis part). /// Quaternion components should usually not be manipulated directly. /// </summary> public real_t y; /// <summary> - /// Z component of the quaternion (imaginary `k` axis part). + /// Z component of the quaternion (imaginary <c>k</c> axis part). /// Quaternion components should usually not be manipulated directly. /// </summary> public real_t z; @@ -52,7 +52,12 @@ namespace Godot /// <summary> /// Access quaternion components using their index. /// </summary> - /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`, `[2]` is equivalent to `.z`, `[3]` is equivalent to `.w`.</value> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="x"/>, + /// <c>[1]</c> is equivalent to <see cref="y"/>, + /// <c>[2]</c> is equivalent to <see cref="z"/>, + /// <c>[3]</c> is equivalent to <see cref="w"/>. + /// </value> public real_t this[int index] { get @@ -96,7 +101,8 @@ namespace Godot /// <summary> /// Returns the length (magnitude) of the quaternion. /// </summary> - /// <value>Equivalent to `Mathf.Sqrt(LengthSquared)`.</value> + /// <seealso cref="LengthSquared"/> + /// <value>Equivalent to <c>Mathf.Sqrt(LengthSquared)</c>.</value> public real_t Length { get { return Mathf.Sqrt(LengthSquared); } @@ -107,14 +113,14 @@ namespace Godot /// This method runs faster than <see cref="Length"/>, so prefer it if /// you need to compare quaternions or need the squared length for some formula. /// </summary> - /// <value>Equivalent to `Dot(this)`.</value> + /// <value>Equivalent to <c>Dot(this)</c>.</value> public real_t LengthSquared { get { return Dot(this); } } /// <summary> - /// Returns the angle between this quaternion and `to`. + /// Returns the angle between this quaternion and <paramref name="to"/>. /// This is the magnitude of the angle you would need to rotate /// by to get from one to the other. /// @@ -131,12 +137,12 @@ namespace Godot } /// <summary> - /// Performs a cubic spherical interpolation between quaternions `preA`, - /// this vector, `b`, and `postB`, by the given amount `t`. + /// Performs a cubic spherical interpolation between quaternions <paramref name="preA"/>, this quaternion, + /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. /// </summary> /// <param name="b">The destination quaternion.</param> /// <param name="preA">A quaternion before this quaternion.</param> - /// <param name="postB">A quaternion after `b`.</param> + /// <param name="postB">A quaternion after <paramref name="b"/>.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated quaternion.</returns> public Quaternion CubicSlerp(Quaternion b, Quaternion preA, Quaternion postB, real_t weight) @@ -154,7 +160,7 @@ namespace Godot /// <returns>The dot product.</returns> public real_t Dot(Quaternion b) { - return x * b.x + y * b.y + z * b.z + w * b.w; + return (x * b.x) + (y * b.y) + (z * b.z) + (w * b.w); } /// <summary> @@ -194,7 +200,7 @@ namespace Godot /// <summary> /// Returns whether the quaternion is normalized or not. /// </summary> - /// <returns>A bool for whether the quaternion is normalized or not.</returns> + /// <returns>A <see langword="bool"/> for whether the quaternion is normalized or not.</returns> public bool IsNormalized() { return Mathf.Abs(LengthSquared - 1) <= Mathf.Epsilon; @@ -211,7 +217,7 @@ namespace Godot /// <summary> /// Returns the result of the spherical linear interpolation between - /// this quaternion and `to` by amount `weight`. + /// this quaternion and <paramref name="to"/> by amount <paramref name="weight"/>. /// /// Note: Both quaternions must be normalized. /// </summary> @@ -274,16 +280,16 @@ namespace Godot // Calculate final values. return new Quaternion ( - scale0 * x + scale1 * to1.x, - scale0 * y + scale1 * to1.y, - scale0 * z + scale1 * to1.z, - scale0 * w + scale1 * to1.w + (scale0 * x) + (scale1 * to1.x), + (scale0 * y) + (scale1 * to1.y), + (scale0 * z) + (scale1 * to1.z), + (scale0 * w) + (scale1 * to1.w) ); } /// <summary> /// Returns the result of the spherical linear interpolation between - /// this quaternion and `to` by amount `weight`, but without + /// this quaternion and <paramref name="to"/> by amount <paramref name="weight"/>, but without /// checking if the rotation path is not bigger than 90 degrees. /// </summary> /// <param name="to">The destination quaternion for interpolation. Must be normalized.</param> @@ -305,10 +311,10 @@ namespace Godot return new Quaternion ( - invFactor * x + newFactor * to.x, - invFactor * y + newFactor * to.y, - invFactor * z + newFactor * to.z, - invFactor * w + newFactor * to.w + (invFactor * x) + (newFactor * to.x), + (invFactor * y) + (newFactor * to.y), + (invFactor * z) + (newFactor * to.z), + (invFactor * w) + (newFactor * to.w) ); } @@ -327,7 +333,7 @@ namespace Godot #endif var u = new Vector3(x, y, z); Vector3 uv = u.Cross(v); - return v + ((uv * w) + u.Cross(uv)) * 2; + return v + (((uv * w) + u.Cross(uv)) * 2); } // Constants @@ -338,15 +344,15 @@ namespace Godot /// Equivalent to an identity <see cref="Basis"/> matrix. If a vector is transformed by /// an identity quaternion, it will not change. /// </summary> - /// <value>Equivalent to `new Quaternion(0, 0, 0, 1)`.</value> + /// <value>Equivalent to <c>new Quaternion(0, 0, 0, 1)</c>.</value> public static Quaternion Identity { get { return _identity; } } /// <summary> - /// Constructs a quaternion defined by the given values. + /// Constructs a <see cref="Quaternion"/> defined by the given values. /// </summary> - /// <param name="x">X component of the quaternion (imaginary `i` axis part).</param> - /// <param name="y">Y component of the quaternion (imaginary `j` axis part).</param> - /// <param name="z">Z component of the quaternion (imaginary `k` axis part).</param> + /// <param name="x">X component of the quaternion (imaginary <c>i</c> axis part).</param> + /// <param name="y">Y component of the quaternion (imaginary <c>j</c> axis part).</param> + /// <param name="z">Z component of the quaternion (imaginary <c>k</c> axis part).</param> /// <param name="w">W component of the quaternion (real part).</param> public Quaternion(real_t x, real_t y, real_t z, real_t w) { @@ -357,7 +363,7 @@ namespace Godot } /// <summary> - /// Constructs a quaternion from the given quaternion. + /// Constructs a <see cref="Quaternion"/> from the given <see cref="Quaternion"/>. /// </summary> /// <param name="q">The existing quaternion.</param> public Quaternion(Quaternion q) @@ -366,46 +372,45 @@ namespace Godot } /// <summary> - /// Constructs a quaternion from the given <see cref="Basis"/>. + /// Constructs a <see cref="Quaternion"/> from the given <see cref="Basis"/>. /// </summary> - /// <param name="basis">The basis to construct from.</param> + /// <param name="basis">The <see cref="Basis"/> to construct from.</param> public Quaternion(Basis basis) { this = basis.Quaternion(); } /// <summary> - /// Constructs a quaternion that will perform a rotation specified by - /// Euler angles (in the YXZ convention: when decomposing, - /// first Z, then X, and Y last), + /// Constructs a <see cref="Quaternion"/> that will perform a rotation specified by + /// Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last), /// given in the vector format as (X angle, Y angle, Z angle). /// </summary> - /// <param name="eulerYXZ"></param> + /// <param name="eulerYXZ">Euler angles that the quaternion will be rotated by.</param> public Quaternion(Vector3 eulerYXZ) { - real_t half_a1 = eulerYXZ.y * 0.5f; - real_t half_a2 = eulerYXZ.x * 0.5f; - real_t half_a3 = eulerYXZ.z * 0.5f; + real_t halfA1 = eulerYXZ.y * 0.5f; + real_t halfA2 = eulerYXZ.x * 0.5f; + real_t halfA3 = eulerYXZ.z * 0.5f; // R = Y(a1).X(a2).Z(a3) convention for Euler angles. // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6) // a3 is the angle of the first rotation, following the notation in this reference. - real_t cos_a1 = Mathf.Cos(half_a1); - real_t sin_a1 = Mathf.Sin(half_a1); - real_t cos_a2 = Mathf.Cos(half_a2); - real_t sin_a2 = Mathf.Sin(half_a2); - real_t cos_a3 = Mathf.Cos(half_a3); - real_t sin_a3 = Mathf.Sin(half_a3); + real_t cosA1 = Mathf.Cos(halfA1); + real_t sinA1 = Mathf.Sin(halfA1); + real_t cosA2 = Mathf.Cos(halfA2); + real_t sinA2 = Mathf.Sin(halfA2); + real_t cosA3 = Mathf.Cos(halfA3); + real_t sinA3 = Mathf.Sin(halfA3); - x = sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3; - y = sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3; - z = cos_a1 * cos_a2 * sin_a3 - sin_a1 * sin_a2 * cos_a3; - w = sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3; + x = (sinA1 * cosA2 * sinA3) + (cosA1 * sinA2 * cosA3); + y = (sinA1 * cosA2 * cosA3) - (cosA1 * sinA2 * sinA3); + z = (cosA1 * cosA2 * sinA3) - (sinA1 * sinA2 * cosA3); + w = (sinA1 * sinA2 * sinA3) + (cosA1 * cosA2 * cosA3); } /// <summary> - /// Constructs a quaternion that will rotate around the given axis + /// Constructs a <see cref="Quaternion"/> that will rotate around the given axis /// by the specified angle. The axis must be a normalized vector. /// </summary> /// <param name="axis">The axis to rotate around. Must be normalized.</param> @@ -445,10 +450,10 @@ namespace Godot { return new Quaternion ( - left.w * right.x + left.x * right.w + left.y * right.z - left.z * right.y, - left.w * right.y + left.y * right.w + left.z * right.x - left.x * right.z, - left.w * right.z + left.z * right.w + left.x * right.y - left.y * right.x, - left.w * right.w - left.x * right.x - left.y * right.y - left.z * right.z + (left.w * right.x) + (left.x * right.w) + (left.y * right.z) - (left.z * right.y), + (left.w * right.y) + (left.y * right.w) + (left.z * right.x) - (left.x * right.z), + (left.w * right.z) + (left.z * right.w) + (left.x * right.y) - (left.y * right.x), + (left.w * right.w) - (left.x * right.x) - (left.y * right.y) - (left.z * right.z) ); } @@ -471,10 +476,10 @@ namespace Godot { return new Quaternion ( - left.w * right.x + left.y * right.z - left.z * right.y, - left.w * right.y + left.z * right.x - left.x * right.z, - left.w * right.z + left.x * right.y - left.y * right.x, - -left.x * right.x - left.y * right.y - left.z * right.z + (left.w * right.x) + (left.y * right.z) - (left.z * right.y), + (left.w * right.y) + (left.z * right.x) - (left.x * right.z), + (left.w * right.z) + (left.x * right.y) - (left.y * right.x), + -(left.x * right.x) - (left.y * right.y) - (left.z * right.z) ); } @@ -482,10 +487,10 @@ namespace Godot { return new Quaternion ( - right.w * left.x + right.y * left.z - right.z * left.y, - right.w * left.y + right.z * left.x - right.x * left.z, - right.w * left.z + right.x * left.y - right.y * left.x, - -right.x * left.x - right.y * left.y - right.z * left.z + (right.w * left.x) + (right.y * left.z) - (right.z * left.y), + (right.w * left.y) + (right.z * left.x) - (right.x * left.z), + (right.w * left.z) + (right.x * left.y) - (right.y * left.x), + -(right.x * left.x) - (right.y * left.y) - (right.z * left.z) ); } @@ -514,6 +519,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this quaternion and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the quaternion and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Quaternion) @@ -524,14 +534,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this quaternion and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other quaternion to compare.</param> + /// <returns>Whether or not the quaternions are equal.</returns> public bool Equals(Quaternion other) { return x == other.x && y == other.y && z == other.z && w == other.w; } /// <summary> - /// Returns true if this quaternion and `other` are approximately equal, by running - /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. + /// Returns <see langword="true"/> if this quaternion and <paramref name="other"/> are approximately equal, + /// by running <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. /// </summary> /// <param name="other">The other quaternion to compare.</param> /// <returns>Whether or not the quaternions are approximately equal.</returns> @@ -540,16 +555,28 @@ namespace Godot return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z) && Mathf.IsEqualApprox(w, other.w); } + /// <summary> + /// Serves as the hash function for <see cref="Quaternion"/>. + /// </summary> + /// <returns>A hash code for this quaternion.</returns> public override int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Quaternion"/> to a string. + /// </summary> + /// <returns>A string representation of this quaternion.</returns> public override string ToString() { return $"({x}, {y}, {z}, {w})"; } + /// <summary> + /// Converts this <see cref="Quaternion"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this quaternion.</returns> public string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)}, {w.ToString(format)})"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs index 94761531b1..1588869ec0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs @@ -3,9 +3,15 @@ using System.Runtime.CompilerServices; namespace Godot { + /// <summary> + /// The RID type is used to access the unique integer ID of a resource. + /// They are opaque, which means they do not grant access to the associated + /// resource by themselves. They are used by and with the low-level Server + /// classes such as <see cref="RenderingServer"/>. + /// </summary> public sealed partial class RID : IDisposable { - private bool disposed = false; + private bool _disposed = false; internal IntPtr ptr; @@ -14,7 +20,7 @@ namespace Godot if (instance == null) throw new NullReferenceException($"The instance of type {nameof(RID)} is null."); - if (instance.disposed) + if (instance._disposed) throw new ObjectDisposedException(instance.GetType().FullName); return instance.ptr; @@ -25,6 +31,9 @@ namespace Godot Dispose(false); } + /// <summary> + /// Disposes of this <see cref="RID"/>. + /// </summary> public void Dispose() { Dispose(true); @@ -33,7 +42,7 @@ namespace Godot private void Dispose(bool disposing) { - if (disposed) + if (_disposed) return; if (ptr != IntPtr.Zero) @@ -42,7 +51,7 @@ namespace Godot ptr = IntPtr.Zero; } - disposed = true; + _disposed = true; } internal RID(IntPtr ptr) @@ -50,6 +59,9 @@ namespace Godot this.ptr = ptr; } + /// <summary> + /// The pointer to the native instance of this <see cref="RID"/>. + /// </summary> public IntPtr NativeInstance { get { return ptr; } @@ -60,25 +72,36 @@ namespace Godot this.ptr = IntPtr.Zero; } + /// <summary> + /// Constructs a new <see cref="RID"/> for the given <see cref="Object"/> <paramref name="from"/>. + /// </summary> public RID(Object from) { this.ptr = godot_icall_RID_Ctor(Object.GetPtr(from)); } + /// <summary> + /// Returns the ID of the referenced resource. + /// </summary> + /// <returns>The ID of the referenced resource.</returns> public int GetId() { - return godot_icall_RID_get_id(RID.GetPtr(this)); + return godot_icall_RID_get_id(GetPtr(this)); } + /// <summary> + /// Converts this <see cref="RID"/> to a string. + /// </summary> + /// <returns>A string representation of this RID.</returns> public override string ToString() => "[RID]"; [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_RID_Ctor(IntPtr from); + internal static extern IntPtr godot_icall_RID_Ctor(IntPtr from); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_RID_Dtor(IntPtr ptr); + internal static extern void godot_icall_RID_Dtor(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_RID_get_id(IntPtr ptr); + internal static extern int godot_icall_RID_get_id(IntPtr ptr); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index dec69c7f94..1d001fba2d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -20,7 +20,7 @@ namespace Godot private Vector2 _size; /// <summary> - /// Beginning corner. Typically has values lower than End. + /// Beginning corner. Typically has values lower than <see cref="End"/>. /// </summary> /// <value>Directly uses a private field.</value> public Vector2 Position @@ -30,7 +30,7 @@ namespace Godot } /// <summary> - /// Size from Position to End. Typically all components are positive. + /// Size from <see cref="Position"/> to <see cref="End"/>. Typically all components are positive. /// If the size is negative, you can use <see cref="Abs"/> to fix it. /// </summary> /// <value>Directly uses a private field.</value> @@ -41,10 +41,13 @@ namespace Godot } /// <summary> - /// Ending corner. This is calculated as <see cref="Position"/> plus - /// <see cref="Size"/>. Setting this value will change the size. + /// Ending corner. This is calculated as <see cref="Position"/> plus <see cref="Size"/>. + /// Setting this value will change the size. /// </summary> - /// <value>Getting is equivalent to `value = Position + Size`, setting is equivalent to `Size = value - Position`.</value> + /// <value> + /// Getting is equivalent to <paramref name="value"/> = <see cref="Position"/> + <see cref="Size"/>, + /// setting is equivalent to <see cref="Size"/> = <paramref name="value"/> - <see cref="Position"/> + /// </value> public Vector2 End { get { return _position + _size; } @@ -52,7 +55,7 @@ namespace Godot } /// <summary> - /// The area of this Rect2. + /// The area of this <see cref="Rect2"/>. /// </summary> /// <value>Equivalent to <see cref="GetArea()"/>.</value> public real_t Area @@ -61,10 +64,10 @@ namespace Godot } /// <summary> - /// Returns a Rect2 with equivalent position and size, modified so that + /// Returns a <see cref="Rect2"/> with equivalent position and size, modified so that /// the top-left corner is the origin and width and height are positive. /// </summary> - /// <returns>The modified Rect2.</returns> + /// <returns>The modified <see cref="Rect2"/>.</returns> public Rect2 Abs() { Vector2 end = End; @@ -73,14 +76,17 @@ namespace Godot } /// <summary> - /// Returns the intersection of this Rect2 and `b`. - /// If the rectangles do not intersect, an empty Rect2 is returned. + /// Returns the intersection of this <see cref="Rect2"/> and <paramref name="b"/>. + /// If the rectangles do not intersect, an empty <see cref="Rect2"/> is returned. /// </summary> - /// <param name="b">The other Rect2.</param> - /// <returns>The intersection of this Rect2 and `b`, or an empty Rect2 if they do not intersect.</returns> + /// <param name="b">The other <see cref="Rect2"/>.</param> + /// <returns> + /// The intersection of this <see cref="Rect2"/> and <paramref name="b"/>, + /// or an empty <see cref="Rect2"/> if they do not intersect. + /// </returns> public Rect2 Intersection(Rect2 b) { - var newRect = b; + Rect2 newRect = b; if (!Intersects(newRect)) { @@ -100,10 +106,12 @@ namespace Godot } /// <summary> - /// Returns true if this Rect2 completely encloses another one. + /// Returns <see langword="true"/> if this <see cref="Rect2"/> completely encloses another one. /// </summary> - /// <param name="b">The other Rect2 that may be enclosed.</param> - /// <returns>A bool for whether or not this Rect2 encloses `b`.</returns> + /// <param name="b">The other <see cref="Rect2"/> that may be enclosed.</param> + /// <returns> + /// A <see langword="bool"/> for whether or not this <see cref="Rect2"/> encloses <paramref name="b"/>. + /// </returns> public bool Encloses(Rect2 b) { return b._position.x >= _position.x && b._position.y >= _position.y && @@ -112,13 +120,13 @@ namespace Godot } /// <summary> - /// Returns this Rect2 expanded to include a given point. + /// Returns this <see cref="Rect2"/> expanded to include a given point. /// </summary> /// <param name="to">The point to include.</param> - /// <returns>The expanded Rect2.</returns> + /// <returns>The expanded <see cref="Rect2"/>.</returns> public Rect2 Expand(Vector2 to) { - var expanded = this; + Rect2 expanded = this; Vector2 begin = expanded._position; Vector2 end = expanded._position + expanded._size; @@ -148,7 +156,7 @@ namespace Godot } /// <summary> - /// Returns the area of the Rect2. + /// Returns the area of the <see cref="Rect2"/>. /// </summary> /// <returns>The area.</returns> public real_t GetArea() @@ -157,13 +165,16 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2 grown by the specified amount on all sides. + /// Returns a copy of the <see cref="Rect2"/> grown by the specified amount + /// on all sides. /// </summary> + /// <seealso cref="GrowIndividual(real_t, real_t, real_t, real_t)"/> + /// <seealso cref="GrowSide(Side, real_t)"/> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown Rect2.</returns> + /// <returns>The grown <see cref="Rect2"/>.</returns> public Rect2 Grow(real_t by) { - var g = this; + Rect2 g = this; g._position.x -= by; g._position.y -= by; @@ -174,16 +185,19 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2 grown by the specified amount on each side individually. + /// Returns a copy of the <see cref="Rect2"/> grown by the specified amount + /// on each side individually. /// </summary> + /// <seealso cref="Grow(real_t)"/> + /// <seealso cref="GrowSide(Side, real_t)"/> /// <param name="left">The amount to grow by on the left side.</param> /// <param name="top">The amount to grow by on the top side.</param> /// <param name="right">The amount to grow by on the right side.</param> /// <param name="bottom">The amount to grow by on the bottom side.</param> - /// <returns>The grown Rect2.</returns> + /// <returns>The grown <see cref="Rect2"/>.</returns> public Rect2 GrowIndividual(real_t left, real_t top, real_t right, real_t bottom) { - var g = this; + Rect2 g = this; g._position.x -= left; g._position.y -= top; @@ -194,14 +208,17 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2 grown by the specified amount on the specified Side. + /// Returns a copy of the <see cref="Rect2"/> grown by the specified amount + /// on the specified <see cref="Side"/>. /// </summary> + /// <seealso cref="Grow(real_t)"/> + /// <seealso cref="GrowIndividual(real_t, real_t, real_t, real_t)"/> /// <param name="side">The side to grow.</param> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown Rect2.</returns> + /// <returns>The grown <see cref="Rect2"/>.</returns> public Rect2 GrowSide(Side side, real_t by) { - var g = this; + Rect2 g = this; g = g.GrowIndividual(Side.Left == side ? by : 0, Side.Top == side ? by : 0, @@ -212,19 +229,25 @@ namespace Godot } /// <summary> - /// Returns true if the Rect2 is flat or empty, or false otherwise. + /// Returns <see langword="true"/> if the <see cref="Rect2"/> is flat or empty, + /// or <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool for whether or not the Rect2 has area.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="Rect2"/> has area. + /// </returns> public bool HasNoArea() { return _size.x <= 0 || _size.y <= 0; } /// <summary> - /// Returns true if the Rect2 contains a point, or false otherwise. + /// Returns <see langword="true"/> if the <see cref="Rect2"/> contains a point, + /// or <see langword="false"/> otherwise. /// </summary> /// <param name="point">The point to check.</param> - /// <returns>A bool for whether or not the Rect2 contains `point`.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="Rect2"/> contains <paramref name="point"/>. + /// </returns> public bool HasPoint(Vector2 point) { if (point.x < _position.x) @@ -241,15 +264,16 @@ namespace Godot } /// <summary> - /// Returns true if the Rect2 overlaps with `b` + /// Returns <see langword="true"/> if the <see cref="Rect2"/> overlaps with <paramref name="b"/> /// (i.e. they have at least one point in common). /// - /// If `includeBorders` is true, they will also be considered overlapping - /// if their borders touch, even without intersection. + /// If <paramref name="includeBorders"/> is <see langword="true"/>, + /// they will also be considered overlapping if their borders touch, + /// even without intersection. /// </summary> - /// <param name="b">The other Rect2 to check for intersections with.</param> + /// <param name="b">The other <see cref="Rect2"/> to check for intersections with.</param> /// <param name="includeBorders">Whether or not to consider borders.</param> - /// <returns>A bool for whether or not they are intersecting.</returns> + /// <returns>A <see langword="bool"/> for whether or not they are intersecting.</returns> public bool Intersects(Rect2 b, bool includeBorders = false) { if (includeBorders) @@ -295,10 +319,10 @@ namespace Godot } /// <summary> - /// Returns a larger Rect2 that contains this Rect2 and `b`. + /// Returns a larger <see cref="Rect2"/> that contains this <see cref="Rect2"/> and <paramref name="b"/>. /// </summary> - /// <param name="b">The other Rect2.</param> - /// <returns>The merged Rect2.</returns> + /// <param name="b">The other <see cref="Rect2"/>.</param> + /// <returns>The merged <see cref="Rect2"/>.</returns> public Rect2 Merge(Rect2 b) { Rect2 newRect; @@ -315,7 +339,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2 from a position and size. + /// Constructs a <see cref="Rect2"/> from a position and size. /// </summary> /// <param name="position">The position.</param> /// <param name="size">The size.</param> @@ -326,7 +350,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2 from a position, width, and height. + /// Constructs a <see cref="Rect2"/> from a position, width, and height. /// </summary> /// <param name="position">The position.</param> /// <param name="width">The width.</param> @@ -338,7 +362,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2 from x, y, and size. + /// Constructs a <see cref="Rect2"/> from x, y, and size. /// </summary> /// <param name="x">The position's X coordinate.</param> /// <param name="y">The position's Y coordinate.</param> @@ -350,7 +374,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2 from x, y, width, and height. + /// Constructs a <see cref="Rect2"/> from x, y, width, and height. /// </summary> /// <param name="x">The position's X coordinate.</param> /// <param name="y">The position's Y coordinate.</param> @@ -372,6 +396,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this rect and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the rect and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Rect2) @@ -382,32 +411,49 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this rect and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other rect to compare.</param> + /// <returns>Whether or not the rects are equal.</returns> public bool Equals(Rect2 other) { return _position.Equals(other._position) && _size.Equals(other._size); } /// <summary> - /// Returns true if this Rect2 and `other` are approximately equal, by running - /// <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component. + /// Returns <see langword="true"/> if this rect and <paramref name="other"/> are approximately equal, + /// by running <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component. /// </summary> - /// <param name="other">The other Rect2 to compare.</param> - /// <returns>Whether or not the Rect2s are approximately equal.</returns> + /// <param name="other">The other rect to compare.</param> + /// <returns>Whether or not the rects are approximately equal.</returns> public bool IsEqualApprox(Rect2 other) { return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other.Size); } + /// <summary> + /// Serves as the hash function for <see cref="Rect2"/>. + /// </summary> + /// <returns>A hash code for this rect.</returns> public override int GetHashCode() { return _position.GetHashCode() ^ _size.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Rect2"/> to a string. + /// </summary> + /// <returns>A string representation of this rect.</returns> public override string ToString() { return $"{_position}, {_size}"; } + /// <summary> + /// Converts this <see cref="Rect2"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this rect.</returns> public string ToString(string format) { return $"{_position.ToString(format)}, {_size.ToString(format)}"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs index 7fb6614d2c..250b0d0baf 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs @@ -15,7 +15,7 @@ namespace Godot private Vector2i _size; /// <summary> - /// Beginning corner. Typically has values lower than End. + /// Beginning corner. Typically has values lower than <see cref="End"/>. /// </summary> /// <value>Directly uses a private field.</value> public Vector2i Position @@ -25,7 +25,7 @@ namespace Godot } /// <summary> - /// Size from Position to End. Typically all components are positive. + /// Size from <see cref="Position"/> to <see cref="End"/>. Typically all components are positive. /// If the size is negative, you can use <see cref="Abs"/> to fix it. /// </summary> /// <value>Directly uses a private field.</value> @@ -36,10 +36,13 @@ namespace Godot } /// <summary> - /// Ending corner. This is calculated as <see cref="Position"/> plus - /// <see cref="Size"/>. Setting this value will change the size. + /// Ending corner. This is calculated as <see cref="Position"/> plus <see cref="Size"/>. + /// Setting this value will change the size. /// </summary> - /// <value>Getting is equivalent to `value = Position + Size`, setting is equivalent to `Size = value - Position`.</value> + /// <value> + /// Getting is equivalent to <paramref name="value"/> = <see cref="Position"/> + <see cref="Size"/>, + /// setting is equivalent to <see cref="Size"/> = <paramref name="value"/> - <see cref="Position"/> + /// </value> public Vector2i End { get { return _position + _size; } @@ -47,7 +50,7 @@ namespace Godot } /// <summary> - /// The area of this Rect2i. + /// The area of this <see cref="Rect2i"/>. /// </summary> /// <value>Equivalent to <see cref="GetArea()"/>.</value> public int Area @@ -56,10 +59,10 @@ namespace Godot } /// <summary> - /// Returns a Rect2i with equivalent position and size, modified so that + /// Returns a <see cref="Rect2i"/> with equivalent position and size, modified so that /// the top-left corner is the origin and width and height are positive. /// </summary> - /// <returns>The modified Rect2i.</returns> + /// <returns>The modified <see cref="Rect2i"/>.</returns> public Rect2i Abs() { Vector2i end = End; @@ -68,14 +71,17 @@ namespace Godot } /// <summary> - /// Returns the intersection of this Rect2i and `b`. - /// If the rectangles do not intersect, an empty Rect2i is returned. + /// Returns the intersection of this <see cref="Rect2i"/> and <paramref name="b"/>. + /// If the rectangles do not intersect, an empty <see cref="Rect2i"/> is returned. /// </summary> - /// <param name="b">The other Rect2i.</param> - /// <returns>The intersection of this Rect2i and `b`, or an empty Rect2i if they do not intersect.</returns> + /// <param name="b">The other <see cref="Rect2i"/>.</param> + /// <returns> + /// The intersection of this <see cref="Rect2i"/> and <paramref name="b"/>, + /// or an empty <see cref="Rect2i"/> if they do not intersect. + /// </returns> public Rect2i Intersection(Rect2i b) { - var newRect = b; + Rect2i newRect = b; if (!Intersects(newRect)) { @@ -95,10 +101,12 @@ namespace Godot } /// <summary> - /// Returns true if this Rect2i completely encloses another one. + /// Returns <see langword="true"/> if this <see cref="Rect2i"/> completely encloses another one. /// </summary> - /// <param name="b">The other Rect2i that may be enclosed.</param> - /// <returns>A bool for whether or not this Rect2i encloses `b`.</returns> + /// <param name="b">The other <see cref="Rect2i"/> that may be enclosed.</param> + /// <returns> + /// A <see langword="bool"/> for whether or not this <see cref="Rect2i"/> encloses <paramref name="b"/>. + /// </returns> public bool Encloses(Rect2i b) { return b._position.x >= _position.x && b._position.y >= _position.y && @@ -107,13 +115,13 @@ namespace Godot } /// <summary> - /// Returns this Rect2i expanded to include a given point. + /// Returns this <see cref="Rect2i"/> expanded to include a given point. /// </summary> /// <param name="to">The point to include.</param> - /// <returns>The expanded Rect2i.</returns> + /// <returns>The expanded <see cref="Rect2i"/>.</returns> public Rect2i Expand(Vector2i to) { - var expanded = this; + Rect2i expanded = this; Vector2i begin = expanded._position; Vector2i end = expanded._position + expanded._size; @@ -143,7 +151,7 @@ namespace Godot } /// <summary> - /// Returns the area of the Rect2. + /// Returns the area of the <see cref="Rect2"/>. /// </summary> /// <returns>The area.</returns> public int GetArea() @@ -152,13 +160,16 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2i grown by the specified amount on all sides. + /// Returns a copy of the <see cref="Rect2i"/> grown by the specified amount + /// on all sides. /// </summary> + /// <seealso cref="GrowIndividual(int, int, int, int)"/> + /// <seealso cref="GrowSide(Side, int)"/> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown Rect2i.</returns> + /// <returns>The grown <see cref="Rect2i"/>.</returns> public Rect2i Grow(int by) { - var g = this; + Rect2i g = this; g._position.x -= by; g._position.y -= by; @@ -169,16 +180,19 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2i grown by the specified amount on each side individually. + /// Returns a copy of the <see cref="Rect2i"/> grown by the specified amount + /// on each side individually. /// </summary> + /// <seealso cref="Grow(int)"/> + /// <seealso cref="GrowSide(Side, int)"/> /// <param name="left">The amount to grow by on the left side.</param> /// <param name="top">The amount to grow by on the top side.</param> /// <param name="right">The amount to grow by on the right side.</param> /// <param name="bottom">The amount to grow by on the bottom side.</param> - /// <returns>The grown Rect2i.</returns> + /// <returns>The grown <see cref="Rect2i"/>.</returns> public Rect2i GrowIndividual(int left, int top, int right, int bottom) { - var g = this; + Rect2i g = this; g._position.x -= left; g._position.y -= top; @@ -189,14 +203,17 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2i grown by the specified amount on the specified Side. + /// Returns a copy of the <see cref="Rect2i"/> grown by the specified amount + /// on the specified <see cref="Side"/>. /// </summary> + /// <seealso cref="Grow(int)"/> + /// <seealso cref="GrowIndividual(int, int, int, int)"/> /// <param name="side">The side to grow.</param> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown Rect2i.</returns> + /// <returns>The grown <see cref="Rect2i"/>.</returns> public Rect2i GrowSide(Side side, int by) { - var g = this; + Rect2i g = this; g = g.GrowIndividual(Side.Left == side ? by : 0, Side.Top == side ? by : 0, @@ -207,19 +224,25 @@ namespace Godot } /// <summary> - /// Returns true if the Rect2i is flat or empty, or false otherwise. + /// Returns <see langword="true"/> if the <see cref="Rect2i"/> is flat or empty, + /// or <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool for whether or not the Rect2i has area.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="Rect2i"/> has area. + /// </returns> public bool HasNoArea() { return _size.x <= 0 || _size.y <= 0; } /// <summary> - /// Returns true if the Rect2i contains a point, or false otherwise. + /// Returns <see langword="true"/> if the <see cref="Rect2i"/> contains a point, + /// or <see langword="false"/> otherwise. /// </summary> /// <param name="point">The point to check.</param> - /// <returns>A bool for whether or not the Rect2i contains `point`.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="Rect2i"/> contains <paramref name="point"/>. + /// </returns> public bool HasPoint(Vector2i point) { if (point.x < _position.x) @@ -236,15 +259,16 @@ namespace Godot } /// <summary> - /// Returns true if the Rect2i overlaps with `b` + /// Returns <see langword="true"/> if the <see cref="Rect2i"/> overlaps with <paramref name="b"/> /// (i.e. they have at least one point in common). /// - /// If `includeBorders` is true, they will also be considered overlapping - /// if their borders touch, even without intersection. + /// If <paramref name="includeBorders"/> is <see langword="true"/>, + /// they will also be considered overlapping if their borders touch, + /// even without intersection. /// </summary> - /// <param name="b">The other Rect2i to check for intersections with.</param> + /// <param name="b">The other <see cref="Rect2i"/> to check for intersections with.</param> /// <param name="includeBorders">Whether or not to consider borders.</param> - /// <returns>A bool for whether or not they are intersecting.</returns> + /// <returns>A <see langword="bool"/> for whether or not they are intersecting.</returns> public bool Intersects(Rect2i b, bool includeBorders = false) { if (includeBorders) @@ -274,10 +298,10 @@ namespace Godot } /// <summary> - /// Returns a larger Rect2i that contains this Rect2i and `b`. + /// Returns a larger <see cref="Rect2i"/> that contains this <see cref="Rect2i"/> and <paramref name="b"/>. /// </summary> - /// <param name="b">The other Rect2i.</param> - /// <returns>The merged Rect2i.</returns> + /// <param name="b">The other <see cref="Rect2i"/>.</param> + /// <returns>The merged <see cref="Rect2i"/>.</returns> public Rect2i Merge(Rect2i b) { Rect2i newRect; @@ -294,7 +318,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2i from a position and size. + /// Constructs a <see cref="Rect2i"/> from a position and size. /// </summary> /// <param name="position">The position.</param> /// <param name="size">The size.</param> @@ -305,7 +329,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2i from a position, width, and height. + /// Constructs a <see cref="Rect2i"/> from a position, width, and height. /// </summary> /// <param name="position">The position.</param> /// <param name="width">The width.</param> @@ -317,7 +341,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2i from x, y, and size. + /// Constructs a <see cref="Rect2i"/> from x, y, and size. /// </summary> /// <param name="x">The position's X coordinate.</param> /// <param name="y">The position's Y coordinate.</param> @@ -329,7 +353,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2i from x, y, width, and height. + /// Constructs a <see cref="Rect2i"/> from x, y, width, and height. /// </summary> /// <param name="x">The position's X coordinate.</param> /// <param name="y">The position's Y coordinate.</param> @@ -351,16 +375,29 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Converts this <see cref="Rect2i"/> to a <see cref="Rect2"/>. + /// </summary> + /// <param name="value">The rect to convert.</param> public static implicit operator Rect2(Rect2i value) { return new Rect2(value._position, value._size); } + /// <summary> + /// Converts a <see cref="Rect2"/> to a <see cref="Rect2i"/>. + /// </summary> + /// <param name="value">The rect to convert.</param> public static explicit operator Rect2i(Rect2 value) { return new Rect2i((Vector2i)value.Position, (Vector2i)value.Size); } + /// <summary> + /// Returns <see langword="true"/> if this rect and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the rect and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Rect2i) @@ -371,21 +408,38 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this rect and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other rect to compare.</param> + /// <returns>Whether or not the rects are equal.</returns> public bool Equals(Rect2i other) { return _position.Equals(other._position) && _size.Equals(other._size); } + /// <summary> + /// Serves as the hash function for <see cref="Rect2i"/>. + /// </summary> + /// <returns>A hash code for this rect.</returns> public override int GetHashCode() { return _position.GetHashCode() ^ _size.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Rect2i"/> to a string. + /// </summary> + /// <returns>A string representation of this rect.</returns> public override string ToString() { return $"{_position}, {_size}"; } + /// <summary> + /// Converts this <see cref="Rect2i"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this rect.</returns> public string ToString(string format) { return $"{_position.ToString(format)}, {_size.ToString(format)}"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs index 4dc630238b..2ba0493002 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs @@ -5,9 +5,9 @@ namespace Godot { public class SignalAwaiter : IAwaiter<object[]>, IAwaitable<object[]> { - private bool completed; - private object[] result; - private Action action; + private bool _completed; + private object[] _result; + private Action _action; public SignalAwaiter(Object source, StringName signal, Object target) { @@ -15,24 +15,24 @@ namespace Godot } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static Error godot_icall_SignalAwaiter_connect(IntPtr source, IntPtr signal, IntPtr target, SignalAwaiter awaiter); + internal static extern Error godot_icall_SignalAwaiter_connect(IntPtr source, IntPtr signal, IntPtr target, SignalAwaiter awaiter); public bool IsCompleted { get { - return completed; + return _completed; } } public void OnCompleted(Action action) { - this.action = action; + this._action = action; } public object[] GetResult() { - return result; + return _result; } public IAwaiter<object[]> GetAwaiter() @@ -42,13 +42,9 @@ namespace Godot internal void SignalCallback(object[] args) { - completed = true; - result = args; - - if (action != null) - { - action(); - } + _completed = true; + _result = args; + _action?.Invoke(); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs index dc92de7a61..5680c9d55a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs @@ -1,13 +1,28 @@ namespace Godot { + /// <summary> + /// Represents a signal defined in an object. + /// </summary> public struct SignalInfo { private readonly Object _owner; private readonly StringName _signalName; + /// <summary> + /// Object that contains the signal. + /// </summary> public Object Owner => _owner; + /// <summary> + /// Name of the signal. + /// </summary> public StringName Name => _signalName; + /// <summary> + /// Creates a new <see cref="Signal"/> with the name <paramref name="name"/> + /// in the specified <paramref name="owner"/>. + /// </summary> + /// <param name="owner">Object that contains the signal.</param> + /// <param name="name">Name of the signal.</param> public SignalInfo(Object owner, StringName name) { _owner = owner; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index 6ce148d51e..6b3eb09581 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -8,6 +8,9 @@ using System.Text.RegularExpressions; namespace Godot { + /// <summary> + /// Extension methods to manipulate strings. + /// </summary> public static class StringExtensions { private static int GetSliceCount(this string instance, string splitter) @@ -64,6 +67,11 @@ namespace Godot /// <summary> /// If the string is a path to a file, return the path to the file without the extension. /// </summary> + /// <seealso cref="GetExtension(string)"/> + /// <seealso cref="GetBaseDir(string)"/> + /// <seealso cref="GetFile(string)"/> + /// <param name="instance">The path to a file.</param> + /// <returns>The path to the file without the extension.</returns> public static string GetBaseName(this string instance) { int index = instance.LastIndexOf('.'); @@ -75,19 +83,25 @@ namespace Godot } /// <summary> - /// Return <see langword="true"/> if the strings begins with the given string. + /// Returns <see langword="true"/> if the strings begins + /// with the given string <paramref name="text"/>. /// </summary> + /// <param name="instance">The string to check.</param> + /// <param name="text">The beginning string.</param> + /// <returns>If the string begins with the given string.</returns> public static bool BeginsWith(this string instance, string text) { return instance.StartsWith(text); } /// <summary> - /// Return the bigrams (pairs of consecutive letters) of this string. + /// Returns the bigrams (pairs of consecutive letters) of this string. /// </summary> + /// <param name="instance">The string that will be used.</param> + /// <returns>The bigrams of this string.</returns> public static string[] Bigrams(this string instance) { - var b = new string[instance.Length - 1]; + string[] b = new string[instance.Length - 1]; for (int i = 0; i < b.Length; i++) { @@ -99,8 +113,8 @@ namespace Godot /// <summary> /// Converts a string containing a binary number into an integer. - /// Binary strings can either be prefixed with `0b` or not, - /// and they can also start with a `-` before the optional prefix. + /// Binary strings can either be prefixed with <c>0b</c> or not, + /// and they can also start with a <c>-</c> before the optional prefix. /// </summary> /// <param name="instance">The string to convert.</param> /// <returns>The converted string.</returns> @@ -128,8 +142,14 @@ namespace Godot } /// <summary> - /// Return the amount of substrings in string. + /// Returns the amount of substrings <paramref name="what"/> in the string. /// </summary> + /// <param name="instance">The string where the substring will be searched.</param> + /// <param name="what">The substring that will be counted.</param> + /// <param name="caseSensitive">If the search is case sensitive.</param> + /// <param name="from">Index to start searching from.</param> + /// <param name="to">Index to stop searching at.</param> + /// <returns>Amount of substrings in the string.</returns> public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0) { if (what.Length == 0) @@ -188,8 +208,10 @@ namespace Godot } /// <summary> - /// Return a copy of the string with special characters escaped using the C language standard. + /// Returns a copy of the string with special characters escaped using the C language standard. /// </summary> + /// <param name="instance">The string to escape.</param> + /// <returns>The escaped string.</returns> public static string CEscape(this string instance) { var sb = new StringBuilder(string.Copy(instance)); @@ -210,9 +232,11 @@ namespace Godot } /// <summary> - /// Return a copy of the string with escaped characters replaced by their meanings + /// Returns a copy of the string with escaped characters replaced by their meanings /// according to the C language standard. /// </summary> + /// <param name="instance">The string to unescape.</param> + /// <returns>The unescaped string.</returns> public static string CUnescape(this string instance) { var sb = new StringBuilder(string.Copy(instance)); @@ -233,15 +257,17 @@ namespace Godot } /// <summary> - /// Change the case of some letters. Replace underscores with spaces, convert all letters + /// Changes the case of some letters. Replace underscores with spaces, convert all letters /// to lowercase then capitalize first and every letter following the space character. /// For <c>capitalize camelCase mixed_with_underscores</c> it will return /// <c>Capitalize Camelcase Mixed With Underscores</c>. /// </summary> + /// <param name="instance">The string to capitalize.</param> + /// <returns>The capitalized string.</returns> public static string Capitalize(this string instance) { string aux = instance.Replace("_", " ").ToLower(); - var cap = string.Empty; + string cap = string.Empty; for (int i = 0; i < aux.GetSliceCount(" "); i++) { @@ -259,16 +285,27 @@ namespace Godot } /// <summary> - /// Perform a case-sensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. + /// Performs a case-sensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. /// </summary> + /// <seealso cref="NocasecmpTo(string, string)"/> + /// <seealso cref="CompareTo(string, string, bool)"/> + /// <param name="instance">The string to compare.</param> + /// <param name="to">The other string to compare.</param> + /// <returns>-1 if less, 0 if equal and +1 if greater.</returns> public static int CasecmpTo(this string instance, string to) { return instance.CompareTo(to, caseSensitive: true); } /// <summary> - /// Perform a comparison to another string, return -1 if less, 0 if equal and +1 if greater. + /// Performs a comparison to another string, return -1 if less, 0 if equal and +1 if greater. /// </summary> + /// <param name="instance">The string to compare.</param> + /// <param name="to">The other string to compare.</param> + /// <param name="caseSensitive"> + /// If <see langword="true"/>, the comparison will be case sensitive. + /// </param> + /// <returns>-1 if less, 0 if equal and +1 if greater.</returns> public static int CompareTo(this string instance, string to, bool caseSensitive = true) { if (string.IsNullOrEmpty(instance)) @@ -321,8 +358,12 @@ namespace Godot } /// <summary> - /// Return <see langword="true"/> if the strings ends with the given string. + /// Returns <see langword="true"/> if the strings ends + /// with the given string <paramref name="text"/>. /// </summary> + /// <param name="instance">The string to check.</param> + /// <param name="text">The ending string.</param> + /// <returns>If the string ends with the given string.</returns> public static bool EndsWith(this string instance, string text) { return instance.EndsWith(text); @@ -331,14 +372,36 @@ namespace Godot /// <summary> /// Erase <paramref name="chars"/> characters from the string starting from <paramref name="pos"/>. /// </summary> + /// <param name="instance">The string to modify.</param> + /// <param name="pos">Starting position from which to erase.</param> + /// <param name="chars">Amount of characters to erase.</param> public static void Erase(this StringBuilder instance, int pos, int chars) { instance.Remove(pos, chars); } /// <summary> - /// If the string is a path to a file, return the extension. - /// </summary> + /// Returns the extension without the leading period character (<c>.</c>) + /// if the string is a valid file name or path. If the string does not contain + /// an extension, returns an empty string instead. + /// </summary> + /// <example> + /// <code> + /// GD.Print("/path/to/file.txt".GetExtension()) // "txt" + /// GD.Print("file.txt".GetExtension()) // "txt" + /// GD.Print("file.sample.txt".GetExtension()) // "txt" + /// GD.Print(".txt".GetExtension()) // "txt" + /// GD.Print("file.txt.".GetExtension()) // "" (empty string) + /// GD.Print("file.txt..".GetExtension()) // "" (empty string) + /// GD.Print("txt".GetExtension()) // "" (empty string) + /// GD.Print("".GetExtension()) // "" (empty string) + /// </code> + /// </example> + /// <seealso cref="GetBaseName(string)"/> + /// <seealso cref="GetBaseDir(string)"/> + /// <seealso cref="GetFile(string)"/> + /// <param name="instance">The path to a file.</param> + /// <returns>The extension of the file or an empty string.</returns> public static string GetExtension(this string instance) { int pos = instance.FindLast("."); @@ -352,6 +415,10 @@ namespace Godot /// <summary> /// Find the first occurrence of a substring. Optionally, the search starting position can be passed. /// </summary> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to find.</param> + /// <param name="from">The search starting position.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int Find(this string instance, string what, int from = 0, bool caseSensitive = true) { @@ -361,6 +428,14 @@ namespace Godot /// <summary> /// Find the first occurrence of a char. Optionally, the search starting position can be passed. /// </summary> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="FindLast(string, string, bool)"/> + /// <seealso cref="FindLast(string, string, int, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to find.</param> + /// <param name="from">The search starting position.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> /// <returns>The first instance of the char, or -1 if not found.</returns> public static int Find(this string instance, char what, int from = 0, bool caseSensitive = true) { @@ -370,6 +445,13 @@ namespace Godot } /// <summary>Find the last occurrence of a substring.</summary> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindLast(string, string, int, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to find.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int FindLast(this string instance, string what, bool caseSensitive = true) { @@ -377,6 +459,14 @@ namespace Godot } /// <summary>Find the last occurrence of a substring specifying the search starting position.</summary> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindLast(string, string, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to find.</param> + /// <param name="from">The search starting position.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int FindLast(this string instance, string what, int from, bool caseSensitive = true) { @@ -387,6 +477,13 @@ namespace Godot /// Find the first occurrence of a substring but search as case-insensitive. /// Optionally, the search starting position can be passed. /// </summary> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindLast(string, string, bool)"/> + /// <seealso cref="FindLast(string, string, int, bool)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to find.</param> + /// <param name="from">The search starting position.</param> /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int FindN(this string instance, string what, int from = 0) { @@ -396,25 +493,30 @@ namespace Godot /// <summary> /// If the string is a path to a file, return the base directory. /// </summary> + /// <seealso cref="GetBaseName(string)"/> + /// <seealso cref="GetExtension(string)"/> + /// <seealso cref="GetFile(string)"/> + /// <param name="instance">The path to a file.</param> + /// <returns>The base directory.</returns> public static string GetBaseDir(this string instance) { int basepos = instance.Find("://"); string rs; - var @base = string.Empty; + string directory = string.Empty; if (basepos != -1) { - var end = basepos + 3; + int end = basepos + 3; rs = instance.Substring(end); - @base = instance.Substring(0, end); + directory = instance.Substring(0, end); } else { if (instance.BeginsWith("/")) { rs = instance.Substring(1); - @base = "/"; + directory = "/"; } else { @@ -425,14 +527,19 @@ namespace Godot int sep = Mathf.Max(rs.FindLast("/"), rs.FindLast("\\")); if (sep == -1) - return @base; + return directory; - return @base + rs.Substr(0, sep); + return directory + rs.Substr(0, sep); } /// <summary> /// If the string is a path to a file, return the file and ignore the base directory. /// </summary> + /// <seealso cref="GetBaseName(string)"/> + /// <seealso cref="GetExtension(string)"/> + /// <seealso cref="GetBaseDir(string)"/> + /// <param name="instance">The path to a file.</param> + /// <returns>The file name.</returns> public static string GetFile(this string instance) { int sep = Mathf.Max(instance.FindLast("/"), instance.FindLast("\\")); @@ -475,6 +582,8 @@ namespace Godot /// <summary> /// Hash the string and return a 32 bits unsigned integer. /// </summary> + /// <param name="instance">The string to hash.</param> + /// <returns>The calculated hash of the string.</returns> public static uint Hash(this string instance) { uint hash = 5381; @@ -494,7 +603,7 @@ namespace Godot /// <returns>The hexadecimal representation of this byte.</returns> internal static string HexEncode(this byte b) { - var ret = string.Empty; + string ret = string.Empty; for (int i = 0; i < 2; i++) { @@ -524,7 +633,7 @@ namespace Godot /// <returns>The hexadecimal representation of this byte array.</returns> public static string HexEncode(this byte[] bytes) { - var ret = string.Empty; + string ret = string.Empty; foreach (byte b in bytes) { @@ -536,8 +645,8 @@ namespace Godot /// <summary> /// Converts a string containing a hexadecimal number into an integer. - /// Hexadecimal strings can either be prefixed with `0x` or not, - /// and they can also start with a `-` before the optional prefix. + /// Hexadecimal strings can either be prefixed with <c>0x</c> or not, + /// and they can also start with a <c>-</c> before the optional prefix. /// </summary> /// <param name="instance">The string to convert.</param> /// <returns>The converted string.</returns> @@ -565,17 +674,29 @@ namespace Godot } /// <summary> - /// Insert a substring at a given position. + /// Inserts a substring at a given position. /// </summary> + /// <param name="instance">The string to modify.</param> + /// <param name="pos">Position at which to insert the substring.</param> + /// <param name="what">Substring to insert.</param> + /// <returns> + /// The string with <paramref name="what"/> inserted at the given + /// position <paramref name="pos"/>. + /// </returns> public static string Insert(this string instance, int pos, string what) { return instance.Insert(pos, what); } /// <summary> - /// If the string is a path to a file or directory, return <see langword="true"/> if the path is absolute. + /// Returns <see langword="true"/> if the string is a path to a file or + /// directory and its startign point is explicitly defined. This includes + /// <c>res://</c>, <c>user://</c>, <c>C:\</c>, <c>/</c>, etc. /// </summary> - public static bool IsAbsPath(this string instance) + /// <seealso cref="IsRelativePath(string)"/> + /// <param name="instance">The string to check.</param> + /// <returns>If the string is an absolute path.</returns> + public static bool IsAbsolutePath(this string instance) { if (string.IsNullOrEmpty(instance)) return false; @@ -586,16 +707,27 @@ namespace Godot } /// <summary> - /// If the string is a path to a file or directory, return <see langword="true"/> if the path is relative. + /// Returns <see langword="true"/> if the string is a path to a file or + /// directory and its starting point is implicitly defined within the + /// context it is being used. The starting point may refer to the current + /// directory (<c>./</c>), or the current <see cref="Node"/>. /// </summary> - public static bool IsRelPath(this string instance) + /// <seealso cref="IsAbsolutePath(string)"/> + /// <param name="instance">The string to check.</param> + /// <returns>If the string is a relative path.</returns> + public static bool IsRelativePath(this string instance) { - return !IsAbsPath(instance); + return !IsAbsolutePath(instance); } /// <summary> /// Check whether this string is a subsequence of the given string. /// </summary> + /// <seealso cref="IsSubsequenceOfI(string, string)"/> + /// <param name="instance">The subsequence to search.</param> + /// <param name="text">The string that contains the subsequence.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the check is case sensitive.</param> + /// <returns>If the string is a subsequence of the given string.</returns> public static bool IsSubsequenceOf(this string instance, string text, bool caseSensitive = true) { int len = instance.Length; @@ -639,6 +771,10 @@ namespace Godot /// <summary> /// Check whether this string is a subsequence of the given string, ignoring case differences. /// </summary> + /// <seealso cref="IsSubsequenceOf(string, string, bool)"/> + /// <param name="instance">The subsequence to search.</param> + /// <param name="text">The string that contains the subsequence.</param> + /// <returns>If the string is a subsequence of the given string.</returns> public static bool IsSubsequenceOfI(this string instance, string text) { return instance.IsSubsequenceOf(text, caseSensitive: false); @@ -647,6 +783,8 @@ namespace Godot /// <summary> /// Check whether the string contains a valid <see langword="float"/>. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid floating point number.</returns> public static bool IsValidFloat(this string instance) { float f; @@ -656,6 +794,8 @@ namespace Godot /// <summary> /// Check whether the string contains a valid color in HTML notation. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid HTML color.</returns> public static bool IsValidHtmlColor(this string instance) { return Color.HtmlIsValid(instance); @@ -666,6 +806,8 @@ namespace Godot /// programming languages, a valid identifier may contain only letters, /// digits and underscores (_) and the first character may not be a digit. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid identifier.</returns> public static bool IsValidIdentifier(this string instance) { int len = instance.Length; @@ -673,18 +815,15 @@ namespace Godot if (len == 0) return false; + if (instance[0] >= '0' && instance[0] <= '9') + return false; // Identifiers cannot start with numbers. + for (int i = 0; i < len; i++) { - if (i == 0) - { - if (instance[0] >= '0' && instance[0] <= '9') - return false; // Don't start with number plz - } - - bool validChar = instance[i] >= '0' && - instance[i] <= '9' || instance[i] >= 'a' && - instance[i] <= 'z' || instance[i] >= 'A' && - instance[i] <= 'Z' || instance[i] == '_'; + bool validChar = instance[i] == '_' || + (instance[i] >= 'a' && instance[i] <= 'z') || + (instance[i] >= 'A' && instance[i] <= 'Z') || + (instance[i] >= '0' && instance[i] <= '9'); if (!validChar) return false; @@ -696,6 +835,8 @@ namespace Godot /// <summary> /// Check whether the string contains a valid integer. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid integer.</returns> public static bool IsValidInteger(this string instance) { int f; @@ -705,6 +846,8 @@ namespace Godot /// <summary> /// Check whether the string contains a valid IP address. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid IP address.</returns> public static bool IsValidIPAddress(this string instance) { // TODO: Support IPv6 addresses @@ -728,8 +871,10 @@ namespace Godot } /// <summary> - /// Return a copy of the string with special characters escaped using the JSON standard. + /// Returns a copy of the string with special characters escaped using the JSON standard. /// </summary> + /// <param name="instance">The string to escape.</param> + /// <returns>The escaped string.</returns> public static string JSONEscape(this string instance) { var sb = new StringBuilder(string.Copy(instance)); @@ -747,8 +892,12 @@ namespace Godot } /// <summary> - /// Return an amount of characters from the left of the string. + /// Returns an amount of characters from the left of the string. /// </summary> + /// <seealso cref="Right(string, int)"/> + /// <param name="instance">The original string.</param> + /// <param name="pos">The position in the string where the left side ends.</param> + /// <returns>The left side of the string from the given position.</returns> public static string Left(this string instance, int pos) { if (pos <= 0) @@ -761,8 +910,10 @@ namespace Godot } /// <summary> - /// Return the length of the string in characters. + /// Returns the length of the string in characters. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>The length of the string.</returns> public static int Length(this string instance) { return instance.Length; @@ -771,6 +922,7 @@ namespace Godot /// <summary> /// Returns a copy of the string with characters removed from the left. /// </summary> + /// <seealso cref="RStrip(string, string)"/> /// <param name="instance">The string to remove characters from.</param> /// <param name="chars">The characters to be removed.</param> /// <returns>A copy of the string with characters removed from the left.</returns> @@ -799,6 +951,12 @@ namespace Godot /// Do a simple expression match, where '*' matches zero or more /// arbitrary characters and '?' matches any single character except '.'. /// </summary> + /// <param name="instance">The string to check.</param> + /// <param name="expr">Expression to check.</param> + /// <param name="caseSensitive"> + /// If <see langword="true"/>, the check will be case sensitive. + /// </param> + /// <returns>If the expression has any matches.</returns> private static bool ExprMatch(this string instance, string expr, bool caseSensitive) { // case '\0': @@ -824,6 +982,13 @@ namespace Godot /// Do a simple case sensitive expression match, using ? and * wildcards /// (see <see cref="ExprMatch(string, string, bool)"/>). /// </summary> + /// <seealso cref="MatchN(string, string)"/> + /// <param name="instance">The string to check.</param> + /// <param name="expr">Expression to check.</param> + /// <param name="caseSensitive"> + /// If <see langword="true"/>, the check will be case sensitive. + /// </param> + /// <returns>If the expression has any matches.</returns> public static bool Match(this string instance, string expr, bool caseSensitive = true) { if (instance.Length == 0 || expr.Length == 0) @@ -836,6 +1001,10 @@ namespace Godot /// Do a simple case insensitive expression match, using ? and * wildcards /// (see <see cref="ExprMatch(string, string, bool)"/>). /// </summary> + /// <seealso cref="Match(string, string, bool)"/> + /// <param name="instance">The string to check.</param> + /// <param name="expr">Expression to check.</param> + /// <returns>If the expression has any matches.</returns> public static bool MatchN(this string instance, string expr) { if (instance.Length == 0 || expr.Length == 0) @@ -845,46 +1014,65 @@ namespace Godot } /// <summary> - /// Return the MD5 hash of the string as an array of bytes. + /// Returns the MD5 hash of the string as an array of bytes. /// </summary> + /// <seealso cref="MD5Text(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The MD5 hash of the string.</returns> public static byte[] MD5Buffer(this string instance) { return godot_icall_String_md5_buffer(instance); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static byte[] godot_icall_String_md5_buffer(string str); + internal static extern byte[] godot_icall_String_md5_buffer(string str); /// <summary> - /// Return the MD5 hash of the string as a string. + /// Returns the MD5 hash of the string as a string. /// </summary> + /// <seealso cref="MD5Buffer(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The MD5 hash of the string.</returns> public static string MD5Text(this string instance) { return godot_icall_String_md5_text(instance); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_String_md5_text(string str); + internal static extern string godot_icall_String_md5_text(string str); /// <summary> /// Perform a case-insensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. /// </summary> + /// <seealso cref="CasecmpTo(string, string)"/> + /// <seealso cref="CompareTo(string, string, bool)"/> + /// <param name="instance">The string to compare.</param> + /// <param name="to">The other string to compare.</param> + /// <returns>-1 if less, 0 if equal and +1 if greater.</returns> public static int NocasecmpTo(this string instance, string to) { return instance.CompareTo(to, caseSensitive: false); } /// <summary> - /// Return the character code at position <paramref name="at"/>. + /// Returns the character code at position <paramref name="at"/>. /// </summary> + /// <param name="instance">The string to check.</param> + /// <param name="at">The position int the string for the character to check.</param> + /// <returns>The character code.</returns> public static int OrdAt(this string instance, int at) { return instance[at]; } /// <summary> - /// Format a number to have an exact number of <paramref name="digits"/> after the decimal point. + /// Format a number to have an exact number of <paramref name="digits"/> + /// after the decimal point. /// </summary> + /// <seealso cref="PadZeros(string, int)"/> + /// <param name="instance">The string to pad.</param> + /// <param name="digits">Amount of digits after the decimal point.</param> + /// <returns>The string padded with zeroes.</returns> public static string PadDecimals(this string instance, int digits) { int c = instance.Find("."); @@ -919,8 +1107,13 @@ namespace Godot } /// <summary> - /// Format a number to have an exact number of <paramref name="digits"/> before the decimal point. + /// Format a number to have an exact number of <paramref name="digits"/> + /// before the decimal point. /// </summary> + /// <seealso cref="PadDecimals(string, int)"/> + /// <param name="instance">The string to pad.</param> + /// <param name="digits">Amount of digits before the decimal point.</param> + /// <returns>The string padded with zeroes.</returns> public static string PadZeros(this string instance, int digits) { string s = instance; @@ -952,9 +1145,13 @@ namespace Godot } /// <summary> - /// If the string is a path, this concatenates <paramref name="file"/> at the end of the string as a subpath. + /// If the string is a path, this concatenates <paramref name="file"/> + /// at the end of the string as a subpath. /// E.g. <c>"this/is".PlusFile("path") == "this/is/path"</c>. /// </summary> + /// <param name="instance">The path that will be concatenated.</param> + /// <param name="file">File name to concatenate with the path.</param> + /// <returns>The concatenated path with the given file name.</returns> public static string PlusFile(this string instance, string file) { if (instance.Length > 0 && instance[instance.Length - 1] == '/') @@ -965,6 +1162,11 @@ namespace Godot /// <summary> /// Replace occurrences of a substring for different ones inside the string. /// </summary> + /// <seealso cref="ReplaceN(string, string, string)"/> + /// <param name="instance">The string to modify.</param> + /// <param name="what">The substring to be replaced in the string.</param> + /// <param name="forwhat">The substring that replaces <paramref name="what"/>.</param> + /// <returns>The string with the substring occurrences replaced.</returns> public static string Replace(this string instance, string what, string forwhat) { return instance.Replace(what, forwhat); @@ -973,6 +1175,11 @@ namespace Godot /// <summary> /// Replace occurrences of a substring for different ones inside the string, but search case-insensitive. /// </summary> + /// <seealso cref="Replace(string, string, string)"/> + /// <param name="instance">The string to modify.</param> + /// <param name="what">The substring to be replaced in the string.</param> + /// <param name="forwhat">The substring that replaces <paramref name="what"/>.</param> + /// <returns>The string with the substring occurrences replaced.</returns> public static string ReplaceN(this string instance, string what, string forwhat) { return Regex.Replace(instance, what, forwhat, RegexOptions.IgnoreCase); @@ -981,29 +1188,43 @@ namespace Godot /// <summary> /// Perform a search for a substring, but start from the end of the string instead of the beginning. /// </summary> + /// <seealso cref="RFindN(string, string, int)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to search in the string.</param> + /// <param name="from">The position at which to start searching.</param> + /// <returns>The position at which the substring was found, or -1 if not found.</returns> public static int RFind(this string instance, string what, int from = -1) { return godot_icall_String_rfind(instance, what, from); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_String_rfind(string str, string what, int from); + internal static extern int godot_icall_String_rfind(string str, string what, int from); /// <summary> /// Perform a search for a substring, but start from the end of the string instead of the beginning. /// Also search case-insensitive. /// </summary> + /// <seealso cref="RFind(string, string, int)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to search in the string.</param> + /// <param name="from">The position at which to start searching.</param> + /// <returns>The position at which the substring was found, or -1 if not found.</returns> public static int RFindN(this string instance, string what, int from = -1) { return godot_icall_String_rfindn(instance, what, from); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_String_rfindn(string str, string what, int from); + internal static extern int godot_icall_String_rfindn(string str, string what, int from); /// <summary> - /// Return the right side of the string from a given position. + /// Returns the right side of the string from a given position. /// </summary> + /// <seealso cref="Left(string, int)"/> + /// <param name="instance">The original string.</param> + /// <param name="pos">The position in the string from which the right side starts.</param> + /// <returns>The right side of the string from the given position.</returns> public static string Right(this string instance, int pos) { if (pos >= instance.Length) @@ -1018,6 +1239,7 @@ namespace Godot /// <summary> /// Returns a copy of the string with characters removed from the right. /// </summary> + /// <seealso cref="LStrip(string, string)"/> /// <param name="instance">The string to remove characters from.</param> /// <param name="chars">The characters to be removed.</param> /// <returns>A copy of the string with characters removed from the right.</returns> @@ -1042,29 +1264,41 @@ namespace Godot return instance.Substr(0, end + 1); } + /// <summary> + /// Returns the SHA-256 hash of the string as an array of bytes. + /// </summary> + /// <seealso cref="SHA256Text(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The SHA-256 hash of the string.</returns> public static byte[] SHA256Buffer(this string instance) { return godot_icall_String_sha256_buffer(instance); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static byte[] godot_icall_String_sha256_buffer(string str); + internal static extern byte[] godot_icall_String_sha256_buffer(string str); /// <summary> - /// Return the SHA-256 hash of the string as a string. + /// Returns the SHA-256 hash of the string as a string. /// </summary> + /// <seealso cref="SHA256Buffer(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The SHA-256 hash of the string.</returns> public static string SHA256Text(this string instance) { return godot_icall_String_sha256_text(instance); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_String_sha256_text(string str); + internal static extern string godot_icall_String_sha256_text(string str); /// <summary> - /// Return the similarity index of the text compared to this string. + /// Returns the similarity index of the text compared to this string. /// 1 means totally similar and 0 means totally dissimilar. /// </summary> + /// <param name="instance">The string to compare.</param> + /// <param name="text">The other string to compare.</param> + /// <returns>The similarity index.</returns> public static float Similarity(this string instance, string text) { if (instance == text) @@ -1103,9 +1337,27 @@ namespace Godot } /// <summary> + /// Returns a simplified canonical path. + /// </summary> + public static string SimplifyPath(this string instance) + { + return godot_icall_String_simplify_path(instance); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static string godot_icall_String_simplify_path(string str); + + /// <summary> /// Split the string by a divisor string, return an array of the substrings. /// Example "One,Two,Three" will return ["One","Two","Three"] if split by ",". /// </summary> + /// <seealso cref="SplitFloats(string, string, bool)"/> + /// <param name="instance">The string to split.</param> + /// <param name="divisor">The divisor string that splits the string.</param> + /// <param name="allowEmpty"> + /// If <see langword="true"/>, the array may include empty strings. + /// </param> + /// <returns>The array of strings split from the string.</returns> public static string[] Split(this string instance, string divisor, bool allowEmpty = true) { return instance.Split(new[] { divisor }, allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries); @@ -1115,6 +1367,13 @@ namespace Godot /// Split the string in floats by using a divisor string, return an array of the substrings. /// Example "1,2.5,3" will return [1,2.5,3] if split by ",". /// </summary> + /// <seealso cref="Split(string, string, bool)"/> + /// <param name="instance">The string to split.</param> + /// <param name="divisor">The divisor string that splits the string.</param> + /// <param name="allowEmpty"> + /// If <see langword="true"/>, the array may include empty floats. + /// </param> + /// <returns>The array of floats split from the string.</returns> public static float[] SplitFloats(this string instance, string divisor, bool allowEmpty = true) { var ret = new List<float>(); @@ -1137,7 +1396,8 @@ namespace Godot return ret.ToArray(); } - private static readonly char[] _nonPrintable = { + private static readonly char[] _nonPrintable = + { (char)00, (char)01, (char)02, (char)03, (char)04, (char)05, (char)06, (char)07, (char)08, (char)09, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, @@ -1147,9 +1407,13 @@ namespace Godot }; /// <summary> - /// Return a copy of the string stripped of any non-printable character at the beginning and the end. + /// Returns a copy of the string stripped of any non-printable character at the beginning and the end. /// The optional arguments are used to toggle stripping on the left and right edges respectively. /// </summary> + /// <param name="instance">The string to strip.</param> + /// <param name="left">If the left side should be stripped.</param> + /// <param name="right">If the right side should be stripped.</param> + /// <returns>The string stripped of any non-printable characters.</returns> public static string StripEdges(this string instance, bool left = true, bool right = true) { if (left) @@ -1163,8 +1427,14 @@ namespace Godot } /// <summary> - /// Return part of the string from the position <paramref name="from"/>, with length <paramref name="len"/>. + /// Returns part of the string from the position <paramref name="from"/>, with length <paramref name="len"/>. /// </summary> + /// <param name="instance">The string to slice.</param> + /// <param name="from">The position in the string that the part starts from.</param> + /// <param name="len">The length of the returned part.</param> + /// <returns> + /// Part of the string from the position <paramref name="from"/>, with length <paramref name="len"/>. + /// </returns> public static string Substr(this string instance, int from, int len) { int max = instance.Length - from; @@ -1172,52 +1442,70 @@ namespace Godot } /// <summary> - /// Convert the String (which is a character array) to PackedByteArray (which is an array of bytes). + /// Converts the String (which is a character array) to PackedByteArray (which is an array of bytes). /// The conversion is speeded up in comparison to <see cref="ToUTF8(string)"/> with the assumption /// that all the characters the String contains are only ASCII characters. /// </summary> + /// <seealso cref="ToUTF8(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The string as ASCII encoded bytes.</returns> public static byte[] ToAscii(this string instance) { return Encoding.ASCII.GetBytes(instance); } /// <summary> - /// Convert a string, containing a decimal number, into a <see langword="float" />. + /// Converts a string, containing a decimal number, into a <see langword="float" />. /// </summary> + /// <seealso cref="ToInt(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The number representation of the string.</returns> public static float ToFloat(this string instance) { return float.Parse(instance); } /// <summary> - /// Convert a string, containing an integer number, into an <see langword="int" />. + /// Converts a string, containing an integer number, into an <see langword="int" />. /// </summary> + /// <seealso cref="ToFloat(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The number representation of the string.</returns> public static int ToInt(this string instance) { return int.Parse(instance); } /// <summary> - /// Return the string converted to lowercase. + /// Returns the string converted to lowercase. /// </summary> + /// <seealso cref="ToUpper(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The string converted to lowercase.</returns> public static string ToLower(this string instance) { return instance.ToLower(); } /// <summary> - /// Return the string converted to uppercase. + /// Returns the string converted to uppercase. /// </summary> + /// <seealso cref="ToLower(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The string converted to uppercase.</returns> public static string ToUpper(this string instance) { return instance.ToUpper(); } /// <summary> - /// Convert the String (which is an array of characters) to PackedByteArray (which is an array of bytes). + /// Converts the String (which is an array of characters) to PackedByteArray (which is an array of bytes). /// The conversion is a bit slower than <see cref="ToAscii(string)"/>, but supports all UTF-8 characters. /// Therefore, you should prefer this function over <see cref="ToAscii(string)"/>. /// </summary> + /// <seealso cref="ToAscii(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The string as UTF-8 encoded bytes.</returns> public static byte[] ToUTF8(this string instance) { return Encoding.UTF8.GetBytes(instance); @@ -1226,8 +1514,8 @@ namespace Godot /// <summary> /// Decodes a string in URL encoded format. This is meant to /// decode parameters in a URL when receiving an HTTP request. - /// This mostly wraps around `System.Uri.UnescapeDataString()`, - /// but also handles `+`. + /// This mostly wraps around <see cref="Uri.UnescapeDataString"/>, + /// but also handles <c>+</c>. /// See <see cref="URIEncode"/> for encoding. /// </summary> /// <param name="instance">The string to decode.</param> @@ -1240,7 +1528,7 @@ namespace Godot /// <summary> /// Encodes a string to URL friendly format. This is meant to /// encode parameters in a URL when sending an HTTP request. - /// This wraps around `System.Uri.EscapeDataString()`. + /// This wraps around <see cref="Uri.EscapeDataString"/>. /// See <see cref="URIDecode"/> for decoding. /// </summary> /// <param name="instance">The string to encode.</param> @@ -1251,17 +1539,23 @@ namespace Godot } /// <summary> - /// Return a copy of the string with special characters escaped using the XML standard. + /// Returns a copy of the string with special characters escaped using the XML standard. /// </summary> + /// <seealso cref="XMLUnescape(string)"/> + /// <param name="instance">The string to escape.</param> + /// <returns>The escaped string.</returns> public static string XMLEscape(this string instance) { return SecurityElement.Escape(instance); } /// <summary> - /// Return a copy of the string with escaped characters replaced by their meanings + /// Returns a copy of the string with escaped characters replaced by their meanings /// according to the XML standard. /// </summary> + /// <seealso cref="XMLEscape(string)"/> + /// <param name="instance">The string to unescape.</param> + /// <returns>The unescaped string.</returns> public static string XMLUnescape(this string instance) { return SecurityElement.FromString(instance).Text; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs index 7700b6d4ed..b1d504410b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs @@ -3,6 +3,13 @@ using System.Runtime.CompilerServices; namespace Godot { + /// <summary> + /// StringNames are immutable strings designed for general-purpose representation of unique names. + /// StringName ensures that only one instance of a given name exists (so two StringNames with the + /// same value are the same object). + /// Comparing them is much faster than with regular strings, because only the pointers are compared, + /// not the whole strings. + /// </summary> public sealed partial class StringName : IDisposable { private IntPtr ptr; @@ -23,6 +30,9 @@ namespace Godot Dispose(false); } + /// <summary> + /// Disposes of this <see cref="StringName"/>. + /// </summary> public void Dispose() { Dispose(true); @@ -43,25 +53,48 @@ namespace Godot this.ptr = ptr; } + /// <summary> + /// Constructs an empty <see cref="StringName"/>. + /// </summary> public StringName() { ptr = IntPtr.Zero; } + /// <summary> + /// Constructs a <see cref="StringName"/> from the given <paramref name="path"/> string. + /// </summary> + /// <param name="path">String to construct the <see cref="StringName"/> from.</param> public StringName(string path) { ptr = path == null ? IntPtr.Zero : godot_icall_StringName_Ctor(path); } + /// <summary> + /// Converts a string to a <see cref="StringName"/>. + /// </summary> + /// <param name="from">The string to convert.</param> public static implicit operator StringName(string from) => new StringName(from); + /// <summary> + /// Converts a <see cref="StringName"/> to a string. + /// </summary> + /// <param name="from">The <see cref="StringName"/> to convert.</param> public static implicit operator string(StringName from) => from.ToString(); + /// <summary> + /// Converts this <see cref="StringName"/> to a string. + /// </summary> + /// <returns>A string representation of this <see cref="StringName"/>.</returns> public override string ToString() { return ptr == IntPtr.Zero ? string.Empty : godot_icall_StringName_operator_String(GetPtr(this)); } + /// <summary> + /// Check whether this <see cref="StringName"/> is empty. + /// </summary> + /// <returns>If the <see cref="StringName"/> is empty.</returns> public bool IsEmpty() { return ptr == IntPtr.Zero || godot_icall_StringName_is_empty(GetPtr(this)); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index 62a6fe6959..daea09ba72 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -21,18 +21,18 @@ namespace Godot public struct Transform2D : IEquatable<Transform2D> { /// <summary> - /// The basis matrix's X vector (column 0). Equivalent to array index `[0]`. + /// The basis matrix's X vector (column 0). Equivalent to array index <c>[0]</c>. /// </summary> /// <value></value> public Vector2 x; /// <summary> - /// The basis matrix's Y vector (column 1). Equivalent to array index `[1]`. + /// The basis matrix's Y vector (column 1). Equivalent to array index <c>[1]</c>. /// </summary> public Vector2 y; /// <summary> - /// The origin vector (column 2, the third column). Equivalent to array index `[2]`. + /// The origin vector (column 2, the third column). Equivalent to array index <c>[2]</c>. /// The origin vector represents translation. /// </summary> public Vector2 origin; @@ -77,7 +77,8 @@ namespace Godot } /// <summary> - /// Access whole columns in the form of Vector2. The third column is the origin vector. + /// Access whole columns in the form of <see cref="Vector2"/>. + /// The third column is the <see cref="origin"/> vector. /// </summary> /// <param name="column">Which column vector.</param> public Vector2 this[int column] @@ -116,7 +117,8 @@ namespace Godot } /// <summary> - /// Access matrix elements in column-major order. The third column is the origin vector. + /// Access matrix elements in column-major order. + /// The third column is the <see cref="origin"/> vector. /// </summary> /// <param name="column">Which column, the matrix horizontal position.</param> /// <param name="row">Which row, the matrix vertical position.</param> @@ -138,6 +140,7 @@ namespace Godot /// Returns the inverse of the transform, under the assumption that /// the transformation is composed of rotation, scaling, and translation. /// </summary> + /// <seealso cref="Inverse"/> /// <returns>The inverse transformation matrix.</returns> public Transform2D AffineInverse() { @@ -146,7 +149,7 @@ namespace Godot if (det == 0) throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted."); - var inv = this; + Transform2D inv = this; real_t temp = inv[0, 0]; inv[0, 0] = inv[1, 1]; @@ -173,13 +176,14 @@ namespace Godot /// <returns>The determinant of the basis matrix.</returns> private real_t BasisDeterminant() { - return x.x * y.y - x.y * y.x; + return (x.x * y.y) - (x.y * y.x); } /// <summary> /// Returns a vector transformed (multiplied) by the basis matrix. - /// This method does not account for translation (the origin vector). + /// This method does not account for translation (the <see cref="origin"/> vector). /// </summary> + /// <seealso cref="BasisXformInv(Vector2)"/> /// <param name="v">A vector to transform.</param> /// <returns>The transformed vector.</returns> public Vector2 BasisXform(Vector2 v) @@ -189,11 +193,12 @@ namespace Godot /// <summary> /// Returns a vector transformed (multiplied) by the inverse basis matrix. - /// This method does not account for translation (the origin vector). + /// This method does not account for translation (the <see cref="origin"/> vector). /// /// Note: This results in a multiplication by the inverse of the /// basis matrix only if it represents a rotation-reflection. /// </summary> + /// <seealso cref="BasisXform(Vector2)"/> /// <param name="v">A vector to inversely transform.</param> /// <returns>The inversely transformed vector.</returns> public Vector2 BasisXformInv(Vector2 v) @@ -202,7 +207,7 @@ namespace Godot } /// <summary> - /// Interpolates this transform to the other `transform` by `weight`. + /// Interpolates this transform to the other <paramref name="transform"/> by <paramref name="weight"/>. /// </summary> /// <param name="transform">The other transform.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> @@ -233,8 +238,8 @@ namespace Godot else { real_t angle = weight * Mathf.Acos(dot); - Vector2 v3 = (v2 - v1 * dot).Normalized(); - v = v1 * Mathf.Cos(angle) + v3 * Mathf.Sin(angle); + Vector2 v3 = (v2 - (v1 * dot)).Normalized(); + v = (v1 * Mathf.Cos(angle)) + (v3 * Mathf.Sin(angle)); } // Extract parameters @@ -258,7 +263,7 @@ namespace Godot /// <returns>The inverse matrix.</returns> public Transform2D Inverse() { - var inv = this; + Transform2D inv = this; // Swap real_t temp = inv.x.y; @@ -277,13 +282,13 @@ namespace Godot /// <returns>The orthonormalized transform.</returns> public Transform2D Orthonormalized() { - var on = this; + Transform2D on = this; Vector2 onX = on.x; Vector2 onY = on.y; onX.Normalize(); - onY = onY - onX * onX.Dot(onY); + onY = onY - (onX * onX.Dot(onY)); onY.Normalize(); on.x = onX; @@ -293,7 +298,7 @@ namespace Godot } /// <summary> - /// Rotates the transform by `phi` (in radians), using matrix multiplication. + /// Rotates the transform by <paramref name="phi"/> (in radians), using matrix multiplication. /// </summary> /// <param name="phi">The angle to rotate, in radians.</param> /// <returns>The rotated transformation matrix.</returns> @@ -309,7 +314,7 @@ namespace Godot /// <returns>The scaled transformation matrix.</returns> public Transform2D Scaled(Vector2 scale) { - var copy = this; + Transform2D copy = this; copy.x *= scale; copy.y *= scale; copy.origin *= scale; @@ -326,16 +331,16 @@ namespace Godot private real_t Tdotx(Vector2 with) { - return this[0, 0] * with[0] + this[1, 0] * with[1]; + return (this[0, 0] * with[0]) + (this[1, 0] * with[1]); } private real_t Tdoty(Vector2 with) { - return this[0, 1] * with[0] + this[1, 1] * with[1]; + return (this[0, 1] * with[0]) + (this[1, 1] * with[1]); } /// <summary> - /// Translates the transform by the given `offset`, + /// Translates the transform by the given <paramref name="offset"/>, /// relative to the transform's basis vectors. /// /// Unlike <see cref="Rotated"/> and <see cref="Scaled"/>, @@ -345,7 +350,7 @@ namespace Godot /// <returns>The translated matrix.</returns> public Transform2D Translated(Vector2 offset) { - var copy = this; + Transform2D copy = this; copy.origin += copy.BasisXform(offset); return copy; } @@ -353,6 +358,7 @@ namespace Godot /// <summary> /// Returns a vector transformed (multiplied) by this transformation matrix. /// </summary> + /// <seealso cref="XformInv(Vector2)"/> /// <param name="v">A vector to transform.</param> /// <returns>The transformed vector.</returns> public Vector2 Xform(Vector2 v) @@ -363,6 +369,7 @@ namespace Godot /// <summary> /// Returns a vector transformed (multiplied) by the inverse transformation matrix. /// </summary> + /// <seealso cref="Xform(Vector2)"/> /// <param name="v">A vector to inversely transform.</param> /// <returns>The inversely transformed vector.</returns> public Vector2 XformInv(Vector2 v) @@ -378,20 +385,20 @@ namespace Godot /// <summary> /// The identity transform, with no translation, rotation, or scaling applied. - /// This is used as a replacement for `Transform2D()` in GDScript. - /// Do not use `new Transform2D()` with no arguments in C#, because it sets all values to zero. + /// This is used as a replacement for <c>Transform2D()</c> in GDScript. + /// Do not use <c>new Transform2D()</c> with no arguments in C#, because it sets all values to zero. /// </summary> - /// <value>Equivalent to `new Transform2D(Vector2.Right, Vector2.Down, Vector2.Zero)`.</value> + /// <value>Equivalent to <c>new Transform2D(Vector2.Right, Vector2.Down, Vector2.Zero)</c>.</value> public static Transform2D Identity { get { return _identity; } } /// <summary> /// The transform that will flip something along the X axis. /// </summary> - /// <value>Equivalent to `new Transform2D(Vector2.Left, Vector2.Down, Vector2.Zero)`.</value> + /// <value>Equivalent to <c>new Transform2D(Vector2.Left, Vector2.Down, Vector2.Zero)</c>.</value> public static Transform2D FlipX { get { return _flipX; } } /// <summary> /// The transform that will flip something along the Y axis. /// </summary> - /// <value>Equivalent to `new Transform2D(Vector2.Right, Vector2.Up, Vector2.Zero)`.</value> + /// <value>Equivalent to <c>new Transform2D(Vector2.Right, Vector2.Up, Vector2.Zero)</c>.</value> public static Transform2D FlipY { get { return _flipY; } } /// <summary> @@ -411,12 +418,12 @@ namespace Godot /// Constructs a transformation matrix from the given components. /// Arguments are named such that xy is equal to calling x.y /// </summary> - /// <param name="xx">The X component of the X column vector, accessed via `t.x.x` or `[0][0]`</param> - /// <param name="xy">The Y component of the X column vector, accessed via `t.x.y` or `[0][1]`</param> - /// <param name="yx">The X component of the Y column vector, accessed via `t.y.x` or `[1][0]`</param> - /// <param name="yy">The Y component of the Y column vector, accessed via `t.y.y` or `[1][1]`</param> - /// <param name="ox">The X component of the origin vector, accessed via `t.origin.x` or `[2][0]`</param> - /// <param name="oy">The Y component of the origin vector, accessed via `t.origin.y` or `[2][1]`</param> + /// <param name="xx">The X component of the X column vector, accessed via <c>t.x.x</c> or <c>[0][0]</c></param> + /// <param name="xy">The Y component of the X column vector, accessed via <c>t.x.y</c> or <c>[0][1]</c></param> + /// <param name="yx">The X component of the Y column vector, accessed via <c>t.y.x</c> or <c>[1][0]</c></param> + /// <param name="yy">The Y component of the Y column vector, accessed via <c>t.y.y</c> or <c>[1][1]</c></param> + /// <param name="ox">The X component of the origin vector, accessed via <c>t.origin.x</c> or <c>[2][0]</c></param> + /// <param name="oy">The Y component of the origin vector, accessed via <c>t.origin.y</c> or <c>[2][1]</c></param> public Transform2D(real_t xx, real_t xy, real_t yx, real_t yy, real_t ox, real_t oy) { x = new Vector2(xx, xy); @@ -425,16 +432,17 @@ namespace Godot } /// <summary> - /// Constructs a transformation matrix from a rotation value and origin vector. + /// Constructs a transformation matrix from a <paramref name="rotation"/> value and + /// <paramref name="origin"/> vector. /// </summary> - /// <param name="rot">The rotation of the new transform, in radians.</param> - /// <param name="pos">The origin vector, or column index 2.</param> - public Transform2D(real_t rot, Vector2 pos) + /// <param name="rotation">The rotation of the new transform, in radians.</param> + /// <param name="origin">The origin vector, or column index 2.</param> + public Transform2D(real_t rotation, Vector2 origin) { - x.x = y.y = Mathf.Cos(rot); - x.y = y.x = Mathf.Sin(rot); + x.x = y.y = Mathf.Cos(rotation); + x.y = y.x = Mathf.Sin(rotation); y.x *= -1; - origin = pos; + this.origin = origin; } public static Transform2D operator *(Transform2D left, Transform2D right) @@ -464,19 +472,29 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this transform and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the transform and the other object are equal.</returns> public override bool Equals(object obj) { return obj is Transform2D transform2D && Equals(transform2D); } + /// <summary> + /// Returns <see langword="true"/> if this transform and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other transform to compare.</param> + /// <returns>Whether or not the matrices are equal.</returns> public bool Equals(Transform2D other) { return x.Equals(other.x) && y.Equals(other.y) && origin.Equals(other.origin); } /// <summary> - /// Returns true if this transform and `other` are approximately equal, by running - /// <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component. + /// Returns <see langword="true"/> if this transform and <paramref name="other"/> are approximately equal, + /// by running <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component. /// </summary> /// <param name="other">The other transform to compare.</param> /// <returns>Whether or not the matrices are approximately equal.</returns> @@ -485,16 +503,28 @@ namespace Godot return x.IsEqualApprox(other.x) && y.IsEqualApprox(other.y) && origin.IsEqualApprox(other.origin); } + /// <summary> + /// Serves as the hash function for <see cref="Transform2D"/>. + /// </summary> + /// <returns>A hash code for this transform.</returns> public override int GetHashCode() { return x.GetHashCode() ^ y.GetHashCode() ^ origin.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Transform2D"/> to a string. + /// </summary> + /// <returns>A string representation of this transform.</returns> public override string ToString() { return $"[X: {x}, Y: {y}, O: {origin}]"; } + /// <summary> + /// Converts this <see cref="Transform2D"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this transform.</returns> public string ToString(string format) { return $"[X: {x.ToString(format)}, Y: {y.ToString(format)}, O: {origin.ToString(format)}]"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index afc6a65a45..7176cd60dc 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -28,12 +28,13 @@ namespace Godot public Basis basis; /// <summary> - /// The origin vector (column 3, the fourth column). Equivalent to array index `[3]`. + /// The origin vector (column 3, the fourth column). Equivalent to array index <c>[3]</c>. /// </summary> public Vector3 origin; /// <summary> - /// Access whole columns in the form of Vector3. The fourth column is the origin vector. + /// Access whole columns in the form of <see cref="Vector3"/>. + /// The fourth column is the <see cref="origin"/> vector. /// </summary> /// <param name="column">Which column vector.</param> public Vector3 this[int column] @@ -77,7 +78,8 @@ namespace Godot } /// <summary> - /// Access matrix elements in column-major order. The fourth column is the origin vector. + /// Access matrix elements in column-major order. + /// The fourth column is the <see cref="origin"/> vector. /// </summary> /// <param name="column">Which column, the matrix horizontal position.</param> /// <param name="row">Which row, the matrix vertical position.</param> @@ -106,6 +108,7 @@ namespace Godot /// Returns the inverse of the transform, under the assumption that /// the transformation is composed of rotation, scaling, and translation. /// </summary> + /// <seealso cref="Inverse"/> /// <returns>The inverse transformation matrix.</returns> public Transform3D AffineInverse() { @@ -114,7 +117,7 @@ namespace Godot } /// <summary> - /// Interpolates this transform to the other `transform` by `weight`. + /// Interpolates this transform to the other <paramref name="transform"/> by <paramref name="weight"/>. /// </summary> /// <param name="transform">The other transform.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> @@ -132,7 +135,9 @@ namespace Godot Vector3 destinationLocation = transform.origin; var interpolated = new Transform3D(); - interpolated.basis.SetQuaternionScale(sourceRotation.Slerp(destinationRotation, weight).Normalized(), sourceScale.Lerp(destinationScale, weight)); + Quaternion quaternion = sourceRotation.Slerp(destinationRotation, weight).Normalized(); + Vector3 scale = sourceScale.Lerp(destinationScale, weight); + interpolated.basis.SetQuaternionScale(quaternion, scale); interpolated.origin = sourceLocation.Lerp(destinationLocation, weight); return interpolated; @@ -152,11 +157,11 @@ namespace Godot /// <summary> /// Returns a copy of the transform rotated such that its - /// -Z axis (forward) points towards the target position. + /// -Z axis (forward) points towards the <paramref name="target"/> position. /// - /// The transform will first be rotated around the given up vector, - /// and then fully aligned to the target by a further rotation around - /// an axis perpendicular to both the target and up vectors. + /// The transform will first be rotated around the given <paramref name="up"/> vector, + /// and then fully aligned to the <paramref name="target"/> by a further rotation around + /// an axis perpendicular to both the <paramref name="target"/> and <paramref name="up"/> vectors. /// /// Operations take place in global space. /// </summary> @@ -165,7 +170,7 @@ namespace Godot /// <returns>The resulting transform.</returns> public Transform3D LookingAt(Vector3 target, Vector3 up) { - var t = this; + Transform3D t = this; t.SetLookAt(origin, target, up); return t; } @@ -181,7 +186,7 @@ namespace Godot } /// <summary> - /// Rotates the transform around the given `axis` by `phi` (in radians), + /// Rotates the transform around the given <paramref name="axis"/> by <paramref name="phi"/> (in radians), /// using matrix multiplication. The axis must be a normalized vector. /// </summary> /// <param name="axis">The axis to rotate around. Must be normalized.</param> @@ -226,7 +231,7 @@ namespace Godot } /// <summary> - /// Translates the transform by the given `offset`, + /// Translates the transform by the given <paramref name="offset"/>, /// relative to the transform's basis vectors. /// /// Unlike <see cref="Rotated"/> and <see cref="Scaled"/>, @@ -247,6 +252,7 @@ namespace Godot /// <summary> /// Returns a vector transformed (multiplied) by this transformation matrix. /// </summary> + /// <seealso cref="XformInv(Vector3)"/> /// <param name="v">A vector to transform.</param> /// <returns>The transformed vector.</returns> public Vector3 Xform(Vector3 v) @@ -265,6 +271,7 @@ namespace Godot /// Note: This results in a multiplication by the inverse of the /// transformation matrix only if it represents a rotation-reflection. /// </summary> + /// <seealso cref="Xform(Vector3)"/> /// <param name="v">A vector to inversely transform.</param> /// <returns>The inversely transformed vector.</returns> public Vector3 XformInv(Vector3 v) @@ -273,9 +280,9 @@ namespace Godot return new Vector3 ( - basis.Row0[0] * vInv.x + basis.Row1[0] * vInv.y + basis.Row2[0] * vInv.z, - basis.Row0[1] * vInv.x + basis.Row1[1] * vInv.y + basis.Row2[1] * vInv.z, - basis.Row0[2] * vInv.x + basis.Row1[2] * vInv.y + basis.Row2[2] * vInv.z + (basis.Row0[0] * vInv.x) + (basis.Row1[0] * vInv.y) + (basis.Row2[0] * vInv.z), + (basis.Row0[1] * vInv.x) + (basis.Row1[1] * vInv.y) + (basis.Row2[1] * vInv.z), + (basis.Row0[2] * vInv.x) + (basis.Row1[2] * vInv.y) + (basis.Row2[2] * vInv.z) ); } @@ -287,25 +294,25 @@ namespace Godot /// <summary> /// The identity transform, with no translation, rotation, or scaling applied. - /// This is used as a replacement for `Transform()` in GDScript. - /// Do not use `new Transform()` with no arguments in C#, because it sets all values to zero. + /// This is used as a replacement for <c>Transform()</c> in GDScript. + /// Do not use <c>new Transform()</c> with no arguments in C#, because it sets all values to zero. /// </summary> - /// <value>Equivalent to `new Transform(Vector3.Right, Vector3.Up, Vector3.Back, Vector3.Zero)`.</value> + /// <value>Equivalent to <c>new Transform(Vector3.Right, Vector3.Up, Vector3.Back, Vector3.Zero)</c>.</value> public static Transform3D Identity { get { return _identity; } } /// <summary> /// The transform that will flip something along the X axis. /// </summary> - /// <value>Equivalent to `new Transform(Vector3.Left, Vector3.Up, Vector3.Back, Vector3.Zero)`.</value> + /// <value>Equivalent to <c>new Transform(Vector3.Left, Vector3.Up, Vector3.Back, Vector3.Zero)</c>.</value> public static Transform3D FlipX { get { return _flipX; } } /// <summary> /// The transform that will flip something along the Y axis. /// </summary> - /// <value>Equivalent to `new Transform(Vector3.Right, Vector3.Down, Vector3.Back, Vector3.Zero)`.</value> + /// <value>Equivalent to <c>new Transform(Vector3.Right, Vector3.Down, Vector3.Back, Vector3.Zero)</c>.</value> public static Transform3D FlipY { get { return _flipY; } } /// <summary> /// The transform that will flip something along the Z axis. /// </summary> - /// <value>Equivalent to `new Transform(Vector3.Right, Vector3.Up, Vector3.Forward, Vector3.Zero)`.</value> + /// <value>Equivalent to <c>new Transform(Vector3.Right, Vector3.Up, Vector3.Forward, Vector3.Zero)</c>.</value> public static Transform3D FlipZ { get { return _flipZ; } } /// <summary> @@ -322,9 +329,10 @@ namespace Godot } /// <summary> - /// Constructs a transformation matrix from the given quaternion and origin vector. + /// Constructs a transformation matrix from the given <paramref name="quaternion"/> + /// and <paramref name="origin"/> vector. /// </summary> - /// <param name="quaternion">The <see cref="Godot.Quaternion"/> to create the basis from.</param> + /// <param name="quaternion">The <see cref="Quaternion"/> to create the basis from.</param> /// <param name="origin">The origin vector, or column index 3.</param> public Transform3D(Quaternion quaternion, Vector3 origin) { @@ -333,9 +341,10 @@ namespace Godot } /// <summary> - /// Constructs a transformation matrix from the given basis and origin vector. + /// Constructs a transformation matrix from the given <paramref name="basis"/> and + /// <paramref name="origin"/> vector. /// </summary> - /// <param name="basis">The <see cref="Godot.Basis"/> to create the basis from.</param> + /// <param name="basis">The <see cref="Basis"/> to create the basis from.</param> /// <param name="origin">The origin vector, or column index 3.</param> public Transform3D(Basis basis, Vector3 origin) { @@ -360,6 +369,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this transform and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the transform and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Transform3D) @@ -370,14 +384,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this transform and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other transform to compare.</param> + /// <returns>Whether or not the matrices are equal.</returns> public bool Equals(Transform3D other) { return basis.Equals(other.basis) && origin.Equals(other.origin); } /// <summary> - /// Returns true if this transform and `other` are approximately equal, by running - /// <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component. + /// Returns <see langword="true"/> if this transform and <paramref name="other"/> are approximately equal, + /// by running <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component. /// </summary> /// <param name="other">The other transform to compare.</param> /// <returns>Whether or not the matrices are approximately equal.</returns> @@ -386,16 +405,28 @@ namespace Godot return basis.IsEqualApprox(other.basis) && origin.IsEqualApprox(other.origin); } + /// <summary> + /// Serves as the hash function for <see cref="Transform3D"/>. + /// </summary> + /// <returns>A hash code for this transform.</returns> public override int GetHashCode() { return basis.GetHashCode() ^ origin.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Transform3D"/> to a string. + /// </summary> + /// <returns>A string representation of this transform.</returns> public override string ToString() { return $"[X: {basis.x}, Y: {basis.y}, Z: {basis.z}, O: {origin}]"; } + /// <summary> + /// Converts this <see cref="Transform3D"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this transform.</returns> public string ToString(string format) { return $"[X: {basis.x.ToString(format)}, Y: {basis.y.ToString(format)}, Z: {basis.z.ToString(format)}, O: {origin.ToString(format)}]"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs index be01674568..eae8927ceb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs @@ -8,7 +8,7 @@ namespace Godot public class UnhandledExceptionArgs { /// <summary> - /// Exception object + /// Exception object. /// </summary> public Exception Exception { get; private set; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 8bb5e90a68..fe70d71cce 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -21,23 +21,36 @@ namespace Godot /// </summary> public enum Axis { + /// <summary> + /// The vector's X axis. + /// </summary> X = 0, + /// <summary> + /// The vector's Y axis. + /// </summary> Y } /// <summary> - /// The vector's X component. Also accessible by using the index position `[0]`. + /// The vector's X component. Also accessible by using the index position <c>[0]</c>. /// </summary> public real_t x; + /// <summary> - /// The vector's Y component. Also accessible by using the index position `[1]`. + /// The vector's Y component. Also accessible by using the index position <c>[1]</c>. /// </summary> public real_t y; /// <summary> /// Access vector components using their index. /// </summary> - /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`.</value> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the given the <paramref name="index"/> is not 0 or 1. + /// </exception> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="x"/>, + /// <c>[1]</c> is equivalent to <see cref="y"/>. + /// </value> public real_t this[int index] { get @@ -97,7 +110,7 @@ namespace Godot /// Returns this vector's angle with respect to the X axis, or (1, 0) vector, in radians. /// /// Equivalent to the result of <see cref="Mathf.Atan2(real_t, real_t)"/> when - /// called with the vector's `y` and `x` as parameters: `Mathf.Atan2(v.y, v.x)`. + /// called with the vector's <see cref="y"/> and <see cref="x"/> as parameters: <c>Mathf.Atan2(v.y, v.x)</c>. /// </summary> /// <returns>The angle of this vector, in radians.</returns> public real_t Angle() @@ -126,9 +139,9 @@ namespace Godot } /// <summary> - /// Returns the aspect ratio of this vector, the ratio of `x` to `y`. + /// Returns the aspect ratio of this vector, the ratio of <see cref="x"/> to <see cref="y"/>. /// </summary> - /// <returns>The `x` component divided by the `y` component.</returns> + /// <returns>The <see cref="x"/> component divided by the <see cref="y"/> component.</returns> public real_t Aspect() { return x / y; @@ -155,7 +168,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components clamped between the - /// components of `min` and `max` using + /// components of <paramref name="min"/> and <paramref name="max"/> using /// <see cref="Mathf.Clamp(real_t, real_t, real_t)"/>. /// </summary> /// <param name="min">The vector with minimum allowed values.</param> @@ -171,21 +184,22 @@ namespace Godot } /// <summary> - /// Returns the cross product of this vector and `b`. + /// Returns the cross product of this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector.</param> /// <returns>The cross product value.</returns> public real_t Cross(Vector2 b) { - return x * b.y - y * b.x; + return (x * b.y) - (y * b.x); } /// <summary> - /// Performs a cubic interpolation between vectors `preA`, this vector, `b`, and `postB`, by the given amount `t`. + /// Performs a cubic interpolation between vectors <paramref name="preA"/>, this vector, + /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. /// </summary> /// <param name="b">The destination vector.</param> /// <param name="preA">A vector before this vector.</param> - /// <param name="postB">A vector after `b`.</param> + /// <param name="postB">A vector after <paramref name="b"/>.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated vector.</returns> public Vector2 CubicInterpolate(Vector2 b, Vector2 preA, Vector2 postB, real_t weight) @@ -199,24 +213,26 @@ namespace Godot real_t t2 = t * t; real_t t3 = t2 * t; - return 0.5f * (p1 * 2.0f + - (-p0 + p2) * t + - (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 + - (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); + return 0.5f * ( + (p1 * 2.0f) + + ((-p0 + p2) * t) + + (((2.0f * p0) - (5.0f * p1) + (4 * p2) - p3) * t2) + + ((-p0 + (3.0f * p1) - (3.0f * p2) + p3) * t3) + ); } /// <summary> - /// Returns the normalized vector pointing from this vector to `b`. + /// Returns the normalized vector pointing from this vector to <paramref name="b"/>. /// </summary> /// <param name="b">The other vector to point towards.</param> - /// <returns>The direction from this vector to `b`.</returns> + /// <returns>The direction from this vector to <paramref name="b"/>.</returns> public Vector2 DirectionTo(Vector2 b) { return new Vector2(b.x - x, b.y - y).Normalized(); } /// <summary> - /// Returns the squared distance between this vector and `to`. + /// Returns the squared distance between this vector and <paramref name="to"/>. /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if /// you need to compare vectors or need the squared distance for some formula. /// </summary> @@ -228,7 +244,7 @@ namespace Godot } /// <summary> - /// Returns the distance between this vector and `to`. + /// Returns the distance between this vector and <paramref name="to"/>. /// </summary> /// <param name="to">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> @@ -238,13 +254,13 @@ namespace Godot } /// <summary> - /// Returns the dot product of this vector and `with`. + /// Returns the dot product of this vector and <paramref name="with"/>. /// </summary> /// <param name="with">The other vector to use.</param> /// <returns>The dot product of the two vectors.</returns> public real_t Dot(Vector2 with) { - return x * with.x + y * with.y; + return (x * with.x) + (y * with.y); } /// <summary> @@ -257,7 +273,7 @@ namespace Godot } /// <summary> - /// Returns the inverse of this vector. This is the same as `new Vector2(1 / v.x, 1 / v.y)`. + /// Returns the inverse of this vector. This is the same as <c>new Vector2(1 / v.x, 1 / v.y)</c>. /// </summary> /// <returns>The inverse of this vector.</returns> public Vector2 Inverse() @@ -266,9 +282,9 @@ namespace Godot } /// <summary> - /// Returns true if the vector is normalized, and false otherwise. + /// Returns <see langword="true"/> if the vector is normalized, and <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool indicating whether or not the vector is normalized.</returns> + /// <returns>A <see langword="bool"/> indicating whether or not the vector is normalized.</returns> public bool IsNormalized() { return Mathf.Abs(LengthSquared() - 1.0f) < Mathf.Epsilon; @@ -277,10 +293,11 @@ namespace Godot /// <summary> /// Returns the length (magnitude) of this vector. /// </summary> + /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> public real_t Length() { - return Mathf.Sqrt(x * x + y * y); + return Mathf.Sqrt((x * x) + (y * y)); } /// <summary> @@ -291,12 +308,12 @@ namespace Godot /// <returns>The squared length of this vector.</returns> public real_t LengthSquared() { - return x * x + y * y; + return (x * x) + (y * y); } /// <summary> /// Returns the result of the linear interpolation between - /// this vector and `to` by amount `weight`. + /// this vector and <paramref name="to"/> by amount <paramref name="weight"/>. /// </summary> /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> @@ -312,10 +329,12 @@ namespace Godot /// <summary> /// Returns the result of the linear interpolation between - /// this vector and `to` by the vector amount `weight`. + /// this vector and <paramref name="to"/> by the vector amount <paramref name="weight"/>. /// </summary> /// <param name="to">The destination vector for interpolation.</param> - /// <param name="weight">A vector with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <param name="weight"> + /// A vector with components on the range of 0.0 to 1.0, representing the amount of interpolation. + /// </param> /// <returns>The resulting vector of the interpolation.</returns> public Vector2 Lerp(Vector2 to, Vector2 weight) { @@ -327,7 +346,7 @@ namespace Godot } /// <summary> - /// Returns the vector with a maximum length by limiting its length to `length`. + /// Returns the vector with a maximum length by limiting its length to <paramref name="length"/>. /// </summary> /// <param name="length">The length to limit to.</param> /// <returns>The vector with its length limited.</returns> @@ -366,35 +385,41 @@ namespace Godot } /// <summary> - /// Moves this vector toward `to` by the fixed `delta` amount. + /// Moves this vector toward <paramref name="to"/> by the fixed <paramref name="delta"/> amount. /// </summary> /// <param name="to">The vector to move towards.</param> /// <param name="delta">The amount to move towards by.</param> /// <returns>The resulting vector.</returns> public Vector2 MoveToward(Vector2 to, real_t delta) { - var v = this; - var vd = to - v; - var len = vd.Length(); - return len <= delta || len < Mathf.Epsilon ? to : v + vd / len * delta; + Vector2 v = this; + Vector2 vd = to - v; + real_t len = vd.Length(); + if (len <= delta || len < Mathf.Epsilon) + return to; + + return v + (vd / len * delta); } /// <summary> - /// Returns the vector scaled to unit length. Equivalent to `v / v.Length()`. + /// Returns the vector scaled to unit length. Equivalent to <c>v / v.Length()</c>. /// </summary> /// <returns>A normalized version of the vector.</returns> public Vector2 Normalized() { - var v = this; + Vector2 v = this; v.Normalize(); return v; } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `mod`. + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components + /// and <paramref name="mod"/>. /// </summary> /// <param name="mod">A value representing the divisor of the operation.</param> - /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `mod`.</returns> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="mod"/>. + /// </returns> public Vector2 PosMod(real_t mod) { Vector2 v; @@ -404,10 +429,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `modv`'s components. + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components + /// and <paramref name="modv"/>'s components. /// </summary> /// <param name="modv">A vector representing the divisors of the operation.</param> - /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `modv`'s components.</returns> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="modv"/>'s components. + /// </returns> public Vector2 PosMod(Vector2 modv) { Vector2 v; @@ -417,7 +445,7 @@ namespace Godot } /// <summary> - /// Returns this vector projected onto another vector `b`. + /// Returns this vector projected onto another vector <paramref name="onNormal"/>. /// </summary> /// <param name="onNormal">The vector to project onto.</param> /// <returns>The projected vector.</returns> @@ -427,7 +455,7 @@ namespace Godot } /// <summary> - /// Returns this vector reflected from a plane defined by the given `normal`. + /// Returns this vector reflected from a plane defined by the given <paramref name="normal"/>. /// </summary> /// <param name="normal">The normal vector defining the plane to reflect from. Must be normalized.</param> /// <returns>The reflected vector.</returns> @@ -439,11 +467,11 @@ namespace Godot throw new ArgumentException("Argument is not normalized", nameof(normal)); } #endif - return 2 * Dot(normal) * normal - this; + return (2 * Dot(normal) * normal) - this; } /// <summary> - /// Rotates this vector by `phi` radians. + /// Rotates this vector by <paramref name="phi"/> radians. /// </summary> /// <param name="phi">The angle to rotate by, in radians.</param> /// <returns>The rotated vector.</returns> @@ -471,7 +499,7 @@ namespace Godot /// on the signs of this vector's components, or zero if the component is zero, /// by calling <see cref="Mathf.Sign(real_t)"/> on each component. /// </summary> - /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns> + /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public Vector2 Sign() { Vector2 v; @@ -482,7 +510,7 @@ namespace Godot /// <summary> /// Returns the result of the spherical linear interpolation between - /// this vector and `to` by amount `weight`. + /// this vector and <paramref name="to"/> by amount <paramref name="weight"/>. /// /// Note: Both vectors must be normalized. /// </summary> @@ -498,24 +526,24 @@ namespace Godot } if (!to.IsNormalized()) { - throw new InvalidOperationException("Vector2.Slerp: `to` is not normalized."); + throw new InvalidOperationException($"Vector2.Slerp: `{nameof(to)}` is not normalized."); } #endif return Rotated(AngleTo(to) * weight); } /// <summary> - /// Returns this vector slid along a plane defined by the given normal. + /// Returns this vector slid along a plane defined by the given <paramref name="normal"/>. /// </summary> /// <param name="normal">The normal vector defining the plane to slide on.</param> /// <returns>The slid vector.</returns> public Vector2 Slide(Vector2 normal) { - return this - normal * Dot(normal); + return this - (normal * Dot(normal)); } /// <summary> - /// Returns this vector with each component snapped to the nearest multiple of `step`. + /// Returns this vector with each component snapped to the nearest multiple of <paramref name="step"/>. /// This can also be used to round to an arbitrary number of decimals. /// </summary> /// <param name="step">A vector value representing the step size to snap to.</param> @@ -546,40 +574,40 @@ namespace Godot private static readonly Vector2 _left = new Vector2(-1, 0); /// <summary> - /// Zero vector, a vector with all components set to `0`. + /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> - /// <value>Equivalent to `new Vector2(0, 0)`</value> + /// <value>Equivalent to <c>new Vector2(0, 0)</c>.</value> public static Vector2 Zero { get { return _zero; } } /// <summary> - /// One vector, a vector with all components set to `1`. + /// One vector, a vector with all components set to <c>1</c>. /// </summary> - /// <value>Equivalent to `new Vector2(1, 1)`</value> + /// <value>Equivalent to <c>new Vector2(1, 1)</c>.</value> public static Vector2 One { get { return _one; } } /// <summary> - /// Infinity vector, a vector with all components set to `Mathf.Inf`. + /// Infinity vector, a vector with all components set to <see cref="Mathf.Inf"/>. /// </summary> - /// <value>Equivalent to `new Vector2(Mathf.Inf, Mathf.Inf)`</value> + /// <value>Equivalent to <c>new Vector2(Mathf.Inf, Mathf.Inf)</c>.</value> public static Vector2 Inf { get { return _inf; } } /// <summary> /// Up unit vector. Y is down in 2D, so this vector points -Y. /// </summary> - /// <value>Equivalent to `new Vector2(0, -1)`</value> + /// <value>Equivalent to <c>new Vector2(0, -1)</c>.</value> public static Vector2 Up { get { return _up; } } /// <summary> /// Down unit vector. Y is down in 2D, so this vector points +Y. /// </summary> - /// <value>Equivalent to `new Vector2(0, 1)`</value> + /// <value>Equivalent to <c>new Vector2(0, 1)</c>.</value> public static Vector2 Down { get { return _down; } } /// <summary> /// Right unit vector. Represents the direction of right. /// </summary> - /// <value>Equivalent to `new Vector2(1, 0)`</value> + /// <value>Equivalent to <c>new Vector2(1, 0)</c>.</value> public static Vector2 Right { get { return _right; } } /// <summary> /// Left unit vector. Represents the direction of left. /// </summary> - /// <value>Equivalent to `new Vector2(-1, 0)`</value> + /// <value>Equivalent to <c>new Vector2(-1, 0)</c>.</value> public static Vector2 Left { get { return _left; } } /// <summary> @@ -603,6 +631,17 @@ namespace Godot y = v.y; } + /// <summary> + /// Creates a unit Vector2 rotated to the given angle. This is equivalent to doing + /// <c>Vector2(Mathf.Cos(angle), Mathf.Sin(angle))</c> or <c>Vector2.Right.Rotated(angle)</c>. + /// </summary> + /// <param name="angle">Angle of the vector, in radians.</param> + /// <returns>The resulting vector.</returns> + public static Vector2 FromAngle(real_t angle) + { + return new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)); + } + public static Vector2 operator +(Vector2 left, Vector2 right) { left.x += right.x; @@ -719,6 +758,11 @@ namespace Godot return left.x >= right.x; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the vector and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Vector2) @@ -728,14 +772,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other vector to compare.</param> + /// <returns>Whether or not the vectors are equal.</returns> public bool Equals(Vector2 other) { return x == other.x && y == other.y; } /// <summary> - /// Returns true if this vector and `other` are approximately equal, by running - /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. + /// Returns <see langword="true"/> if this vector and <paramref name="other"/> are approximately equal, + /// by running <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. /// </summary> /// <param name="other">The other vector to compare.</param> /// <returns>Whether or not the vectors are approximately equal.</returns> @@ -744,16 +793,28 @@ namespace Godot return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y); } + /// <summary> + /// Serves as the hash function for <see cref="Vector2"/>. + /// </summary> + /// <returns>A hash code for this vector.</returns> public override int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Vector2"/> to a string. + /// </summary> + /// <returns>A string representation of this vector.</returns> public override string ToString() { return $"({x}, {y})"; } + /// <summary> + /// Converts this <see cref="Vector2"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this vector.</returns> public string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)})"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs index 959f262f52..ca4531d885 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs @@ -21,23 +21,36 @@ namespace Godot /// </summary> public enum Axis { + /// <summary> + /// The vector's X axis. + /// </summary> X = 0, + /// <summary> + /// The vector's Y axis. + /// </summary> Y } /// <summary> - /// The vector's X component. Also accessible by using the index position `[0]`. + /// The vector's X component. Also accessible by using the index position <c>[0]</c>. /// </summary> public int x; + /// <summary> - /// The vector's Y component. Also accessible by using the index position `[1]`. + /// The vector's Y component. Also accessible by using the index position <c>[1]</c>. /// </summary> public int y; /// <summary> /// Access vector components using their index. /// </summary> - /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`.</value> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the given the <paramref name="index"/> is not 0 or 1. + /// </exception> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="x"/>, + /// <c>[1]</c> is equivalent to <see cref="y"/>. + /// </value> public int this[int index] { get @@ -81,7 +94,7 @@ namespace Godot /// Returns this vector's angle with respect to the X axis, or (1, 0) vector, in radians. /// /// Equivalent to the result of <see cref="Mathf.Atan2(real_t, real_t)"/> when - /// called with the vector's `y` and `x` as parameters: `Mathf.Atan2(v.y, v.x)`. + /// called with the vector's <see cref="y"/> and <see cref="x"/> as parameters: <c>Mathf.Atan2(v.y, v.x)</c>. /// </summary> /// <returns>The angle of this vector, in radians.</returns> public real_t Angle() @@ -110,9 +123,9 @@ namespace Godot } /// <summary> - /// Returns the aspect ratio of this vector, the ratio of `x` to `y`. + /// Returns the aspect ratio of this vector, the ratio of <see cref="x"/> to <see cref="y"/>. /// </summary> - /// <returns>The `x` component divided by the `y` component.</returns> + /// <returns>The <see cref="x"/> component divided by the <see cref="y"/> component.</returns> public real_t Aspect() { return x / (real_t)y; @@ -120,7 +133,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components clamped between the - /// components of `min` and `max` using + /// components of <paramref name="min"/> and <paramref name="max"/> using /// <see cref="Mathf.Clamp(int, int, int)"/>. /// </summary> /// <param name="min">The vector with minimum allowed values.</param> @@ -136,7 +149,7 @@ namespace Godot } /// <summary> - /// Returns the cross product of this vector and `b`. + /// Returns the cross product of this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector.</param> /// <returns>The cross product vector.</returns> @@ -146,7 +159,7 @@ namespace Godot } /// <summary> - /// Returns the squared distance between this vector and `b`. + /// Returns the squared distance between this vector and <paramref name="b"/>. /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if /// you need to compare vectors or need the squared distance for some formula. /// </summary> @@ -158,7 +171,7 @@ namespace Godot } /// <summary> - /// Returns the distance between this vector and `b`. + /// Returns the distance between this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> @@ -168,7 +181,7 @@ namespace Godot } /// <summary> - /// Returns the dot product of this vector and `b`. + /// Returns the dot product of this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector to use.</param> /// <returns>The dot product of the two vectors.</returns> @@ -180,6 +193,7 @@ namespace Godot /// <summary> /// Returns the length (magnitude) of this vector. /// </summary> + /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> public real_t Length() { @@ -224,10 +238,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `mod`. + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components + /// and <paramref name="mod"/>. /// </summary> /// <param name="mod">A value representing the divisor of the operation.</param> - /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `mod`.</returns> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="mod"/>. + /// </returns> public Vector2i PosMod(int mod) { Vector2i v = this; @@ -237,10 +254,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `modv`'s components. + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components + /// and <paramref name="modv"/>'s components. /// </summary> /// <param name="modv">A vector representing the divisors of the operation.</param> - /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `modv`'s components.</returns> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="modv"/>'s components. + /// </returns> public Vector2i PosMod(Vector2i modv) { Vector2i v = this; @@ -254,7 +274,7 @@ namespace Godot /// on the signs of this vector's components, or zero if the component is zero, /// by calling <see cref="Mathf.Sign(int)"/> on each component. /// </summary> - /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns> + /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public Vector2i Sign() { Vector2i v = this; @@ -283,35 +303,35 @@ namespace Godot private static readonly Vector2i _left = new Vector2i(-1, 0); /// <summary> - /// Zero vector, a vector with all components set to `0`. + /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> - /// <value>Equivalent to `new Vector2i(0, 0)`</value> + /// <value>Equivalent to <c>new Vector2i(0, 0)</c>.</value> public static Vector2i Zero { get { return _zero; } } /// <summary> - /// One vector, a vector with all components set to `1`. + /// One vector, a vector with all components set to <c>1</c>. /// </summary> - /// <value>Equivalent to `new Vector2i(1, 1)`</value> + /// <value>Equivalent to <c>new Vector2i(1, 1)</c>.</value> public static Vector2i One { get { return _one; } } /// <summary> /// Up unit vector. Y is down in 2D, so this vector points -Y. /// </summary> - /// <value>Equivalent to `new Vector2i(0, -1)`</value> + /// <value>Equivalent to <c>new Vector2i(0, -1)</c>.</value> public static Vector2i Up { get { return _up; } } /// <summary> /// Down unit vector. Y is down in 2D, so this vector points +Y. /// </summary> - /// <value>Equivalent to `new Vector2i(0, 1)`</value> + /// <value>Equivalent to <c>new Vector2i(0, 1)</c>.</value> public static Vector2i Down { get { return _down; } } /// <summary> /// Right unit vector. Represents the direction of right. /// </summary> - /// <value>Equivalent to `new Vector2i(1, 0)`</value> + /// <value>Equivalent to <c>new Vector2i(1, 0)</c>.</value> public static Vector2i Right { get { return _right; } } /// <summary> /// Left unit vector. Represents the direction of left. /// </summary> - /// <value>Equivalent to `new Vector2i(-1, 0)`</value> + /// <value>Equivalent to <c>new Vector2i(-1, 0)</c>.</value> public static Vector2i Left { get { return _left; } } /// <summary> @@ -476,16 +496,29 @@ namespace Godot return left.x >= right.x; } + /// <summary> + /// Converts this <see cref="Vector2i"/> to a <see cref="Vector2"/>. + /// </summary> + /// <param name="value">The vector to convert.</param> public static implicit operator Vector2(Vector2i value) { return new Vector2(value.x, value.y); } + /// <summary> + /// Converts a <see cref="Vector2"/> to a <see cref="Vector2i"/>. + /// </summary> + /// <param name="value">The vector to convert.</param> public static explicit operator Vector2i(Vector2 value) { return new Vector2i(value); } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the vector and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Vector2i) @@ -496,21 +529,38 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other vector to compare.</param> + /// <returns>Whether or not the vectors are equal.</returns> public bool Equals(Vector2i other) { return x == other.x && y == other.y; } + /// <summary> + /// Serves as the hash function for <see cref="Vector2i"/>. + /// </summary> + /// <returns>A hash code for this vector.</returns> public override int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Vector2i"/> to a string. + /// </summary> + /// <returns>A string representation of this vector.</returns> public override string ToString() { return $"({x}, {y})"; } + /// <summary> + /// Converts this <see cref="Vector2i"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this vector.</returns> public string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)})"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index bdf64159e9..01e3a71bcb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -21,28 +21,46 @@ namespace Godot /// </summary> public enum Axis { + /// <summary> + /// The vector's X axis. + /// </summary> X = 0, + /// <summary> + /// The vector's Y axis. + /// </summary> Y, + /// <summary> + /// The vector's Z axis. + /// </summary> Z } /// <summary> - /// The vector's X component. Also accessible by using the index position `[0]`. + /// The vector's X component. Also accessible by using the index position <c>[0]</c>. /// </summary> public real_t x; + /// <summary> - /// The vector's Y component. Also accessible by using the index position `[1]`. + /// The vector's Y component. Also accessible by using the index position <c>[1]</c>. /// </summary> public real_t y; + /// <summary> - /// The vector's Z component. Also accessible by using the index position `[2]`. + /// The vector's Z component. Also accessible by using the index position <c>[2]</c>. /// </summary> public real_t z; /// <summary> /// Access vector components using their index. /// </summary> - /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`, `[2]` is equivalent to `.z`.</value> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the given the <paramref name="index"/> is not 0, 1 or 2. + /// </exception> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="x"/>, + /// <c>[1]</c> is equivalent to <see cref="y"/>, + /// <c>[2]</c> is equivalent to <see cref="z"/>. + /// </value> public real_t this[int index] { get @@ -135,7 +153,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components clamped between the - /// components of `min` and `max` using + /// components of <paramref name="min"/> and <paramref name="max"/> using /// <see cref="Mathf.Clamp(real_t, real_t, real_t)"/>. /// </summary> /// <param name="min">The vector with minimum allowed values.</param> @@ -152,7 +170,7 @@ namespace Godot } /// <summary> - /// Returns the cross product of this vector and `b`. + /// Returns the cross product of this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector.</param> /// <returns>The cross product vector.</returns> @@ -160,19 +178,19 @@ namespace Godot { return new Vector3 ( - y * b.z - z * b.y, - z * b.x - x * b.z, - x * b.y - y * b.x + (y * b.z) - (z * b.y), + (z * b.x) - (x * b.z), + (x * b.y) - (y * b.x) ); } /// <summary> - /// Performs a cubic interpolation between vectors `preA`, this vector, - /// `b`, and `postB`, by the given amount `t`. + /// Performs a cubic interpolation between vectors <paramref name="preA"/>, this vector, + /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. /// </summary> /// <param name="b">The destination vector.</param> /// <param name="preA">A vector before this vector.</param> - /// <param name="postB">A vector after `b`.</param> + /// <param name="postB">A vector after <paramref name="b"/>.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated vector.</returns> public Vector3 CubicInterpolate(Vector3 b, Vector3 preA, Vector3 postB, real_t weight) @@ -187,24 +205,24 @@ namespace Godot real_t t3 = t2 * t; return 0.5f * ( - p1 * 2.0f + (-p0 + p2) * t + - (2.0f * p0 - 5.0f * p1 + 4f * p2 - p3) * t2 + - (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3 - ); + (p1 * 2.0f) + ((-p0 + p2) * t) + + (((2.0f * p0) - (5.0f * p1) + (4f * p2) - p3) * t2) + + ((-p0 + (3.0f * p1) - (3.0f * p2) + p3) * t3) + ); } /// <summary> - /// Returns the normalized vector pointing from this vector to `b`. + /// Returns the normalized vector pointing from this vector to <paramref name="b"/>. /// </summary> /// <param name="b">The other vector to point towards.</param> - /// <returns>The direction from this vector to `b`.</returns> + /// <returns>The direction from this vector to <paramref name="b"/>.</returns> public Vector3 DirectionTo(Vector3 b) { return new Vector3(b.x - x, b.y - y, b.z - z).Normalized(); } /// <summary> - /// Returns the squared distance between this vector and `b`. + /// Returns the squared distance between this vector and <paramref name="b"/>. /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if /// you need to compare vectors or need the squared distance for some formula. /// </summary> @@ -216,8 +234,9 @@ namespace Godot } /// <summary> - /// Returns the distance between this vector and `b`. + /// Returns the distance between this vector and <paramref name="b"/>. /// </summary> + /// <seealso cref="DistanceSquaredTo(Vector3)"/> /// <param name="b">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> public real_t DistanceTo(Vector3 b) @@ -226,13 +245,13 @@ namespace Godot } /// <summary> - /// Returns the dot product of this vector and `b`. + /// Returns the dot product of this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector to use.</param> /// <returns>The dot product of the two vectors.</returns> public real_t Dot(Vector3 b) { - return x * b.x + y * b.y + z * b.z; + return (x * b.x) + (y * b.y) + (z * b.z); } /// <summary> @@ -245,7 +264,7 @@ namespace Godot } /// <summary> - /// Returns the inverse of this vector. This is the same as `new Vector3(1 / v.x, 1 / v.y, 1 / v.z)`. + /// Returns the inverse of this vector. This is the same as <c>new Vector3(1 / v.x, 1 / v.y, 1 / v.z)</c>. /// </summary> /// <returns>The inverse of this vector.</returns> public Vector3 Inverse() @@ -254,9 +273,9 @@ namespace Godot } /// <summary> - /// Returns true if the vector is normalized, and false otherwise. + /// Returns <see langword="true"/> if the vector is normalized, and <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool indicating whether or not the vector is normalized.</returns> + /// <returns>A <see langword="bool"/> indicating whether or not the vector is normalized.</returns> public bool IsNormalized() { return Mathf.Abs(LengthSquared() - 1.0f) < Mathf.Epsilon; @@ -265,6 +284,7 @@ namespace Godot /// <summary> /// Returns the length (magnitude) of this vector. /// </summary> + /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> public real_t Length() { @@ -292,7 +312,7 @@ namespace Godot /// <summary> /// Returns the result of the linear interpolation between - /// this vector and `to` by amount `weight`. + /// this vector and <paramref name="to"/> by amount <paramref name="weight"/>. /// </summary> /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> @@ -309,7 +329,7 @@ namespace Godot /// <summary> /// Returns the result of the linear interpolation between - /// this vector and `to` by the vector amount `weight`. + /// this vector and <paramref name="to"/> by the vector amount <paramref name="weight"/>. /// </summary> /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A vector with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param> @@ -325,7 +345,7 @@ namespace Godot } /// <summary> - /// Returns the vector with a maximum length by limiting its length to `length`. + /// Returns the vector with a maximum length by limiting its length to <paramref name="length"/>. /// </summary> /// <param name="length">The length to limit to.</param> /// <returns>The vector with its length limited.</returns> @@ -364,32 +384,35 @@ namespace Godot } /// <summary> - /// Moves this vector toward `to` by the fixed `delta` amount. + /// Moves this vector toward <paramref name="to"/> by the fixed <paramref name="delta"/> amount. /// </summary> /// <param name="to">The vector to move towards.</param> /// <param name="delta">The amount to move towards by.</param> /// <returns>The resulting vector.</returns> public Vector3 MoveToward(Vector3 to, real_t delta) { - var v = this; - var vd = to - v; - var len = vd.Length(); - return len <= delta || len < Mathf.Epsilon ? to : v + vd / len * delta; + Vector3 v = this; + Vector3 vd = to - v; + real_t len = vd.Length(); + if (len <= delta || len < Mathf.Epsilon) + return to; + + return v + (vd / len * delta); } /// <summary> - /// Returns the vector scaled to unit length. Equivalent to `v / v.Length()`. + /// Returns the vector scaled to unit length. Equivalent to <c>v / v.Length()</c>. /// </summary> /// <returns>A normalized version of the vector.</returns> public Vector3 Normalized() { - var v = this; + Vector3 v = this; v.Normalize(); return v; } /// <summary> - /// Returns the outer product with `b`. + /// Returns the outer product with <paramref name="b"/>. /// </summary> /// <param name="b">The other vector.</param> /// <returns>A <see cref="Basis"/> representing the outer product matrix.</returns> @@ -403,10 +426,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `mod`. + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components + /// and <paramref name="mod"/>. /// </summary> /// <param name="mod">A value representing the divisor of the operation.</param> - /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `mod`.</returns> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="mod"/>. + /// </returns> public Vector3 PosMod(real_t mod) { Vector3 v; @@ -417,10 +443,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `modv`'s components. + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components + /// and <paramref name="modv"/>'s components. /// </summary> /// <param name="modv">A vector representing the divisors of the operation.</param> - /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `modv`'s components.</returns> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="modv"/>'s components. + /// </returns> public Vector3 PosMod(Vector3 modv) { Vector3 v; @@ -431,7 +460,7 @@ namespace Godot } /// <summary> - /// Returns this vector projected onto another vector `b`. + /// Returns this vector projected onto another vector <paramref name="onNormal"/>. /// </summary> /// <param name="onNormal">The vector to project onto.</param> /// <returns>The projected vector.</returns> @@ -441,7 +470,7 @@ namespace Godot } /// <summary> - /// Returns this vector reflected from a plane defined by the given `normal`. + /// Returns this vector reflected from a plane defined by the given <paramref name="normal"/>. /// </summary> /// <param name="normal">The normal vector defining the plane to reflect from. Must be normalized.</param> /// <returns>The reflected vector.</returns> @@ -453,12 +482,12 @@ namespace Godot throw new ArgumentException("Argument is not normalized", nameof(normal)); } #endif - return 2.0f * Dot(normal) * normal - this; + return (2.0f * Dot(normal) * normal) - this; } /// <summary> - /// Rotates this vector around a given `axis` vector by `phi` radians. - /// The `axis` vector must be a normalized vector. + /// Rotates this vector around a given <paramref name="axis"/> vector by <paramref name="phi"/> radians. + /// The <paramref name="axis"/> vector must be a normalized vector. /// </summary> /// <param name="axis">The vector to rotate around. Must be normalized.</param> /// <param name="phi">The angle to rotate by, in radians.</param> @@ -489,7 +518,7 @@ namespace Godot /// on the signs of this vector's components, or zero if the component is zero, /// by calling <see cref="Mathf.Sign(real_t)"/> on each component. /// </summary> - /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns> + /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public Vector3 Sign() { Vector3 v; @@ -503,7 +532,7 @@ namespace Godot /// Returns the signed angle to the given vector, in radians. /// The sign of the angle is positive in a counter-clockwise /// direction and negative in a clockwise direction when viewed - /// from the side specified by the `axis`. + /// from the side specified by the <paramref name="axis"/>. /// </summary> /// <param name="to">The other vector to compare this vector to.</param> /// <param name="axis">The reference axis to use for the angle sign.</param> @@ -518,7 +547,7 @@ namespace Godot /// <summary> /// Returns the result of the spherical linear interpolation between - /// this vector and `to` by amount `weight`. + /// this vector and <paramref name="to"/> by amount <paramref name="weight"/>. /// /// Note: Both vectors must be normalized. /// </summary> @@ -534,7 +563,7 @@ namespace Godot } if (!to.IsNormalized()) { - throw new InvalidOperationException("Vector3.Slerp: `to` is not normalized."); + throw new InvalidOperationException($"Vector3.Slerp: `{nameof(to)}` is not normalized."); } #endif real_t theta = AngleTo(to); @@ -542,17 +571,17 @@ namespace Godot } /// <summary> - /// Returns this vector slid along a plane defined by the given normal. + /// Returns this vector slid along a plane defined by the given <paramref name="normal"/>. /// </summary> /// <param name="normal">The normal vector defining the plane to slide on.</param> /// <returns>The slid vector.</returns> public Vector3 Slide(Vector3 normal) { - return this - normal * Dot(normal); + return this - (normal * Dot(normal)); } /// <summary> - /// Returns this vector with each component snapped to the nearest multiple of `step`. + /// Returns this vector with each component snapped to the nearest multiple of <paramref name="step"/>. /// This can also be used to round to an arbitrary number of decimals. /// </summary> /// <param name="step">A vector value representing the step size to snap to.</param> @@ -570,10 +599,10 @@ namespace Godot /// <summary> /// Returns a diagonal matrix with the vector as main diagonal. /// - /// This is equivalent to a Basis with no rotation or shearing and + /// This is equivalent to a <see cref="Basis"/> with no rotation or shearing and /// this vector's components set as the scale. /// </summary> - /// <returns>A Basis with the vector as its main diagonal.</returns> + /// <returns>A <see cref="Basis"/> with the vector as its main diagonal.</returns> public Basis ToDiagonalMatrix() { return new Basis( @@ -596,54 +625,54 @@ namespace Godot private static readonly Vector3 _back = new Vector3(0, 0, 1); /// <summary> - /// Zero vector, a vector with all components set to `0`. + /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> - /// <value>Equivalent to `new Vector3(0, 0, 0)`</value> + /// <value>Equivalent to <c>new Vector3(0, 0, 0)</c>.</value> public static Vector3 Zero { get { return _zero; } } /// <summary> - /// One vector, a vector with all components set to `1`. + /// One vector, a vector with all components set to <c>1</c>. /// </summary> - /// <value>Equivalent to `new Vector3(1, 1, 1)`</value> + /// <value>Equivalent to <c>new Vector3(1, 1, 1)</c>.</value> public static Vector3 One { get { return _one; } } /// <summary> - /// Infinity vector, a vector with all components set to `Mathf.Inf`. + /// Infinity vector, a vector with all components set to <see cref="Mathf.Inf"/>. /// </summary> - /// <value>Equivalent to `new Vector3(Mathf.Inf, Mathf.Inf, Mathf.Inf)`</value> + /// <value>Equivalent to <c>new Vector3(Mathf.Inf, Mathf.Inf, Mathf.Inf)</c>.</value> public static Vector3 Inf { get { return _inf; } } /// <summary> /// Up unit vector. /// </summary> - /// <value>Equivalent to `new Vector3(0, 1, 0)`</value> + /// <value>Equivalent to <c>new Vector3(0, 1, 0)</c>.</value> public static Vector3 Up { get { return _up; } } /// <summary> /// Down unit vector. /// </summary> - /// <value>Equivalent to `new Vector3(0, -1, 0)`</value> + /// <value>Equivalent to <c>new Vector3(0, -1, 0)</c>.</value> public static Vector3 Down { get { return _down; } } /// <summary> /// Right unit vector. Represents the local direction of right, /// and the global direction of east. /// </summary> - /// <value>Equivalent to `new Vector3(1, 0, 0)`</value> + /// <value>Equivalent to <c>new Vector3(1, 0, 0)</c>.</value> public static Vector3 Right { get { return _right; } } /// <summary> /// Left unit vector. Represents the local direction of left, /// and the global direction of west. /// </summary> - /// <value>Equivalent to `new Vector3(-1, 0, 0)`</value> + /// <value>Equivalent to <c>new Vector3(-1, 0, 0)</c>.</value> public static Vector3 Left { get { return _left; } } /// <summary> /// Forward unit vector. Represents the local direction of forward, /// and the global direction of north. /// </summary> - /// <value>Equivalent to `new Vector3(0, 0, -1)`</value> + /// <value>Equivalent to <c>new Vector3(0, 0, -1)</c>.</value> public static Vector3 Forward { get { return _forward; } } /// <summary> /// Back unit vector. Represents the local direction of back, /// and the global direction of south. /// </summary> - /// <value>Equivalent to `new Vector3(0, 0, 1)`</value> + /// <value>Equivalent to <c>new Vector3(0, 0, 1)</c>.</value> public static Vector3 Back { get { return _back; } } /// <summary> @@ -812,6 +841,11 @@ namespace Godot return left.x > right.x; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the vector and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Vector3) @@ -822,14 +856,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="other"/> are equal + /// </summary> + /// <param name="other">The other vector to compare.</param> + /// <returns>Whether or not the vectors are equal.</returns> public bool Equals(Vector3 other) { return x == other.x && y == other.y && z == other.z; } /// <summary> - /// Returns true if this vector and `other` are approximately equal, by running - /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. + /// Returns <see langword="true"/> if this vector and <paramref name="other"/> are approximately equal, + /// by running <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. /// </summary> /// <param name="other">The other vector to compare.</param> /// <returns>Whether or not the vectors are approximately equal.</returns> @@ -838,16 +877,28 @@ namespace Godot return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z); } + /// <summary> + /// Serves as the hash function for <see cref="Vector3"/>. + /// </summary> + /// <returns>A hash code for this vector.</returns> public override int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Vector3"/> to a string. + /// </summary> + /// <returns>A string representation of this vector.</returns> public override string ToString() { return $"({x}, {y}, {z})"; } + /// <summary> + /// Converts this <see cref="Vector3"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this vector.</returns> public string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs index c96a7cf1b0..2a7771cdfc 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs @@ -21,28 +21,46 @@ namespace Godot /// </summary> public enum Axis { + /// <summary> + /// The vector's X axis. + /// </summary> X = 0, + /// <summary> + /// The vector's Y axis. + /// </summary> Y, + /// <summary> + /// The vector's Z axis. + /// </summary> Z } /// <summary> - /// The vector's X component. Also accessible by using the index position `[0]`. + /// The vector's X component. Also accessible by using the index position <c>[0]</c>. /// </summary> public int x; + /// <summary> - /// The vector's Y component. Also accessible by using the index position `[1]`. + /// The vector's Y component. Also accessible by using the index position <c>[1]</c>. /// </summary> public int y; + /// <summary> - /// The vector's Z component. Also accessible by using the index position `[2]`. + /// The vector's Z component. Also accessible by using the index position <c>[2]</c>. /// </summary> public int z; /// <summary> - /// Access vector components using their index. + /// Access vector components using their <paramref name="index"/>. /// </summary> - /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`, `[2]` is equivalent to `.z`.</value> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the given the <paramref name="index"/> is not 0, 1 or 2. + /// </exception> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="x"/>, + /// <c>[1]</c> is equivalent to <see cref="y"/>, + /// <c>[2]</c> is equivalent to <see cref="z"/>. + /// </value> public int this[int index] { get @@ -89,7 +107,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components clamped between the - /// components of `min` and `max` using + /// components of <paramref name="min"/> and <paramref name="max"/> using /// <see cref="Mathf.Clamp(int, int, int)"/>. /// </summary> /// <param name="min">The vector with minimum allowed values.</param> @@ -106,7 +124,7 @@ namespace Godot } /// <summary> - /// Returns the squared distance between this vector and `b`. + /// Returns the squared distance between this vector and <paramref name="b"/>. /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if /// you need to compare vectors or need the squared distance for some formula. /// </summary> @@ -118,8 +136,9 @@ namespace Godot } /// <summary> - /// Returns the distance between this vector and `b`. + /// Returns the distance between this vector and <paramref name="b"/>. /// </summary> + /// <seealso cref="DistanceSquaredTo(Vector3i)"/> /// <param name="b">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> public real_t DistanceTo(Vector3i b) @@ -128,7 +147,7 @@ namespace Godot } /// <summary> - /// Returns the dot product of this vector and `b`. + /// Returns the dot product of this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector to use.</param> /// <returns>The dot product of the two vectors.</returns> @@ -140,6 +159,7 @@ namespace Godot /// <summary> /// Returns the length (magnitude) of this vector. /// </summary> + /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> public real_t Length() { @@ -186,10 +206,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `mod`. + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components + /// and <paramref name="mod"/>. /// </summary> /// <param name="mod">A value representing the divisor of the operation.</param> - /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `mod`.</returns> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="mod"/>. + /// </returns> public Vector3i PosMod(int mod) { Vector3i v = this; @@ -200,10 +223,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `modv`'s components. + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components + /// and <paramref name="modv"/>'s components. /// </summary> /// <param name="modv">A vector representing the divisors of the operation.</param> - /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `modv`'s components.</returns> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="modv"/>'s components. + /// </returns> public Vector3i PosMod(Vector3i modv) { Vector3i v = this; @@ -218,7 +244,7 @@ namespace Godot /// on the signs of this vector's components, or zero if the component is zero, /// by calling <see cref="Mathf.Sign(int)"/> on each component. /// </summary> - /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns> + /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public Vector3i Sign() { Vector3i v = this; @@ -240,49 +266,49 @@ namespace Godot private static readonly Vector3i _back = new Vector3i(0, 0, 1); /// <summary> - /// Zero vector, a vector with all components set to `0`. + /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> - /// <value>Equivalent to `new Vector3i(0, 0, 0)`</value> + /// <value>Equivalent to <c>new Vector3i(0, 0, 0)</c>.</value> public static Vector3i Zero { get { return _zero; } } /// <summary> - /// One vector, a vector with all components set to `1`. + /// One vector, a vector with all components set to <c>1</c>. /// </summary> - /// <value>Equivalent to `new Vector3i(1, 1, 1)`</value> + /// <value>Equivalent to <c>new Vector3i(1, 1, 1)</c>.</value> public static Vector3i One { get { return _one; } } /// <summary> /// Up unit vector. /// </summary> - /// <value>Equivalent to `new Vector3i(0, 1, 0)`</value> + /// <value>Equivalent to <c>new Vector3i(0, 1, 0)</c>.</value> public static Vector3i Up { get { return _up; } } /// <summary> /// Down unit vector. /// </summary> - /// <value>Equivalent to `new Vector3i(0, -1, 0)`</value> + /// <value>Equivalent to <c>new Vector3i(0, -1, 0)</c>.</value> public static Vector3i Down { get { return _down; } } /// <summary> /// Right unit vector. Represents the local direction of right, /// and the global direction of east. /// </summary> - /// <value>Equivalent to `new Vector3i(1, 0, 0)`</value> + /// <value>Equivalent to <c>new Vector3i(1, 0, 0)</c>.</value> public static Vector3i Right { get { return _right; } } /// <summary> /// Left unit vector. Represents the local direction of left, /// and the global direction of west. /// </summary> - /// <value>Equivalent to `new Vector3i(-1, 0, 0)`</value> + /// <value>Equivalent to <c>new Vector3i(-1, 0, 0)</c>.</value> public static Vector3i Left { get { return _left; } } /// <summary> /// Forward unit vector. Represents the local direction of forward, /// and the global direction of north. /// </summary> - /// <value>Equivalent to `new Vector3i(0, 0, -1)`</value> + /// <value>Equivalent to <c>new Vector3i(0, 0, -1)</c>.</value> public static Vector3i Forward { get { return _forward; } } /// <summary> /// Back unit vector. Represents the local direction of back, /// and the global direction of south. /// </summary> - /// <value>Equivalent to `new Vector3i(0, 0, 1)`</value> + /// <value>Equivalent to <c>new Vector3i(0, 0, 1)</c>.</value> public static Vector3i Back { get { return _back; } } /// <summary> @@ -479,16 +505,29 @@ namespace Godot return left.x > right.x; } + /// <summary> + /// Converts this <see cref="Vector3i"/> to a <see cref="Vector3"/>. + /// </summary> + /// <param name="value">The vector to convert.</param> public static implicit operator Vector3(Vector3i value) { return new Vector3(value.x, value.y, value.z); } + /// <summary> + /// Converts a <see cref="Vector3"/> to a <see cref="Vector3i"/>. + /// </summary> + /// <param name="value">The vector to convert.</param> public static explicit operator Vector3i(Vector3 value) { return new Vector3i(value); } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the vector and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Vector3i) @@ -499,21 +538,38 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="other"/> are equal + /// </summary> + /// <param name="other">The other vector to compare.</param> + /// <returns>Whether or not the vectors are equal.</returns> public bool Equals(Vector3i other) { return x == other.x && y == other.y && z == other.z; } + /// <summary> + /// Serves as the hash function for <see cref="Vector3i"/>. + /// </summary> + /// <returns>A hash code for this vector.</returns> public override int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Vector3i"/> to a string. + /// </summary> + /// <returns>A string representation of this vector.</returns> public override string ToString() { return $"({x}, {y}, {z})"; } + /// <summary> + /// Converts this <see cref="Vector3i"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this vector.</returns> public string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})"; diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp index 18a9221f89..bb80a836ad 100644 --- a/modules/mono/glue/string_glue.cpp +++ b/modules/mono/glue/string_glue.cpp @@ -67,6 +67,11 @@ MonoString *godot_icall_String_sha256_text(MonoString *p_str) { return GDMonoMarshal::mono_string_from_godot(ret); } +MonoString *godot_icall_String_simplify_path(MonoString *p_str) { + String ret = GDMonoMarshal::mono_string_to_godot(p_str).simplify_path(); + return GDMonoMarshal::mono_string_from_godot(ret); +} + void godot_register_string_icalls() { GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", godot_icall_String_md5_buffer); GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", godot_icall_String_md5_text); @@ -74,6 +79,7 @@ void godot_register_string_icalls() { GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", godot_icall_String_rfindn); GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", godot_icall_String_sha256_buffer); GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", godot_icall_String_sha256_text); + GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_simplify_path", godot_icall_String_simplify_path); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp index 0b36796d98..8b215a66c2 100644 --- a/modules/mono/mono_gd/gd_mono_cache.cpp +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -141,7 +141,6 @@ void CachedData::clear_godot_api_cache() { class_SignalAttribute = nullptr; class_ToolAttribute = nullptr; class_RemoteAttribute = nullptr; - class_MasterAttribute = nullptr; class_PuppetAttribute = nullptr; class_GodotMethodAttribute = nullptr; field_GodotMethodAttribute_methodName = nullptr; @@ -267,7 +266,6 @@ void update_godot_api_cache() { CACHE_CLASS_AND_CHECK(SignalAttribute, GODOT_API_CLASS(SignalAttribute)); CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute)); CACHE_CLASS_AND_CHECK(RemoteAttribute, GODOT_API_CLASS(RemoteAttribute)); - CACHE_CLASS_AND_CHECK(MasterAttribute, GODOT_API_CLASS(MasterAttribute)); CACHE_CLASS_AND_CHECK(PuppetAttribute, GODOT_API_CLASS(PuppetAttribute)); CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute)); CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName")); diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index 6d49da0af3..fd28bbda14 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -112,7 +112,6 @@ struct CachedData { GDMonoClass *class_SignalAttribute; GDMonoClass *class_ToolAttribute; GDMonoClass *class_RemoteAttribute; - GDMonoClass *class_MasterAttribute; GDMonoClass *class_PuppetAttribute; GDMonoClass *class_GodotMethodAttribute; GDMonoField *field_GodotMethodAttribute_methodName; diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index 60047545c4..cf76c23926 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -91,7 +91,11 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { // The object was just created, no script instance binding should have been attached CRASH_COND(CSharpLanguage::has_instance_binding(unmanaged)); - void *data = (void *)CSharpLanguage::get_singleton()->insert_script_binding(unmanaged, script_binding); + void *data; + { + MutexLock lock(CSharpLanguage::get_singleton()->get_language_bind_mutex()); + data = (void *)CSharpLanguage::get_singleton()->insert_script_binding(unmanaged, script_binding); + } // Should be thread safe because the object was just created and nothing else should be referencing it CSharpLanguage::set_instance_binding(unmanaged, data); diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index 053618ebe4..2fb5e446da 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -200,7 +200,7 @@ String str_format(const char *p_format, ...) { return res; } -#if defined(MINGW_ENABLED) || defined(_MSC_VER) && _MSC_VER < 1900 +#if defined(MINGW_ENABLED) #define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_args_copy) #define gd_vscprintf(m_format, m_args_copy) _vscprintf(m_format, m_args_copy) #else diff --git a/modules/msdfgen/SCsub b/modules/msdfgen/SCsub new file mode 100644 index 0000000000..227369f4bd --- /dev/null +++ b/modules/msdfgen/SCsub @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_msdfgen = env_modules.Clone() + +# Thirdparty source files + +thirdparty_obj = [] + +if env["builtin_msdfgen"]: + env_msdfgen.disable_warnings() + + thirdparty_dir = "#thirdparty/msdfgen/" + thirdparty_sources = [ + "core/Contour.cpp", + "core/EdgeHolder.cpp", + "core/MSDFErrorCorrection.cpp", + "core/Projection.cpp", + "core/Scanline.cpp", + "core/Shape.cpp", + "core/SignedDistance.cpp", + "core/Vector2.cpp", + "core/contour-combiners.cpp", + "core/edge-coloring.cpp", + "core/edge-segments.cpp", + "core/edge-selectors.cpp", + "core/equation-solver.cpp", + "core/msdf-error-correction.cpp", + "core/msdfgen.cpp", + "core/rasterization.cpp", + "core/render-sdf.cpp", + "core/sdf-error-estimation.cpp", + "core/shape-description.cpp", + ] + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + env_msdfgen.Append(CPPPATH=["#thirdparty/freetype/include", "#thirdparty/msdfgen", "#thirdparty/nanosvg"]) + + lib = env_msdfgen.add_library("msdfgen_builtin", thirdparty_sources) + thirdparty_obj += lib + + # Needs to be appended to arrive after libscene in the linker call, + # but we don't want it to arrive *after* system libs, so manual hack + # LIBS contains first SCons Library objects ("SCons.Node.FS.File object") + # and then plain strings for system library. We insert between the two. + inserted = False + for idx, linklib in enumerate(env["LIBS"]): + if isinstance(linklib, (str, bytes)): # first system lib such as "X11", otherwise SCons lib object + env["LIBS"].insert(idx, lib) + inserted = True + break + if not inserted: + env.Append(LIBS=[lib]) + + +# Godot source files + +module_obj = [] + +env_msdfgen.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/msdfgen/config.py b/modules/msdfgen/config.py new file mode 100644 index 0000000000..653e466a74 --- /dev/null +++ b/modules/msdfgen/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return env.module_check_dependencies("msdfgen", ["freetype"]) + + +def configure(env): + pass diff --git a/modules/gdnative/xr/register_types.cpp b/modules/msdfgen/register_types.cpp index cb043debc5..ad60a66d2d 100644 --- a/modules/gdnative/xr/register_types.cpp +++ b/modules/msdfgen/register_types.cpp @@ -29,12 +29,7 @@ /*************************************************************************/ #include "register_types.h" -#include "xr_interface_gdnative.h" -void register_xr_types() { - GDREGISTER_CLASS(XRInterfaceGDNative); - ClassDB::add_compatibility_class("ARVRInterfaceGDNative", "XRInterfaceGDNative"); -} +void register_msdfgen_types() {} -void unregister_xr_types() { -} +void unregister_msdfgen_types() {} diff --git a/modules/gdnative/xr/register_types.h b/modules/msdfgen/register_types.h index 4e7469abe9..fb776c14ed 100644 --- a/modules/gdnative/xr/register_types.h +++ b/modules/msdfgen/register_types.h @@ -28,10 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef XR_REGISTER_TYPES_H -#define XR_REGISTER_TYPES_H +#ifndef MSDFGEN_REGISTER_TYPES_H +#define MSDFGEN_REGISTER_TYPES_H -void register_xr_types(); -void unregister_xr_types(); +void register_msdfgen_types(); +void unregister_msdfgen_types(); -#endif // XR_REGISTER_TYPES_H +#endif // MSDFGEN_REGISTER_TYPES_H diff --git a/modules/ogg/config.py b/modules/ogg/config.py index d22f9454ed..5a417ba8dd 100644 --- a/modules/ogg/config.py +++ b/modules/ogg/config.py @@ -4,3 +4,14 @@ def can_build(env, platform): def configure(env): pass + + +def get_doc_classes(): + return [ + "OGGPacketSequence", + "OGGPacketSequencePlayback", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/ogg/doc_classes/OGGPacketSequence.xml b/modules/ogg/doc_classes/OGGPacketSequence.xml new file mode 100644 index 0000000000..9d3789cb07 --- /dev/null +++ b/modules/ogg/doc_classes/OGGPacketSequence.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OGGPacketSequence" inherits="Resource" version="4.0"> + <brief_description> + A sequence of OGG packets. + </brief_description> + <description> + A sequence of OGG packets. + </description> + <tutorials> + </tutorials> + <methods> + <method name="get_length" qualifiers="const"> + <return type="float" /> + <description> + The length of this stream, in seconds. + </description> + </method> + </methods> + <members> + <member name="granule_positions" type="Array" setter="set_packet_granule_positions" getter="get_packet_granule_positions" default="[]"> + Contains the granule positions for each page in this packet sequence. + </member> + <member name="packet_data" type="Array" setter="set_packet_data" getter="get_packet_data" default="[]"> + Contains the raw packets that make up this OGGPacketSequence. + </member> + <member name="sampling_rate" type="float" setter="set_sampling_rate" getter="get_sampling_rate" default="0.0"> + Holds sample rate information about this sequence. Must be set by another class that actually understands the codec. + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml b/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml new file mode 100644 index 0000000000..49e32f0d6e --- /dev/null +++ b/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OGGPacketSequencePlayback" inherits="RefCounted" version="4.0"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/ogg/ogg_packet_sequence.cpp b/modules/ogg/ogg_packet_sequence.cpp new file mode 100644 index 0000000000..b7a3ad2876 --- /dev/null +++ b/modules/ogg/ogg_packet_sequence.cpp @@ -0,0 +1,220 @@ +/*************************************************************************/ +/* ogg_packet_sequence.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 "ogg_packet_sequence.h" +#include "core/variant/typed_array.h" + +void OGGPacketSequence::push_page(int64_t p_granule_pos, const Vector<PackedByteArray> &p_data) { + Vector<PackedByteArray> data_stored; + for (int i = 0; i < p_data.size(); i++) { + data_stored.push_back(p_data[i]); + } + page_granule_positions.push_back(p_granule_pos); + page_data.push_back(data_stored); + data_version++; +} + +void OGGPacketSequence::set_packet_data(const Array &p_data) { + data_version++; // Update the data version so old playbacks know that they can't rely on us anymore. + page_data.clear(); + for (int page_idx = 0; page_idx < p_data.size(); page_idx++) { + // Push a new page. We cleared the vector so this will be at index `page_idx`. + page_data.push_back(Vector<PackedByteArray>()); + TypedArray<PackedByteArray> this_page_data = p_data[page_idx]; + for (int packet = 0; packet < this_page_data.size(); packet++) { + page_data.write[page_idx].push_back(this_page_data[packet]); + } + } +} + +Array OGGPacketSequence::get_packet_data() const { + Array ret; + for (const Vector<PackedByteArray> &page : page_data) { + Array page_variant; + for (const PackedByteArray &packet : page) { + page_variant.push_back(packet); + } + ret.push_back(page_variant); + } + return ret; +} + +void OGGPacketSequence::set_packet_granule_positions(const Array &p_granule_positions) { + data_version++; // Update the data version so old playbacks know that they can't rely on us anymore. + page_granule_positions.clear(); + for (int page_idx = 0; page_idx < p_granule_positions.size(); page_idx++) { + int64_t granule_pos = p_granule_positions[page_idx]; + page_granule_positions.push_back(granule_pos); + } +} + +Array OGGPacketSequence::get_packet_granule_positions() const { + Array ret; + for (int64_t granule_pos : page_granule_positions) { + ret.push_back(granule_pos); + } + return ret; +} + +void OGGPacketSequence::set_sampling_rate(float p_sampling_rate) { + sampling_rate = p_sampling_rate; +} + +float OGGPacketSequence::get_sampling_rate() const { + return sampling_rate; +} + +int64_t OGGPacketSequence::get_final_granule_pos() const { + if (!page_granule_positions.is_empty()) { + return page_granule_positions[page_granule_positions.size() - 1]; + } + return -1; +} + +float OGGPacketSequence::get_length() const { + int64_t granule_pos = get_final_granule_pos(); + if (granule_pos < 0) { + return 0; + } + return granule_pos / sampling_rate; +} + +Ref<OGGPacketSequencePlayback> OGGPacketSequence::instance_playback() { + Ref<OGGPacketSequencePlayback> playback; + playback.instantiate(); + playback->ogg_packet_sequence = Ref<OGGPacketSequence>(this); + playback->data_version = data_version; + + return playback; +} + +void OGGPacketSequence::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_packet_data", "packet_data"), &OGGPacketSequence::set_packet_data); + ClassDB::bind_method(D_METHOD("get_packet_data"), &OGGPacketSequence::get_packet_data); + + ClassDB::bind_method(D_METHOD("set_packet_granule_positions", "granule_positions"), &OGGPacketSequence::set_packet_granule_positions); + ClassDB::bind_method(D_METHOD("get_packet_granule_positions"), &OGGPacketSequence::get_packet_granule_positions); + + ClassDB::bind_method(D_METHOD("set_sampling_rate", "sampling_rate"), &OGGPacketSequence::set_sampling_rate); + ClassDB::bind_method(D_METHOD("get_sampling_rate"), &OGGPacketSequence::get_sampling_rate); + + ClassDB::bind_method(D_METHOD("get_length"), &OGGPacketSequence::get_length); + + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "packet_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_packet_data", "get_packet_data"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "granule_positions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_packet_granule_positions", "get_packet_granule_positions"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sampling_rate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_sampling_rate", "get_sampling_rate"); +} + +bool OGGPacketSequencePlayback::next_ogg_packet(ogg_packet **p_packet) const { + ERR_FAIL_COND_V(data_version != ogg_packet_sequence->data_version, false); + ERR_FAIL_COND_V(ogg_packet_sequence->page_data.is_empty(), false); + ERR_FAIL_COND_V(ogg_packet_sequence->page_granule_positions.is_empty(), false); + // Move on to the next page if need be. This happens first to help simplify seek logic. + while (packet_cursor >= ogg_packet_sequence->page_data[page_cursor].size()) { + packet_cursor = 0; + page_cursor++; + if (page_cursor >= ogg_packet_sequence->page_data.size()) { + return false; + } + } + + ERR_FAIL_COND_V(page_cursor >= ogg_packet_sequence->page_data.size(), false); + + packet->b_o_s = page_cursor == 0 && packet_cursor == 0; + packet->e_o_s = page_cursor == ogg_packet_sequence->page_data.size() - 1 && packet_cursor == ogg_packet_sequence->page_data[page_cursor].size() - 1; + packet->granulepos = packet_cursor == ogg_packet_sequence->page_data[page_cursor].size() - 1 ? ogg_packet_sequence->page_granule_positions[page_cursor] : -1; + packet->packetno = packetno++; + packet->bytes = ogg_packet_sequence->page_data[page_cursor][packet_cursor].size(); + packet->packet = (unsigned char *)(ogg_packet_sequence->page_data[page_cursor][packet_cursor].ptr()); + + *p_packet = packet; + + packet_cursor++; + + return true; +} + +uint32_t OGGPacketSequencePlayback::seek_page_internal(int64_t granule, uint32_t after_page_inclusive, uint32_t before_page_inclusive) { + if (before_page_inclusive == after_page_inclusive) { + return before_page_inclusive; + } + uint32_t actual_middle_page = after_page_inclusive + (before_page_inclusive - after_page_inclusive) / 2; + // Complicating the bisection search algorithm, the middle page might not have a packet that ends on it, + // which means it might not have a correct granule position. Find a nearby page that does have a packet ending on it. + uint32_t bisection_page = -1; + for (uint32_t test_page = actual_middle_page; test_page <= before_page_inclusive; test_page++) { + if (ogg_packet_sequence->page_data[test_page].size() > 0) { + bisection_page = test_page; + break; + } + } + // Check if we have to go backwards. + if (bisection_page == (unsigned int)-1) { + for (uint32_t test_page = actual_middle_page; test_page >= after_page_inclusive; test_page--) { + if (ogg_packet_sequence->page_data[test_page].size() > 0) { + bisection_page = test_page; + break; + } + } + } + if (bisection_page == (unsigned int)-1) { + return -1; + } + + int64_t bisection_granule_pos = ogg_packet_sequence->page_granule_positions[bisection_page]; + if (granule > bisection_granule_pos) { + return seek_page_internal(granule, bisection_page + 1, before_page_inclusive); + } else { + return seek_page_internal(granule, after_page_inclusive, bisection_page); + } +} + +bool OGGPacketSequencePlayback::seek_page(int64_t p_granule_pos) { + int correct_page = seek_page_internal(p_granule_pos, 0, ogg_packet_sequence->page_data.size() - 1); + if (correct_page == -1) { + return false; + } + + packet_cursor = 0; + page_cursor = correct_page; + + // Don't pretend subsequent packets are contiguous with previous ones. + packetno = 0; + + return true; +} + +OGGPacketSequencePlayback::OGGPacketSequencePlayback() { + packet = new ogg_packet(); +} + +OGGPacketSequencePlayback::~OGGPacketSequencePlayback() { + delete packet; +} diff --git a/modules/ogg/ogg_packet_sequence.h b/modules/ogg/ogg_packet_sequence.h new file mode 100644 index 0000000000..b00ada06c1 --- /dev/null +++ b/modules/ogg/ogg_packet_sequence.h @@ -0,0 +1,128 @@ +/*************************************************************************/ +/* ogg_packet_sequence.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 OGG_PACKET_SEQUENCE_H +#define OGG_PACKET_SEQUENCE_H + +#include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/variant/native_ptr.h" +#include "core/variant/typed_array.h" +#include "core/variant/variant.h" +#include "thirdparty/libogg/ogg/ogg.h" + +class OGGPacketSequencePlayback; + +class OGGPacketSequence : public Resource { + GDCLASS(OGGPacketSequence, Resource); + + friend class OGGPacketSequencePlayback; + + // List of pages, each of which is a list of packets on that page. The innermost PackedByteArrays contain complete ogg packets. + Vector<Vector<PackedByteArray>> page_data; + + // List of the granule position for each page. + Vector<uint64_t> page_granule_positions; + + // The page after the current last page. Similar semantics to an end() iterator. + int64_t end_page = 0; + + uint64_t data_version = 0; + + float sampling_rate = 0; + float length = 0; + +protected: + static void _bind_methods(); + +public: + // Pushes information about all the pages that ended on this page. + // This should be called for each page, even for pages that no packets ended on. + void push_page(int64_t p_granule_pos, const Vector<PackedByteArray> &p_data); + + void set_packet_data(const Array &p_data); + Array get_packet_data() const; + + void set_packet_granule_positions(const Array &p_granule_positions); + Array get_packet_granule_positions() const; + + // Sets a sampling rate associated with this object. OGGPacketSequence doesn't understand codecs, + // so this value is naively stored as a convenience. + void set_sampling_rate(float p_sampling_rate); + + // Returns a sampling rate previously set by set_sampling_rate(). + float get_sampling_rate() const; + + // Returns a length previously set by set_length(). + float get_length() const; + + // Returns the granule position of the last page in this sequence. + int64_t get_final_granule_pos() const; + + Ref<OGGPacketSequencePlayback> instance_playback(); + + OGGPacketSequence() {} + virtual ~OGGPacketSequence() {} +}; + +class OGGPacketSequencePlayback : public RefCounted { + GDCLASS(OGGPacketSequencePlayback, RefCounted); + + friend class OGGPacketSequence; + + Ref<OGGPacketSequence> ogg_packet_sequence; + + mutable int64_t page_cursor = 0; + mutable int32_t packet_cursor = 0; + + mutable ogg_packet *packet; + + uint64_t data_version; + + mutable int64_t packetno = 0; + + // Recursive bisection search for the correct page. + uint32_t seek_page_internal(int64_t granule, uint32_t after_page_inclusive, uint32_t before_page_inclusive); + +public: + // Calling functions must not modify this packet. + // Returns true on success, false on error or if there is no next packet. + bool next_ogg_packet(ogg_packet **p_packet) const; + + // Seeks to the page such that the previous page has a granule position less than or equal to this value, + // and the current page has a granule position greater than this value. + // Returns true on success, false on failure. + bool seek_page(int64_t p_granule_pos); + + OGGPacketSequencePlayback(); + virtual ~OGGPacketSequencePlayback(); +}; + +#endif // OGG_PACKET_SEQUENCE_H diff --git a/modules/ogg/register_types.cpp b/modules/ogg/register_types.cpp index b23ea65378..3448e7063a 100644 --- a/modules/ogg/register_types.cpp +++ b/modules/ogg/register_types.cpp @@ -30,8 +30,11 @@ #include "register_types.h" -// Dummy module as libogg is needed by other modules (vorbis, theora, opus, ...) +#include "ogg_packet_sequence.h" -void register_ogg_types() {} +void register_ogg_types() { + GDREGISTER_CLASS(OGGPacketSequence); + GDREGISTER_CLASS(OGGPacketSequencePlayback); +} void unregister_ogg_types() {} diff --git a/modules/stb_vorbis/SCsub b/modules/stb_vorbis/SCsub deleted file mode 100644 index 8fddb23dc8..0000000000 --- a/modules/stb_vorbis/SCsub +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python - -Import("env") -Import("env_modules") - -env_stb_vorbis = env_modules.Clone() - -# Thirdparty source files - -thirdparty_obj = [] - -thirdparty_sources = ["#thirdparty/misc/stb_vorbis.c"] - -env_thirdparty = env_stb_vorbis.Clone() -env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) -env.modules_sources += thirdparty_obj - -# Godot source files - -module_obj = [] - -env_stb_vorbis.add_source_files(module_obj, "*.cpp") -env.modules_sources += module_obj - -# Needed to force rebuilding the module files when the thirdparty library is updated. -env.Depends(module_obj, thirdparty_obj) diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp deleted file mode 100644 index 768b419348..0000000000 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp +++ /dev/null @@ -1,270 +0,0 @@ -/*************************************************************************/ -/* audio_stream_ogg_vorbis.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 "audio_stream_ogg_vorbis.h" - -#include "core/io/file_access.h" - -void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) { - ERR_FAIL_COND(!active); - - int todo = p_frames; - - int start_buffer = 0; - - while (todo && active) { - float *buffer = (float *)p_buffer; - if (start_buffer > 0) { - buffer = (buffer + start_buffer * 2); - } - int mixed = stb_vorbis_get_samples_float_interleaved(ogg_stream, 2, buffer, todo * 2); - if (vorbis_stream->channels == 1 && mixed > 0) { - //mix mono to stereo - for (int i = start_buffer; i < start_buffer + mixed; i++) { - p_buffer[i].r = p_buffer[i].l; - } - } - todo -= mixed; - frames_mixed += mixed; - - if (todo) { - //end of file! - bool is_not_empty = mixed > 0 || stb_vorbis_stream_length_in_samples(ogg_stream) > 0; - if (vorbis_stream->loop && is_not_empty) { - //loop - seek(vorbis_stream->loop_offset); - loops++; - // we still have buffer to fill, start from this element in the next iteration. - start_buffer = p_frames - todo; - } else { - for (int i = p_frames - todo; i < p_frames; i++) { - p_buffer[i] = AudioFrame(0, 0); - } - active = false; - todo = 0; - } - } - } -} - -float AudioStreamPlaybackOGGVorbis::get_stream_sampling_rate() { - return vorbis_stream->sample_rate; -} - -void AudioStreamPlaybackOGGVorbis::start(float p_from_pos) { - active = true; - seek(p_from_pos); - loops = 0; - _begin_resample(); -} - -void AudioStreamPlaybackOGGVorbis::stop() { - active = false; -} - -bool AudioStreamPlaybackOGGVorbis::is_playing() const { - return active; -} - -int AudioStreamPlaybackOGGVorbis::get_loop_count() const { - return loops; -} - -float AudioStreamPlaybackOGGVorbis::get_playback_position() const { - return float(frames_mixed) / vorbis_stream->sample_rate; -} - -void AudioStreamPlaybackOGGVorbis::seek(float p_time) { - if (!active) { - return; - } - - if (p_time >= vorbis_stream->get_length()) { - p_time = 0; - } - frames_mixed = uint32_t(vorbis_stream->sample_rate * p_time); - - stb_vorbis_seek(ogg_stream, frames_mixed); -} - -AudioStreamPlaybackOGGVorbis::~AudioStreamPlaybackOGGVorbis() { - if (ogg_alloc.alloc_buffer) { - stb_vorbis_close(ogg_stream); - memfree(ogg_alloc.alloc_buffer); - } -} - -Ref<AudioStreamPlayback> AudioStreamOGGVorbis::instance_playback() { - Ref<AudioStreamPlaybackOGGVorbis> ovs; - - ERR_FAIL_COND_V_MSG(data == nullptr, ovs, - "This AudioStreamOGGVorbis does not have an audio file assigned " - "to it. AudioStreamOGGVorbis should not be created from the " - "inspector or with `.new()`. Instead, load an audio file."); - - ovs.instantiate(); - ovs->vorbis_stream = Ref<AudioStreamOGGVorbis>(this); - ovs->ogg_alloc.alloc_buffer = (char *)memalloc(decode_mem_size); - ovs->ogg_alloc.alloc_buffer_length_in_bytes = decode_mem_size; - ovs->frames_mixed = 0; - ovs->active = false; - ovs->loops = 0; - int error; - ovs->ogg_stream = stb_vorbis_open_memory((const unsigned char *)data, data_len, &error, &ovs->ogg_alloc); - if (!ovs->ogg_stream) { - memfree(ovs->ogg_alloc.alloc_buffer); - ovs->ogg_alloc.alloc_buffer = nullptr; - ERR_FAIL_COND_V(!ovs->ogg_stream, Ref<AudioStreamPlaybackOGGVorbis>()); - } - - return ovs; -} - -String AudioStreamOGGVorbis::get_stream_name() const { - return ""; //return stream_name; -} - -void AudioStreamOGGVorbis::clear_data() { - if (data) { - memfree(data); - data = nullptr; - data_len = 0; - } -} - -void AudioStreamOGGVorbis::set_data(const Vector<uint8_t> &p_data) { - int src_data_len = p_data.size(); - uint32_t alloc_try = 1024; - Vector<char> alloc_mem; - char *w; - stb_vorbis *ogg_stream = nullptr; - stb_vorbis_alloc ogg_alloc; - - // Vorbis comments may be up to UINT32_MAX, but that's arguably pretty rare. - // Let's go with 2^30 so we don't risk going out of bounds. - const uint32_t MAX_TEST_MEM = 1 << 30; - - while (alloc_try < MAX_TEST_MEM) { - alloc_mem.resize(alloc_try); - w = alloc_mem.ptrw(); - - ogg_alloc.alloc_buffer = w; - ogg_alloc.alloc_buffer_length_in_bytes = alloc_try; - - const uint8_t *src_datar = p_data.ptr(); - - int error; - ogg_stream = stb_vorbis_open_memory((const unsigned char *)src_datar, src_data_len, &error, &ogg_alloc); - - if (!ogg_stream && error == VORBIS_outofmem) { - alloc_try *= 2; - } else { - ERR_FAIL_COND(alloc_try == MAX_TEST_MEM); - ERR_FAIL_COND(ogg_stream == nullptr); - - stb_vorbis_info info = stb_vorbis_get_info(ogg_stream); - - channels = info.channels; - sample_rate = info.sample_rate; - decode_mem_size = alloc_try; - //does this work? (it's less mem..) - //decode_mem_size = ogg_alloc.alloc_buffer_length_in_bytes + info.setup_memory_required + info.temp_memory_required + info.max_frame_size; - - length = stb_vorbis_stream_length_in_seconds(ogg_stream); - stb_vorbis_close(ogg_stream); - - // free any existing data - clear_data(); - - data = memalloc(src_data_len); - memcpy(data, src_datar, src_data_len); - data_len = src_data_len; - - break; - } - } - - ERR_FAIL_COND_MSG(alloc_try == MAX_TEST_MEM, vformat("Couldn't set vorbis data even with an alloc buffer of %d bytes, report bug.", MAX_TEST_MEM)); -} - -Vector<uint8_t> AudioStreamOGGVorbis::get_data() const { - Vector<uint8_t> vdata; - - if (data_len && data) { - vdata.resize(data_len); - { - uint8_t *w = vdata.ptrw(); - memcpy(w, data, data_len); - } - } - - return vdata; -} - -void AudioStreamOGGVorbis::set_loop(bool p_enable) { - loop = p_enable; -} - -bool AudioStreamOGGVorbis::has_loop() const { - return loop; -} - -void AudioStreamOGGVorbis::set_loop_offset(float p_seconds) { - loop_offset = p_seconds; -} - -float AudioStreamOGGVorbis::get_loop_offset() const { - return loop_offset; -} - -float AudioStreamOGGVorbis::get_length() const { - return length; -} - -void AudioStreamOGGVorbis::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamOGGVorbis::set_data); - ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamOGGVorbis::get_data); - - ClassDB::bind_method(D_METHOD("set_loop", "enable"), &AudioStreamOGGVorbis::set_loop); - ClassDB::bind_method(D_METHOD("has_loop"), &AudioStreamOGGVorbis::has_loop); - - ClassDB::bind_method(D_METHOD("set_loop_offset", "seconds"), &AudioStreamOGGVorbis::set_loop_offset); - ClassDB::bind_method(D_METHOD("get_loop_offset"), &AudioStreamOGGVorbis::get_loop_offset); - - ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "loop_offset"), "set_loop_offset", "get_loop_offset"); -} - -AudioStreamOGGVorbis::AudioStreamOGGVorbis() {} - -AudioStreamOGGVorbis::~AudioStreamOGGVorbis() { - clear_data(); -} diff --git a/modules/stb_vorbis/config.py b/modules/stb_vorbis/config.py deleted file mode 100644 index 1eb0a8cf33..0000000000 --- a/modules/stb_vorbis/config.py +++ /dev/null @@ -1,16 +0,0 @@ -def can_build(env, platform): - return True - - -def configure(env): - pass - - -def get_doc_classes(): - return [ - "AudioStreamOGGVorbis", - ] - - -def get_doc_path(): - return "doc_classes" diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index d06c5c2f14..6691f86e60 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -15,7 +15,7 @@ def make_icu_data(target, source, env): g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("/* (C) 2016 and later: Unicode, Inc. and others. */\n") - g.write("/* License & terms of use: http://www.unicode.org/copyright.html */\n") + g.write("/* License & terms of use: https://www.unicode.org/copyright.html */\n") g.write("#ifndef _ICU_DATA_H\n") g.write("#define _ICU_DATA_H\n") g.write('#include "unicode/utypes.h"\n') @@ -38,7 +38,8 @@ def make_icu_data(target, source, env): # Thirdparty source files thirdparty_obj = [] -freetype_enabled = env.module_check_dependencies("text_server_adv", ["freetype"]) +freetype_enabled = env.module_check_dependencies("text_server_adv", ["freetype"], True) +msdngen_enabled = env.module_check_dependencies("text_server_adv", ["msdfgen"], True) if env["builtin_harfbuzz"]: env_harfbuzz = env_modules.Clone() @@ -509,6 +510,13 @@ env_text_server_adv.Append( ] ) +if msdngen_enabled: + env_text_server_adv.Append( + CPPPATH=[ + "#thirdparty/msdfgen", + ] + ) + if freetype_enabled: env_text_server_adv.Append( CPPPATH=[ diff --git a/modules/text_server_adv/bitmap_font_adv.cpp b/modules/text_server_adv/bitmap_font_adv.cpp deleted file mode 100644 index df7b42eac6..0000000000 --- a/modules/text_server_adv/bitmap_font_adv.cpp +++ /dev/null @@ -1,585 +0,0 @@ -/*************************************************************************/ -/* bitmap_font_adv.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 "bitmap_font_adv.h" - -/*************************************************************************/ -/* hb_bmp_font_t HarfBuzz Bitmap font interface */ -/*************************************************************************/ - -struct hb_bmp_font_t { - BitmapFontDataAdvanced *face = nullptr; - float font_size = 0.0; - bool unref = false; /* Whether to destroy bm_face when done. */ -}; - -static hb_bmp_font_t *_hb_bmp_font_create(BitmapFontDataAdvanced *p_face, float p_font_size, bool p_unref) { - hb_bmp_font_t *bm_font = reinterpret_cast<hb_bmp_font_t *>(calloc(1, sizeof(hb_bmp_font_t))); - - if (!bm_font) { - return nullptr; - } - - bm_font->face = p_face; - bm_font->font_size = p_font_size; - bm_font->unref = p_unref; - - return bm_font; -} - -static void _hb_bmp_font_destroy(void *data) { - hb_bmp_font_t *bm_font = reinterpret_cast<hb_bmp_font_t *>(data); - free(bm_font); -} - -static hb_bool_t hb_bmp_get_nominal_glyph(hb_font_t *font, void *font_data, hb_codepoint_t unicode, hb_codepoint_t *glyph, void *user_data) { - const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data); - - if (!bm_font->face) { - return false; - } - - if (!bm_font->face->has_char(unicode)) { - if (bm_font->face->has_char(0xF000u + unicode)) { - *glyph = 0xF000u + unicode; - return true; - } else { - return false; - } - } - - *glyph = unicode; - return true; -} - -static hb_position_t hb_bmp_get_glyph_h_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph, void *user_data) { - const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data); - - if (!bm_font->face) { - return 0; - } - - if (!bm_font->face->has_char(glyph)) { - return 0; - } - - return bm_font->face->get_advance(glyph, bm_font->font_size).x * 64; -} - -static hb_position_t hb_bmp_get_glyph_v_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph, void *user_data) { - const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data); - - if (!bm_font->face) { - return 0; - } - - if (!bm_font->face->has_char(glyph)) { - return 0; - } - - return -bm_font->face->get_advance(glyph, bm_font->font_size).y * 64; -} - -static hb_position_t hb_bmp_get_glyph_h_kerning(hb_font_t *font, void *font_data, hb_codepoint_t left_glyph, hb_codepoint_t right_glyph, void *user_data) { - const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data); - - if (!bm_font->face) { - return 0; - } - - if (!bm_font->face->has_char(left_glyph)) { - return 0; - } - - if (!bm_font->face->has_char(right_glyph)) { - return 0; - } - - return bm_font->face->get_kerning(left_glyph, right_glyph, bm_font->font_size).x * 64; -} - -static hb_bool_t hb_bmp_get_glyph_v_origin(hb_font_t *font, void *font_data, hb_codepoint_t glyph, hb_position_t *x, hb_position_t *y, void *user_data) { - const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data); - - if (!bm_font->face) { - return false; - } - - if (!bm_font->face->has_char(glyph)) { - return false; - } - - *x = bm_font->face->get_advance(glyph, bm_font->font_size).x * 32; - *y = bm_font->face->get_ascent(bm_font->font_size) * 64; - - return true; -} - -static hb_bool_t hb_bmp_get_glyph_extents(hb_font_t *font, void *font_data, hb_codepoint_t glyph, hb_glyph_extents_t *extents, void *user_data) { - const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data); - - if (!bm_font->face) { - return false; - } - - if (!bm_font->face->has_char(glyph)) { - return false; - } - - extents->x_bearing = 0; - extents->y_bearing = 0; - extents->width = bm_font->face->get_size(glyph, bm_font->font_size).x * 64; - extents->height = bm_font->face->get_size(glyph, bm_font->font_size).y * 64; - - return true; -} - -static hb_bool_t hb_bmp_get_font_h_extents(hb_font_t *font, void *font_data, hb_font_extents_t *metrics, void *user_data) { - const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data); - - if (!bm_font->face) { - return false; - } - - metrics->ascender = bm_font->face->get_ascent(bm_font->font_size); - metrics->descender = bm_font->face->get_descent(bm_font->font_size); - metrics->line_gap = 0; - - return true; -} - -static hb_font_funcs_t *funcs = nullptr; -void hb_bmp_create_font_funcs() { - funcs = hb_font_funcs_create(); - - hb_font_funcs_set_font_h_extents_func(funcs, hb_bmp_get_font_h_extents, nullptr, nullptr); - //hb_font_funcs_set_font_v_extents_func (funcs, hb_bmp_get_font_v_extents, nullptr, nullptr); - hb_font_funcs_set_nominal_glyph_func(funcs, hb_bmp_get_nominal_glyph, nullptr, nullptr); - //hb_font_funcs_set_variation_glyph_func (funcs, hb_bmp_get_variation_glyph, nullptr, nullptr); - hb_font_funcs_set_glyph_h_advance_func(funcs, hb_bmp_get_glyph_h_advance, nullptr, nullptr); - hb_font_funcs_set_glyph_v_advance_func(funcs, hb_bmp_get_glyph_v_advance, nullptr, nullptr); - //hb_font_funcs_set_glyph_h_origin_func(funcs, hb_bmp_get_glyph_h_origin, nullptr, nullptr); - hb_font_funcs_set_glyph_v_origin_func(funcs, hb_bmp_get_glyph_v_origin, nullptr, nullptr); - hb_font_funcs_set_glyph_h_kerning_func(funcs, hb_bmp_get_glyph_h_kerning, nullptr, nullptr); - //hb_font_funcs_set_glyph_v_kerning_func (funcs, hb_bmp_get_glyph_v_kerning, nullptr, nullptr); - hb_font_funcs_set_glyph_extents_func(funcs, hb_bmp_get_glyph_extents, nullptr, nullptr); - //hb_font_funcs_set_glyph_contour_point_func (funcs, hb_bmp_get_glyph_contour_point, nullptr, nullptr); - //hb_font_funcs_set_glyph_name_func (funcs, hb_bmp_get_glyph_name, nullptr, nullptr); - //hb_font_funcs_set_glyph_from_name_func (funcs, hb_bmp_get_glyph_from_name, nullptr, nullptr); - - hb_font_funcs_make_immutable(funcs); -} - -void hb_bmp_free_font_funcs() { - if (funcs != nullptr) { - hb_font_funcs_destroy(funcs); - funcs = nullptr; - } -} - -static void _hb_bmp_font_set_funcs(hb_font_t *p_font, BitmapFontDataAdvanced *p_face, int p_size, bool p_unref) { - hb_font_set_funcs(p_font, funcs, _hb_bmp_font_create(p_face, p_size, p_unref), _hb_bmp_font_destroy); -} - -hb_font_t *hb_bmp_font_create(BitmapFontDataAdvanced *p_face, int p_size, hb_destroy_func_t p_destroy) { - hb_font_t *font; - hb_face_t *face = hb_face_create(nullptr, 0); - - font = hb_font_create(face); - hb_face_destroy(face); - _hb_bmp_font_set_funcs(font, p_face, p_size, false); - return font; -} - -/*************************************************************************/ -/* BitmapFontDataAdvanced */ -/*************************************************************************/ - -Error BitmapFontDataAdvanced::load_from_file(const String &p_filename, int p_base_size) { - _THREAD_SAFE_METHOD_ - //fnt format used by angelcode bmfont - //http://www.angelcode.com/products/bmfont/ - - FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!f, ERR_FILE_NOT_FOUND, "Can't open font: " + p_filename + "."); - - while (true) { - String line = f->get_line(); - - int delimiter = line.find(" "); - String type = line.substr(0, delimiter); - int pos = delimiter + 1; - Map<String, String> keys; - - while (pos < line.size() && line[pos] == ' ') { - pos++; - } - - while (pos < line.size()) { - int eq = line.find("=", pos); - if (eq == -1) { - break; - } - String key = line.substr(pos, eq - pos); - int end = -1; - String value; - if (line[eq + 1] == '"') { - end = line.find("\"", eq + 2); - if (end == -1) { - break; - } - value = line.substr(eq + 2, end - 1 - eq - 1); - pos = end + 1; - } else { - end = line.find(" ", eq + 1); - if (end == -1) { - end = line.size(); - } - value = line.substr(eq + 1, end - eq); - pos = end; - } - - while (pos < line.size() && line[pos] == ' ') { - pos++; - } - - keys[key] = value; - } - - if (type == "info") { - if (keys.has("size")) { - base_size = keys["size"].to_int(); - } - } else if (type == "common") { - if (keys.has("lineHeight")) { - height = keys["lineHeight"].to_int(); - } - if (keys.has("base")) { - ascent = keys["base"].to_int(); - } - } else if (type == "page") { - if (keys.has("file")) { - String base_dir = p_filename.get_base_dir(); - String file = base_dir.plus_file(keys["file"]); - if (RenderingServer::get_singleton() != nullptr) { - Ref<Texture2D> tex = ResourceLoader::load(file); - if (tex.is_null()) { - ERR_PRINT("Can't load font texture!"); - } else { - ERR_FAIL_COND_V_MSG(tex.is_null(), ERR_FILE_CANT_READ, "It's not a reference to a valid Texture object."); - textures.push_back(tex); - } - } - } - } else if (type == "char") { - Character c; - char32_t idx = 0; - if (keys.has("id")) { - idx = keys["id"].to_int(); - } - if (keys.has("x")) { - c.rect.position.x = keys["x"].to_int(); - } - if (keys.has("y")) { - c.rect.position.y = keys["y"].to_int(); - } - if (keys.has("width")) { - c.rect.size.width = keys["width"].to_int(); - } - if (keys.has("height")) { - c.rect.size.height = keys["height"].to_int(); - } - if (keys.has("xoffset")) { - c.align.x = keys["xoffset"].to_int(); - } - if (keys.has("yoffset")) { - c.align.y = keys["yoffset"].to_int(); - } - if (keys.has("page")) { - c.texture_idx = keys["page"].to_int(); - } - if (keys.has("xadvance")) { - c.advance.x = keys["xadvance"].to_int(); - } - if (keys.has("yadvance")) { - c.advance.x = keys["yadvance"].to_int(); - } - if (c.advance.x < 0) { - c.advance.x = c.rect.size.width + 1; - } - if (c.advance.y < 0) { - c.advance.y = c.rect.size.height + 1; - } - char_map[idx] = c; - } else if (type == "kerning") { - KerningPairKey kpk; - float k = 0.0; - if (keys.has("first")) { - kpk.A = keys["first"].to_int(); - } - if (keys.has("second")) { - kpk.B = keys["second"].to_int(); - } - if (keys.has("amount")) { - k = keys["amount"].to_int(); - } - kerning_map[kpk] = k; - } - - if (f->eof_reached()) { - break; - } - } - if (base_size == 0) { - base_size = height; - } - - if (hb_handle) { - hb_font_destroy(hb_handle); - } - hb_handle = hb_bmp_font_create(this, base_size, nullptr); - valid = true; - - memdelete(f); - return OK; -} - -Error BitmapFontDataAdvanced::bitmap_new(float p_height, float p_ascent, int p_base_size) { - height = p_height; - ascent = p_ascent; - - base_size = p_base_size; - if (base_size == 0) { - base_size = height; - } - - char_map.clear(); - textures.clear(); - kerning_map.clear(); - if (hb_handle) { - hb_font_destroy(hb_handle); - } - hb_handle = hb_bmp_font_create(this, base_size, nullptr); - valid = true; - - return OK; -} - -void BitmapFontDataAdvanced::bitmap_add_texture(const Ref<Texture> &p_texture) { - ERR_FAIL_COND(!valid); - ERR_FAIL_COND_MSG(p_texture.is_null(), "It's not a reference to a valid Texture object."); - - textures.push_back(p_texture); -} - -void BitmapFontDataAdvanced::bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { - ERR_FAIL_COND(!valid); - - Character chr; - chr.rect = p_rect; - chr.texture_idx = p_texture_idx; - if (p_advance < 0) { - chr.advance.x = chr.rect.size.x; - } else { - chr.advance.x = p_advance; - } - chr.align = p_align; - char_map[p_char] = chr; -} - -void BitmapFontDataAdvanced::bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) { - ERR_FAIL_COND(!valid); - - KerningPairKey kpk; - kpk.A = p_A; - kpk.B = p_B; - - if (p_kerning == 0 && kerning_map.has(kpk)) { - kerning_map.erase(kpk); - } else { - kerning_map[kpk] = p_kerning; - } -} - -float BitmapFontDataAdvanced::get_height(int p_size) const { - ERR_FAIL_COND_V(!valid, 0.f); - return height * (float(p_size) / float(base_size)); -} - -float BitmapFontDataAdvanced::get_ascent(int p_size) const { - ERR_FAIL_COND_V(!valid, 0.f); - return ascent * (float(p_size) / float(base_size)); -} - -float BitmapFontDataAdvanced::get_descent(int p_size) const { - ERR_FAIL_COND_V(!valid, 0.f); - return (height - ascent) * (float(p_size) / float(base_size)); -} - -float BitmapFontDataAdvanced::get_underline_position(int p_size) const { - ERR_FAIL_COND_V(!valid, 0.f); - return 2 * (float(p_size) / float(base_size)); -} - -float BitmapFontDataAdvanced::get_underline_thickness(int p_size) const { - ERR_FAIL_COND_V(!valid, 0.f); - return 1 * (float(p_size) / float(base_size)); -} - -void BitmapFontDataAdvanced::set_distance_field_hint(bool p_distance_field) { - distance_field_hint = p_distance_field; -} - -bool BitmapFontDataAdvanced::get_distance_field_hint() const { - return distance_field_hint; -} - -float BitmapFontDataAdvanced::get_base_size() const { - return base_size; -} - -hb_font_t *BitmapFontDataAdvanced::get_hb_handle(int p_size) { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(!valid, nullptr); - return hb_handle; -} - -bool BitmapFontDataAdvanced::has_char(char32_t p_char) const { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(!valid, false); - return char_map.has(p_char); -} - -String BitmapFontDataAdvanced::get_supported_chars() const { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(!valid, String()); - String chars; - const uint32_t *k = nullptr; - while ((k = char_map.next(k))) { - chars += char32_t(*k); - } - return chars; -} - -Vector2 BitmapFontDataAdvanced::get_advance(uint32_t p_char, int p_size) const { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(!valid, Vector2()); - const Character *c = char_map.getptr(p_char); - ERR_FAIL_COND_V(c == nullptr, Vector2()); - - return c->advance * (float(p_size) / float(base_size)); -} - -Vector2 BitmapFontDataAdvanced::get_align(uint32_t p_char, int p_size) const { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(!valid, Vector2()); - const Character *c = char_map.getptr(p_char); - ERR_FAIL_COND_V(c == nullptr, Vector2()); - - return c->align * (float(p_size) / float(base_size)); -} - -Vector2 BitmapFontDataAdvanced::get_size(uint32_t p_char, int p_size) const { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(!valid, Vector2()); - const Character *c = char_map.getptr(p_char); - ERR_FAIL_COND_V(c == nullptr, Vector2()); - - return c->rect.size * (float(p_size) / float(base_size)); -} - -float BitmapFontDataAdvanced::get_font_scale(int p_size) const { - return float(p_size) / float(base_size); -} - -Vector2 BitmapFontDataAdvanced::get_kerning(uint32_t p_char, uint32_t p_next, int p_size) const { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(!valid, Vector2()); - KerningPairKey kpk; - kpk.A = p_char; - kpk.B = p_next; - - const Map<KerningPairKey, int>::Element *E = kerning_map.find(kpk); - if (E) { - return Vector2(-E->get() * (float(p_size) / float(base_size)), 0.f); - } else { - return Vector2(); - } -} - -Vector2 BitmapFontDataAdvanced::draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - if (p_index == 0) { - return Vector2(); - } - ERR_FAIL_COND_V(!valid, Vector2()); - const Character *c = char_map.getptr(p_index); - - ERR_FAIL_COND_V(c == nullptr, Vector2()); - ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), Vector2()); - if (c->texture_idx != -1) { - Point2i cpos = p_pos; - cpos += (c->align + Vector2(0, -ascent)) * (float(p_size) / float(base_size)); - Size2i csize = c->rect.size * (float(p_size) / float(base_size)); - if (RenderingServer::get_singleton() != nullptr) { - //if (distance_field_hint) { // Not implemented. - // RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(p_canvas, true); - //} - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), textures[c->texture_idx]->get_rid(), c->rect, p_color, false, false); - //if (distance_field_hint) { - // RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(p_canvas, false); - //} - } - } - - return c->advance * (float(p_size) / float(base_size)); -} - -Vector2 BitmapFontDataAdvanced::draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - if (p_index == 0) { - return Vector2(); - } - ERR_FAIL_COND_V(!valid, Vector2()); - const Character *c = char_map.getptr(p_index); - - ERR_FAIL_COND_V(c == nullptr, Vector2()); - ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), Vector2()); - - // Not supported, return advance for compatibility. - - return c->advance * (float(p_size) / float(base_size)); -} - -BitmapFontDataAdvanced::~BitmapFontDataAdvanced() { - if (hb_handle) { - hb_font_destroy(hb_handle); - } -} diff --git a/modules/text_server_adv/bitmap_font_adv.h b/modules/text_server_adv/bitmap_font_adv.h deleted file mode 100644 index 7b620021e1..0000000000 --- a/modules/text_server_adv/bitmap_font_adv.h +++ /dev/null @@ -1,123 +0,0 @@ -/*************************************************************************/ -/* bitmap_font_adv.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 BITMAP_FONT_ADV_H -#define BITMAP_FONT_ADV_H - -#include "font_adv.h" - -void hb_bmp_create_font_funcs(); -void hb_bmp_free_font_funcs(); - -struct BitmapFontDataAdvanced : public FontDataAdvanced { - _THREAD_SAFE_CLASS_ - -private: - Vector<Ref<Texture2D>> textures; - - struct Character { - int texture_idx = 0; - Rect2 rect; - Vector2 align; - Vector2 advance = Vector2(-1, -1); - }; - - struct KerningPairKey { - union { - struct { - uint32_t A, B; - }; - - uint64_t pair = 0; - }; - - _FORCE_INLINE_ bool operator<(const KerningPairKey &p_r) const { return pair < p_r.pair; } - }; - - HashMap<uint32_t, Character> char_map; - Map<KerningPairKey, int> kerning_map; - hb_font_t *hb_handle = nullptr; - - float height = 0.f; - float ascent = 0.f; - int base_size = 0; - bool distance_field_hint = false; - -public: - virtual void clear_cache() override{}; - - virtual Error load_from_file(const String &p_filename, int p_base_size) override; - virtual Error bitmap_new(float p_height, float p_ascent, int p_base_size) override; - - virtual void bitmap_add_texture(const Ref<Texture> &p_texture) override; - virtual void bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) override; - virtual void bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) override; - - virtual float get_height(int p_size) const override; - virtual float get_ascent(int p_size) const override; - virtual float get_descent(int p_size) const override; - - virtual float get_underline_position(int p_size) const override; - virtual float get_underline_thickness(int p_size) const override; - - virtual void set_antialiased(bool p_antialiased) override{}; - virtual bool get_antialiased() const override { return false; }; - - virtual void set_hinting(TextServer::Hinting p_hinting) override{}; - virtual TextServer::Hinting get_hinting() const override { return TextServer::HINTING_NONE; }; - - virtual void set_distance_field_hint(bool p_distance_field) override; - virtual bool get_distance_field_hint() const override; - - virtual void set_force_autohinter(bool p_enabeld) override{}; - virtual bool get_force_autohinter() const override { return false; }; - - virtual bool has_outline() const override { return false; }; - virtual float get_base_size() const override; - virtual float get_font_scale(int p_size) const override; - - virtual hb_font_t *get_hb_handle(int p_size) override; - - virtual bool has_char(char32_t p_char) const override; - virtual String get_supported_chars() const override; - - virtual Vector2 get_advance(uint32_t p_char, int p_size) const override; - Vector2 get_align(uint32_t p_char, int p_size) const; - Vector2 get_size(uint32_t p_char, int p_size) const; - virtual Vector2 get_kerning(uint32_t p_char, uint32_t p_next, int p_size) const override; - virtual uint32_t get_glyph_index(char32_t p_char, char32_t p_variation_selector) const override { return (uint32_t)p_char; }; - - virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; - virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; - - virtual ~BitmapFontDataAdvanced(); -}; - -#endif // BITMAP_FONT_ADV_H diff --git a/modules/text_server_adv/dynamic_font_adv.cpp b/modules/text_server_adv/dynamic_font_adv.cpp deleted file mode 100644 index 62eedebb59..0000000000 --- a/modules/text_server_adv/dynamic_font_adv.cpp +++ /dev/null @@ -1,1030 +0,0 @@ -/*************************************************************************/ -/* dynamic_font_adv.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 "dynamic_font_adv.h" - -#ifdef MODULE_FREETYPE_ENABLED - -#include FT_STROKER_H -#include FT_ADVANCES_H -#include FT_MULTIPLE_MASTERS_H - -DynamicFontDataAdvanced::DataAtSize *DynamicFontDataAdvanced::get_data_for_size(int p_size, int p_outline_size) { - ERR_FAIL_COND_V(!valid, nullptr); - ERR_FAIL_COND_V(p_size < 0 || p_size > UINT16_MAX, nullptr); - ERR_FAIL_COND_V(p_outline_size < 0 || p_outline_size > UINT16_MAX, nullptr); - - CacheID id; - id.size = p_size; - id.outline_size = p_outline_size; - - DataAtSize *fds = nullptr; - Map<CacheID, DataAtSize *>::Element *E = nullptr; - if (p_outline_size != 0) { - E = size_cache_outline.find(id); - } else { - E = size_cache.find(id); - } - - if (E != nullptr) { - fds = E->get(); - } else { - if (font_mem == nullptr && font_path != String()) { - if (!font_mem_cache.is_empty()) { - font_mem = font_mem_cache.ptr(); - font_mem_size = font_mem_cache.size(); - } else { - FileAccess *f = FileAccess::open(font_path, FileAccess::READ); - if (!f) { - ERR_FAIL_V_MSG(nullptr, "Cannot open font file '" + font_path + "'."); - } - - uint64_t len = f->get_length(); - font_mem_cache.resize(len); - f->get_buffer(font_mem_cache.ptrw(), len); - font_mem = font_mem_cache.ptr(); - font_mem_size = len; - f->close(); - } - } - - int error = 0; - fds = memnew(DataAtSize); - if (font_mem) { - memset(&fds->stream, 0, sizeof(FT_StreamRec)); - fds->stream.base = (unsigned char *)font_mem; - fds->stream.size = font_mem_size; - fds->stream.pos = 0; - - FT_Open_Args fargs; - memset(&fargs, 0, sizeof(FT_Open_Args)); - fargs.memory_base = (unsigned char *)font_mem; - fargs.memory_size = font_mem_size; - fargs.flags = FT_OPEN_MEMORY; - fargs.stream = &fds->stream; - error = FT_Open_Face(library, &fargs, 0, &fds->face); - - } else { - memdelete(fds); - ERR_FAIL_V_MSG(nullptr, "DynamicFont uninitialized."); - } - - if (error == FT_Err_Unknown_File_Format) { - memdelete(fds); - ERR_FAIL_V_MSG(nullptr, "Unknown font format."); - } else if (error) { - memdelete(fds); - ERR_FAIL_V_MSG(nullptr, "Error loading font."); - } - - oversampling = TS->font_get_oversampling(); - - if (FT_HAS_COLOR(fds->face) && fds->face->num_fixed_sizes > 0) { - int best_match = 0; - int diff = ABS(p_size - ((int64_t)fds->face->available_sizes[0].width)); - fds->scale_color_font = float(p_size * oversampling) / fds->face->available_sizes[0].width; - for (int i = 1; i < fds->face->num_fixed_sizes; i++) { - int ndiff = ABS(p_size - ((int64_t)fds->face->available_sizes[i].width)); - if (ndiff < diff) { - best_match = i; - diff = ndiff; - fds->scale_color_font = float(p_size * oversampling) / fds->face->available_sizes[i].width; - } - } - FT_Select_Size(fds->face, best_match); - } else { - FT_Set_Pixel_Sizes(fds->face, 0, p_size * oversampling); - } - - fds->size = p_size; - fds->ascent = (fds->face->size->metrics.ascender / 64.0) / oversampling * fds->scale_color_font; - fds->descent = (-fds->face->size->metrics.descender / 64.0) / oversampling * fds->scale_color_font; - fds->underline_position = (-FT_MulFix(fds->face->underline_position, fds->face->size->metrics.y_scale) / 64.0) / oversampling * fds->scale_color_font; - fds->underline_thickness = (FT_MulFix(fds->face->underline_thickness, fds->face->size->metrics.y_scale) / 64.0) / oversampling * fds->scale_color_font; - - //Load os2 TTF table - fds->os2 = (TT_OS2 *)FT_Get_Sfnt_Table(fds->face, FT_SFNT_OS2); - - fds->hb_handle = hb_ft_font_create(fds->face, nullptr); - if (fds->hb_handle == nullptr) { - memdelete(fds); - ERR_FAIL_V_MSG(nullptr, "Error loading HB font."); - } - - if (p_outline_size != 0) { - size_cache_outline[id] = fds; - } else { - size_cache[id] = fds; - } - - // Write variations. - if (fds->face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { - FT_MM_Var *amaster; - - FT_Get_MM_Var(fds->face, &amaster); - - Vector<hb_variation_t> hb_vars; - Vector<FT_Fixed> coords; - coords.resize(amaster->num_axis); - - FT_Get_Var_Design_Coordinates(fds->face, coords.size(), coords.ptrw()); - - for (FT_UInt i = 0; i < amaster->num_axis; i++) { - hb_variation_t var; - - // Reset to default. - var.tag = amaster->axis[i].tag; - var.value = (double)amaster->axis[i].def / 65536.f; - coords.write[i] = amaster->axis[i].def; - - if (variations.has(var.tag)) { - var.value = variations[var.tag]; - coords.write[i] = CLAMP(variations[var.tag] * 65536.f, amaster->axis[i].minimum, amaster->axis[i].maximum); - } - - hb_vars.push_back(var); - } - - FT_Set_Var_Design_Coordinates(fds->face, coords.size(), coords.ptrw()); - hb_font_set_variations(fds->hb_handle, hb_vars.is_empty() ? nullptr : &hb_vars[0], hb_vars.size()); - - FT_Done_MM_Var(library, amaster); - } - } - return fds; -} - -Dictionary DynamicFontDataAdvanced::get_variation_list() const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(base_size); - if (fds == nullptr) { - return Dictionary(); - } - - Dictionary ret; - // Read variations. - if (fds->face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { - FT_MM_Var *amaster; - - FT_Get_MM_Var(fds->face, &amaster); - - for (FT_UInt i = 0; i < amaster->num_axis; i++) { - ret[(int32_t)amaster->axis[i].tag] = Vector3i(amaster->axis[i].minimum / 65536, amaster->axis[i].maximum / 65536, amaster->axis[i].def / 65536); - } - - FT_Done_MM_Var(library, amaster); - } - return ret; -} - -void DynamicFontDataAdvanced::set_variation(const String &p_name, double p_value) { - _THREAD_SAFE_METHOD_ - int32_t tag = TS->name_to_tag(p_name); - if (!variations.has(tag) || (variations[tag] != p_value)) { - variations[tag] = p_value; - clear_cache(); - } -} - -double DynamicFontDataAdvanced::get_variation(const String &p_name) const { - _THREAD_SAFE_METHOD_ - int32_t tag = TS->name_to_tag(p_name); - if (!variations.has(tag)) { - return 0.f; - } - return variations[tag]; -} - -Dictionary DynamicFontDataAdvanced::get_feature_list() const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(base_size); - if (fds == nullptr) { - return Dictionary(); - } - - Dictionary out; - // Read feature flags. - unsigned int count = hb_ot_layout_table_get_feature_tags(hb_font_get_face(fds->hb_handle), HB_OT_TAG_GSUB, 0, nullptr, nullptr); - if (count != 0) { - hb_tag_t *feature_tags = (hb_tag_t *)memalloc(count * sizeof(hb_tag_t)); - hb_ot_layout_table_get_feature_tags(hb_font_get_face(fds->hb_handle), HB_OT_TAG_GSUB, 0, &count, feature_tags); - for (unsigned int i = 0; i < count; i++) { - out[feature_tags[i]] = 1; - } - memfree(feature_tags); - } - count = hb_ot_layout_table_get_feature_tags(hb_font_get_face(fds->hb_handle), HB_OT_TAG_GPOS, 0, nullptr, nullptr); - if (count != 0) { - hb_tag_t *feature_tags = (hb_tag_t *)memalloc(count * sizeof(hb_tag_t)); - hb_ot_layout_table_get_feature_tags(hb_font_get_face(fds->hb_handle), HB_OT_TAG_GPOS, 0, &count, feature_tags); - for (unsigned int i = 0; i < count; i++) { - out[feature_tags[i]] = 1; - } - memfree(feature_tags); - } - - return out; -} - -DynamicFontDataAdvanced::TexturePosition DynamicFontDataAdvanced::find_texture_pos_for_glyph(DynamicFontDataAdvanced::DataAtSize *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) { - TexturePosition ret; - ret.index = -1; - - int mw = p_width; - int mh = p_height; - - for (int i = 0; i < p_data->textures.size(); i++) { - const CharTexture &ct = p_data->textures[i]; - - if (RenderingServer::get_singleton() != nullptr) { - if (ct.texture->get_format() != p_image_format) { - continue; - } - } - - if (mw > ct.texture_size || mh > ct.texture_size) { //too big for this texture - continue; - } - - ret.y = 0x7FFFFFFF; - ret.x = 0; - - for (int j = 0; j < ct.texture_size - mw; j++) { - int max_y = 0; - - for (int k = j; k < j + mw; k++) { - int y = ct.offsets[k]; - if (y > max_y) { - max_y = y; - } - } - - if (max_y < ret.y) { - ret.y = max_y; - ret.x = j; - } - } - - if (ret.y == 0x7FFFFFFF || ret.y + mh > ct.texture_size) { - continue; //fail, could not fit it here - } - - ret.index = i; - break; - } - - if (ret.index == -1) { - //could not find texture to fit, create one - ret.x = 0; - ret.y = 0; - - int texsize = MAX(p_data->size * oversampling * 8, 256); - if (mw > texsize) { - texsize = mw; //special case, adapt to it? - } - if (mh > texsize) { - texsize = mh; //special case, adapt to it? - } - - texsize = next_power_of_2(texsize); - - texsize = MIN(texsize, 4096); - - CharTexture tex; - tex.texture_size = texsize; - tex.imgdata.resize(texsize * texsize * p_color_size); //grayscale alpha - - { - //zero texture - uint8_t *w = tex.imgdata.ptrw(); - ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.imgdata.size(), ret); - // Initialize the texture to all-white pixels to prevent artifacts when the - // font is displayed at a non-default scale with filtering enabled. - if (p_color_size == 2) { - for (int i = 0; i < texsize * texsize * p_color_size; i += 2) { // FORMAT_LA8 - w[i + 0] = 255; - w[i + 1] = 0; - } - } else if (p_color_size == 4) { - for (int i = 0; i < texsize * texsize * p_color_size; i += 4) { // FORMAT_RGBA8 - w[i + 0] = 255; - w[i + 1] = 255; - w[i + 2] = 255; - w[i + 3] = 0; - } - } else { - ERR_FAIL_V(ret); - } - } - tex.offsets.resize(texsize); - for (int i = 0; i < texsize; i++) { //zero offsets - tex.offsets.write[i] = 0; - } - - p_data->textures.push_back(tex); - ret.index = p_data->textures.size() - 1; - } - - return ret; -} - -DynamicFontDataAdvanced::Character DynamicFontDataAdvanced::Character::not_found() { - Character ch; - return ch; -} - -DynamicFontDataAdvanced::Character DynamicFontDataAdvanced::bitmap_to_character(DynamicFontDataAdvanced::DataAtSize *p_data, FT_Bitmap bitmap, int yofs, int xofs, const Vector2 &advance) { - int w = bitmap.width; - int h = bitmap.rows; - - int mw = w + rect_margin * 2; - int mh = h + rect_margin * 2; - - ERR_FAIL_COND_V(mw > 4096, Character::not_found()); - ERR_FAIL_COND_V(mh > 4096, Character::not_found()); - - int color_size = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2; - Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8; - - TexturePosition tex_pos = find_texture_pos_for_glyph(p_data, color_size, require_format, mw, mh); - ERR_FAIL_COND_V(tex_pos.index < 0, Character::not_found()); - - //fit character in char texture - - CharTexture &tex = p_data->textures.write[tex_pos.index]; - - { - uint8_t *wr = tex.imgdata.ptrw(); - - for (int i = 0; i < h; i++) { - for (int j = 0; j < w; j++) { - int ofs = ((i + tex_pos.y + rect_margin) * tex.texture_size + j + tex_pos.x + rect_margin) * color_size; - ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), Character::not_found()); - switch (bitmap.pixel_mode) { - case FT_PIXEL_MODE_MONO: { - int byte = i * bitmap.pitch + (j >> 3); - int bit = 1 << (7 - (j % 8)); - wr[ofs + 0] = 255; //grayscale as 1 - wr[ofs + 1] = (bitmap.buffer[byte] & bit) ? 255 : 0; - } break; - case FT_PIXEL_MODE_GRAY: - wr[ofs + 0] = 255; //grayscale as 1 - wr[ofs + 1] = bitmap.buffer[i * bitmap.pitch + j]; - break; - case FT_PIXEL_MODE_BGRA: { - int ofs_color = i * bitmap.pitch + (j << 2); - wr[ofs + 2] = bitmap.buffer[ofs_color + 0]; - wr[ofs + 1] = bitmap.buffer[ofs_color + 1]; - wr[ofs + 0] = bitmap.buffer[ofs_color + 2]; - wr[ofs + 3] = bitmap.buffer[ofs_color + 3]; - } break; - // TODO: FT_PIXEL_MODE_LCD - default: - ERR_FAIL_V_MSG(Character::not_found(), "Font uses unsupported pixel format: " + itos(bitmap.pixel_mode) + "."); - break; - } - } - } - } - - //blit to image and texture - { - if (RenderingServer::get_singleton() != nullptr) { - Ref<Image> img = memnew(Image(tex.texture_size, tex.texture_size, 0, require_format, tex.imgdata)); - - if (tex.texture.is_null()) { - tex.texture.instantiate(); - tex.texture->create_from_image(img); - } else { - tex.texture->update(img); //update - } - } - } - - // update height array - for (int k = tex_pos.x; k < tex_pos.x + mw; k++) { - tex.offsets.write[k] = tex_pos.y + mh; - } - - Character chr; - chr.align = (Vector2(xofs, -yofs) * p_data->scale_color_font / oversampling).round(); - chr.advance = (advance * p_data->scale_color_font / oversampling).round(); - chr.texture_idx = tex_pos.index; - chr.found = true; - - chr.rect_uv = Rect2(tex_pos.x + rect_margin, tex_pos.y + rect_margin, w, h); - chr.rect = chr.rect_uv; - chr.rect.position /= oversampling; - chr.rect.size *= (p_data->scale_color_font / oversampling); - return chr; -} - -void DynamicFontDataAdvanced::update_glyph(int p_size, uint32_t p_index) { - DataAtSize *fds = get_data_for_size(p_size, false); - ERR_FAIL_COND(fds == nullptr); - - if (fds->glyph_map.has(p_index)) { - return; - } - - Character character = Character::not_found(); - FT_GlyphSlot slot = fds->face->glyph; - - if (p_index == 0) { - fds->glyph_map[p_index] = character; - return; - } - - int ft_hinting; - switch (hinting) { - case TextServer::HINTING_NONE: - ft_hinting = FT_LOAD_NO_HINTING; - break; - case TextServer::HINTING_LIGHT: - ft_hinting = FT_LOAD_TARGET_LIGHT; - break; - default: - ft_hinting = FT_LOAD_TARGET_NORMAL; - break; - } - - FT_Fixed v, h; - FT_Get_Advance(fds->face, p_index, FT_HAS_COLOR(fds->face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting, &h); - FT_Get_Advance(fds->face, p_index, FT_HAS_COLOR(fds->face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting | FT_LOAD_VERTICAL_LAYOUT, &v); - - int error = FT_Load_Glyph(fds->face, p_index, FT_HAS_COLOR(fds->face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting); - if (error) { - fds->glyph_map[p_index] = character; - return; - } - - error = FT_Render_Glyph(fds->face->glyph, antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); - if (!error) { - character = bitmap_to_character(fds, slot->bitmap, slot->bitmap_top, slot->bitmap_left, Vector2((h + (1 << 9)) >> 10, (v + (1 << 9)) >> 10) / 64.0); - } - - fds->glyph_map[p_index] = character; -} - -void DynamicFontDataAdvanced::update_glyph_outline(int p_size, int p_outline_size, uint32_t p_index) { - DataAtSize *fds = get_data_for_size(p_size, p_outline_size); - ERR_FAIL_COND(fds == nullptr); - - if (fds->glyph_map.has(p_index)) { - return; - } - - Character character = Character::not_found(); - if (p_index == 0) { - fds->glyph_map[p_index] = character; - return; - } - - int error = FT_Load_Glyph(fds->face, p_index, FT_LOAD_NO_BITMAP | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); - if (error) { - fds->glyph_map[p_index] = character; - return; - } - - FT_Stroker stroker; - if (FT_Stroker_New(library, &stroker) != 0) { - fds->glyph_map[p_index] = character; - return; - } - - FT_Stroker_Set(stroker, (int)(p_outline_size * oversampling * 64.0), FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0); - FT_Glyph glyph; - FT_BitmapGlyph glyph_bitmap; - - if (FT_Get_Glyph(fds->face->glyph, &glyph) != 0) { - goto cleanup_stroker; - } - if (FT_Glyph_Stroke(&glyph, stroker, 1) != 0) { - goto cleanup_glyph; - } - if (FT_Glyph_To_Bitmap(&glyph, antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, nullptr, 1) != 0) { - goto cleanup_glyph; - } - - glyph_bitmap = (FT_BitmapGlyph)glyph; - character = bitmap_to_character(fds, glyph_bitmap->bitmap, glyph_bitmap->top, glyph_bitmap->left, Vector2()); - -cleanup_glyph: - FT_Done_Glyph(glyph); -cleanup_stroker: - FT_Stroker_Done(stroker); - - fds->glyph_map[p_index] = character; -} - -void DynamicFontDataAdvanced::clear_cache() { - _THREAD_SAFE_METHOD_ - for (Map<CacheID, DataAtSize *>::Element *E = size_cache.front(); E; E = E->next()) { - memdelete(E->get()); - } - size_cache.clear(); - for (Map<CacheID, DataAtSize *>::Element *E = size_cache_outline.front(); E; E = E->next()) { - memdelete(E->get()); - } - size_cache_outline.clear(); -} - -Error DynamicFontDataAdvanced::load_from_file(const String &p_filename, int p_base_size) { - _THREAD_SAFE_METHOD_ - if (library == nullptr) { - int error = FT_Init_FreeType(&library); - ERR_FAIL_COND_V_MSG(error != 0, ERR_CANT_CREATE, "Error initializing FreeType."); - } - clear_cache(); - - font_path = p_filename; - base_size = p_base_size; - - valid = true; - DataAtSize *fds = get_data_for_size(base_size); // load base size. - if (fds == nullptr) { - valid = false; - ERR_FAIL_V(ERR_CANT_CREATE); - } - - return OK; -} - -Error DynamicFontDataAdvanced::load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) { - _THREAD_SAFE_METHOD_ - if (library == nullptr) { - int error = FT_Init_FreeType(&library); - ERR_FAIL_COND_V_MSG(error != 0, ERR_CANT_CREATE, "Error initializing FreeType."); - } - clear_cache(); - - font_mem = p_data; - font_mem_size = p_size; - base_size = p_base_size; - - valid = true; - DataAtSize *fds = get_data_for_size(base_size); // load base size. - if (fds == nullptr) { - valid = false; - ERR_FAIL_V(ERR_CANT_CREATE); - } - - return OK; -} - -float DynamicFontDataAdvanced::get_height(int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, 0.f); - return fds->ascent + fds->descent; -} - -float DynamicFontDataAdvanced::get_ascent(int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, 0.f); - return fds->ascent; -} - -float DynamicFontDataAdvanced::get_descent(int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, 0.f); - return fds->descent; -} - -float DynamicFontDataAdvanced::get_underline_position(int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, 0.f); - return fds->underline_position; -} - -float DynamicFontDataAdvanced::get_underline_thickness(int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, 0.f); - return fds->underline_thickness; -} - -bool DynamicFontDataAdvanced::is_script_supported(uint32_t p_script) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(base_size); - ERR_FAIL_COND_V(fds == nullptr, false); - - unsigned int count = hb_ot_layout_table_get_script_tags(hb_font_get_face(fds->hb_handle), HB_OT_TAG_GSUB, 0, nullptr, nullptr); - if (count != 0) { - hb_tag_t *script_tags = (hb_tag_t *)memalloc(count * sizeof(hb_tag_t)); - hb_ot_layout_table_get_script_tags(hb_font_get_face(fds->hb_handle), HB_OT_TAG_GSUB, 0, &count, script_tags); - for (unsigned int i = 0; i < count; i++) { - if (p_script == script_tags[i]) { - memfree(script_tags); - return true; - } - } - memfree(script_tags); - } - count = hb_ot_layout_table_get_script_tags(hb_font_get_face(fds->hb_handle), HB_OT_TAG_GPOS, 0, nullptr, nullptr); - if (count != 0) { - hb_tag_t *script_tags = (hb_tag_t *)memalloc(count * sizeof(hb_tag_t)); - hb_ot_layout_table_get_script_tags(hb_font_get_face(fds->hb_handle), HB_OT_TAG_GPOS, 0, &count, script_tags); - for (unsigned int i = 0; i < count; i++) { - if (p_script == script_tags[i]) { - memfree(script_tags); - return true; - } - } - memfree(script_tags); - } - - if (!fds->os2) { - return false; - } - - switch (p_script) { - case HB_SCRIPT_COMMON: - return (fds->os2->ulUnicodeRange1 & 1L << 4) || (fds->os2->ulUnicodeRange1 & 1L << 5) || (fds->os2->ulUnicodeRange1 & 1L << 6) || (fds->os2->ulUnicodeRange1 & 1L << 31) || (fds->os2->ulUnicodeRange2 & 1L << 0) || (fds->os2->ulUnicodeRange2 & 1L << 1) || (fds->os2->ulUnicodeRange2 & 1L << 2) || (fds->os2->ulUnicodeRange2 & 1L << 3) || (fds->os2->ulUnicodeRange2 & 1L << 4) || (fds->os2->ulUnicodeRange2 & 1L << 5) || (fds->os2->ulUnicodeRange2 & 1L << 6) || (fds->os2->ulUnicodeRange2 & 1L << 7) || (fds->os2->ulUnicodeRange2 & 1L << 8) || (fds->os2->ulUnicodeRange2 & 1L << 9) || (fds->os2->ulUnicodeRange2 & 1L << 10) || (fds->os2->ulUnicodeRange2 & 1L << 11) || (fds->os2->ulUnicodeRange2 & 1L << 12) || (fds->os2->ulUnicodeRange2 & 1L << 13) || (fds->os2->ulUnicodeRange2 & 1L << 14) || (fds->os2->ulUnicodeRange2 & 1L << 15) || (fds->os2->ulUnicodeRange2 & 1L << 30) || (fds->os2->ulUnicodeRange3 & 1L << 0) || (fds->os2->ulUnicodeRange3 & 1L << 1) || (fds->os2->ulUnicodeRange3 & 1L << 2) || (fds->os2->ulUnicodeRange3 & 1L << 4) || (fds->os2->ulUnicodeRange3 & 1L << 5) || (fds->os2->ulUnicodeRange3 & 1L << 18) || (fds->os2->ulUnicodeRange3 & 1L << 24) || (fds->os2->ulUnicodeRange3 & 1L << 25) || (fds->os2->ulUnicodeRange3 & 1L << 26) || (fds->os2->ulUnicodeRange3 & 1L << 27) || (fds->os2->ulUnicodeRange3 & 1L << 28) || (fds->os2->ulUnicodeRange4 & 1L << 3) || (fds->os2->ulUnicodeRange4 & 1L << 6) || (fds->os2->ulUnicodeRange4 & 1L << 15) || (fds->os2->ulUnicodeRange4 & 1L << 23) || (fds->os2->ulUnicodeRange4 & 1L << 24) || (fds->os2->ulUnicodeRange4 & 1L << 26); - case HB_SCRIPT_LATIN: - return (fds->os2->ulUnicodeRange1 & 1L << 0) || (fds->os2->ulUnicodeRange1 & 1L << 1) || (fds->os2->ulUnicodeRange1 & 1L << 2) || (fds->os2->ulUnicodeRange1 & 1L << 3) || (fds->os2->ulUnicodeRange1 & 1L << 29); - case HB_SCRIPT_GREEK: - return (fds->os2->ulUnicodeRange1 & 1L << 7) || (fds->os2->ulUnicodeRange1 & 1L << 30); - case HB_SCRIPT_COPTIC: - return (fds->os2->ulUnicodeRange1 & 1L << 8); - case HB_SCRIPT_CYRILLIC: - return (fds->os2->ulUnicodeRange1 & 1L << 9); - case HB_SCRIPT_ARMENIAN: - return (fds->os2->ulUnicodeRange1 & 1L << 10); - case HB_SCRIPT_HEBREW: - return (fds->os2->ulUnicodeRange1 & 1L << 11); - case HB_SCRIPT_VAI: - return (fds->os2->ulUnicodeRange1 & 1L << 12); - case HB_SCRIPT_ARABIC: - return (fds->os2->ulUnicodeRange1 & 1L << 13) || (fds->os2->ulUnicodeRange2 & 1L << 31) || (fds->os2->ulUnicodeRange3 & 1L << 3); - case HB_SCRIPT_NKO: - return (fds->os2->ulUnicodeRange1 & 1L << 14); - case HB_SCRIPT_DEVANAGARI: - return (fds->os2->ulUnicodeRange1 & 1L << 15); - case HB_SCRIPT_BENGALI: - return (fds->os2->ulUnicodeRange1 & 1L << 16); - case HB_SCRIPT_GURMUKHI: - return (fds->os2->ulUnicodeRange1 & 1L << 17); - case HB_SCRIPT_GUJARATI: - return (fds->os2->ulUnicodeRange1 & 1L << 18); - case HB_SCRIPT_ORIYA: - return (fds->os2->ulUnicodeRange1 & 1L << 19); - case HB_SCRIPT_TAMIL: - return (fds->os2->ulUnicodeRange1 & 1L << 20); - case HB_SCRIPT_TELUGU: - return (fds->os2->ulUnicodeRange1 & 1L << 21); - case HB_SCRIPT_KANNADA: - return (fds->os2->ulUnicodeRange1 & 1L << 22); - case HB_SCRIPT_MALAYALAM: - return (fds->os2->ulUnicodeRange1 & 1L << 23); - case HB_SCRIPT_THAI: - return (fds->os2->ulUnicodeRange1 & 1L << 24); - case HB_SCRIPT_LAO: - return (fds->os2->ulUnicodeRange1 & 1L << 25); - case HB_SCRIPT_GEORGIAN: - return (fds->os2->ulUnicodeRange1 & 1L << 26); - case HB_SCRIPT_BALINESE: - return (fds->os2->ulUnicodeRange1 & 1L << 27); - case HB_SCRIPT_HANGUL: - return (fds->os2->ulUnicodeRange1 & 1L << 28) || (fds->os2->ulUnicodeRange2 & 1L << 20) || (fds->os2->ulUnicodeRange2 & 1L << 24); - case HB_SCRIPT_HAN: - return (fds->os2->ulUnicodeRange2 & 1L << 21) || (fds->os2->ulUnicodeRange2 & 1L << 22) || (fds->os2->ulUnicodeRange2 & 1L << 23) || (fds->os2->ulUnicodeRange2 & 1L << 26) || (fds->os2->ulUnicodeRange2 & 1L << 27) || (fds->os2->ulUnicodeRange2 & 1L << 29); - case HB_SCRIPT_HIRAGANA: - return (fds->os2->ulUnicodeRange2 & 1L << 17); - case HB_SCRIPT_KATAKANA: - return (fds->os2->ulUnicodeRange2 & 1L << 18); - case HB_SCRIPT_BOPOMOFO: - return (fds->os2->ulUnicodeRange2 & 1L << 19); - case HB_SCRIPT_TIBETAN: - return (fds->os2->ulUnicodeRange3 & 1L << 6); - case HB_SCRIPT_SYRIAC: - return (fds->os2->ulUnicodeRange3 & 1L << 7); - case HB_SCRIPT_THAANA: - return (fds->os2->ulUnicodeRange3 & 1L << 8); - case HB_SCRIPT_SINHALA: - return (fds->os2->ulUnicodeRange3 & 1L << 9); - case HB_SCRIPT_MYANMAR: - return (fds->os2->ulUnicodeRange3 & 1L << 10); - case HB_SCRIPT_ETHIOPIC: - return (fds->os2->ulUnicodeRange3 & 1L << 11); - case HB_SCRIPT_CHEROKEE: - return (fds->os2->ulUnicodeRange3 & 1L << 12); - case HB_SCRIPT_CANADIAN_SYLLABICS: - return (fds->os2->ulUnicodeRange3 & 1L << 13); - case HB_SCRIPT_OGHAM: - return (fds->os2->ulUnicodeRange3 & 1L << 14); - case HB_SCRIPT_RUNIC: - return (fds->os2->ulUnicodeRange3 & 1L << 15); - case HB_SCRIPT_KHMER: - return (fds->os2->ulUnicodeRange3 & 1L << 16); - case HB_SCRIPT_MONGOLIAN: - return (fds->os2->ulUnicodeRange3 & 1L << 17); - case HB_SCRIPT_YI: - return (fds->os2->ulUnicodeRange3 & 1L << 19); - case HB_SCRIPT_HANUNOO: - case HB_SCRIPT_TAGBANWA: - case HB_SCRIPT_BUHID: - case HB_SCRIPT_TAGALOG: - return (fds->os2->ulUnicodeRange3 & 1L << 20); - case HB_SCRIPT_OLD_ITALIC: - return (fds->os2->ulUnicodeRange3 & 1L << 21); - case HB_SCRIPT_GOTHIC: - return (fds->os2->ulUnicodeRange3 & 1L << 22); - case HB_SCRIPT_DESERET: - return (fds->os2->ulUnicodeRange3 & 1L << 23); - case HB_SCRIPT_LIMBU: - return (fds->os2->ulUnicodeRange3 & 1L << 29); - case HB_SCRIPT_TAI_LE: - return (fds->os2->ulUnicodeRange3 & 1L << 30); - case HB_SCRIPT_NEW_TAI_LUE: - return (fds->os2->ulUnicodeRange3 & 1L << 31); - case HB_SCRIPT_BUGINESE: - return (fds->os2->ulUnicodeRange4 & 1L << 0); - case HB_SCRIPT_GLAGOLITIC: - return (fds->os2->ulUnicodeRange4 & 1L << 1); - case HB_SCRIPT_TIFINAGH: - return (fds->os2->ulUnicodeRange4 & 1L << 2); - case HB_SCRIPT_SYLOTI_NAGRI: - return (fds->os2->ulUnicodeRange4 & 1L << 4); - case HB_SCRIPT_LINEAR_B: - return (fds->os2->ulUnicodeRange4 & 1L << 5); - case HB_SCRIPT_UGARITIC: - return (fds->os2->ulUnicodeRange4 & 1L << 7); - case HB_SCRIPT_OLD_PERSIAN: - return (fds->os2->ulUnicodeRange4 & 1L << 8); - case HB_SCRIPT_SHAVIAN: - return (fds->os2->ulUnicodeRange4 & 1L << 9); - case HB_SCRIPT_OSMANYA: - return (fds->os2->ulUnicodeRange4 & 1L << 10); - case HB_SCRIPT_CYPRIOT: - return (fds->os2->ulUnicodeRange4 & 1L << 11); - case HB_SCRIPT_KHAROSHTHI: - return (fds->os2->ulUnicodeRange4 & 1L << 12); - case HB_SCRIPT_TAI_VIET: - return (fds->os2->ulUnicodeRange4 & 1L << 13); - case HB_SCRIPT_CUNEIFORM: - return (fds->os2->ulUnicodeRange4 & 1L << 14); - case HB_SCRIPT_SUNDANESE: - return (fds->os2->ulUnicodeRange4 & 1L << 16); - case HB_SCRIPT_LEPCHA: - return (fds->os2->ulUnicodeRange4 & 1L << 17); - case HB_SCRIPT_OL_CHIKI: - return (fds->os2->ulUnicodeRange4 & 1L << 18); - case HB_SCRIPT_SAURASHTRA: - return (fds->os2->ulUnicodeRange4 & 1L << 19); - case HB_SCRIPT_KAYAH_LI: - return (fds->os2->ulUnicodeRange4 & 1L << 20); - case HB_SCRIPT_REJANG: - return (fds->os2->ulUnicodeRange4 & 1L << 21); - case HB_SCRIPT_CHAM: - return (fds->os2->ulUnicodeRange4 & 1L << 22); - case HB_SCRIPT_ANATOLIAN_HIEROGLYPHS: - return (fds->os2->ulUnicodeRange4 & 1L << 25); - default: - return false; - }; -} - -void DynamicFontDataAdvanced::set_antialiased(bool p_antialiased) { - if (antialiased != p_antialiased) { - clear_cache(); - antialiased = p_antialiased; - } -} - -bool DynamicFontDataAdvanced::get_antialiased() const { - return antialiased; -} - -void DynamicFontDataAdvanced::set_force_autohinter(bool p_enabled) { - if (force_autohinter != p_enabled) { - clear_cache(); - force_autohinter = p_enabled; - } -} - -bool DynamicFontDataAdvanced::get_force_autohinter() const { - return force_autohinter; -} - -void DynamicFontDataAdvanced::set_hinting(TextServer::Hinting p_hinting) { - if (hinting != p_hinting) { - clear_cache(); - hinting = p_hinting; - } -} - -TextServer::Hinting DynamicFontDataAdvanced::get_hinting() const { - return hinting; -} - -bool DynamicFontDataAdvanced::has_outline() const { - return true; -} - -float DynamicFontDataAdvanced::get_base_size() const { - return base_size; -} - -String DynamicFontDataAdvanced::get_supported_chars() const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(base_size); - ERR_FAIL_COND_V(fds == nullptr, String()); - - String chars; - - FT_UInt gindex; - FT_ULong charcode = FT_Get_First_Char(fds->face, &gindex); - while (gindex != 0) { - if (charcode != 0) { - chars += char32_t(charcode); - } - charcode = FT_Get_Next_Char(fds->face, charcode, &gindex); - } - - return chars; -} - -float DynamicFontDataAdvanced::get_font_scale(int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, 1.0f); - - return fds->scale_color_font / oversampling; -} - -bool DynamicFontDataAdvanced::has_char(char32_t p_char) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(base_size); - ERR_FAIL_COND_V(fds == nullptr, false); - - const_cast<DynamicFontDataAdvanced *>(this)->update_glyph(base_size, FT_Get_Char_Index(fds->face, p_char)); - Character ch = fds->glyph_map[FT_Get_Char_Index(fds->face, p_char)]; - - return (ch.found); -} - -hb_font_t *DynamicFontDataAdvanced::get_hb_handle(int p_size) { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, nullptr); - - return fds->hb_handle; -} - -uint32_t DynamicFontDataAdvanced::get_glyph_index(char32_t p_char, char32_t p_variation_selector) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(base_size); - ERR_FAIL_COND_V(fds == nullptr, 0); - - if (p_variation_selector == 0x0000) { - return FT_Get_Char_Index(fds->face, p_char); - } else { - return FT_Face_GetCharVariantIndex(fds->face, p_char, p_variation_selector); - } -} - -Vector2 DynamicFontDataAdvanced::get_advance(uint32_t p_index, int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, Vector2()); - - const_cast<DynamicFontDataAdvanced *>(this)->update_glyph(p_size, p_index); - Character ch = fds->glyph_map[p_index]; - - return ch.advance; -} - -Vector2 DynamicFontDataAdvanced::get_kerning(uint32_t p_char, uint32_t p_next, int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, Vector2()); - - FT_Vector delta; - FT_Get_Kerning(fds->face, p_char, p_next, FT_KERNING_DEFAULT, &delta); - return Vector2(delta.x, delta.y); -} - -Vector2 DynamicFontDataAdvanced::draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, Vector2()); - - const_cast<DynamicFontDataAdvanced *>(this)->update_glyph(p_size, p_index); - Character ch = fds->glyph_map[p_index]; - - Vector2 advance; - if (ch.found) { - ERR_FAIL_COND_V(ch.texture_idx < -1 || ch.texture_idx >= fds->textures.size(), Vector2()); - - if (ch.texture_idx != -1) { - Point2i cpos = p_pos; - cpos += ch.align; - Color modulate = p_color; - if (FT_HAS_COLOR(fds->face)) { - modulate.r = modulate.g = modulate.b = 1.0; - } - if (RenderingServer::get_singleton() != nullptr) { - RID texture = fds->textures[ch.texture_idx].texture->get_rid(); - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, ch.rect.size), texture, ch.rect_uv, modulate, false, false); - } - } - - advance = ch.advance; - } - - return advance; -} - -Vector2 DynamicFontDataAdvanced::draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size, p_outline_size); - ERR_FAIL_COND_V(fds == nullptr, Vector2()); - - const_cast<DynamicFontDataAdvanced *>(this)->update_glyph_outline(p_size, p_outline_size, p_index); - Character ch = fds->glyph_map[p_index]; - - Vector2 advance; - if (ch.found) { - ERR_FAIL_COND_V(ch.texture_idx < -1 || ch.texture_idx >= fds->textures.size(), Vector2()); - - if (ch.texture_idx != -1) { - Point2i cpos = p_pos; - cpos += ch.align; - Color modulate = p_color; - if (FT_HAS_COLOR(fds->face)) { - modulate.r = modulate.g = modulate.b = 1.0; - } - if (RenderingServer::get_singleton() != nullptr) { - RID texture = fds->textures[ch.texture_idx].texture->get_rid(); - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, ch.rect.size), texture, ch.rect_uv, modulate, false, false); - } - } - - advance = ch.advance; - } - - return advance; -} - -bool DynamicFontDataAdvanced::get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataAdvanced *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, false); - - int error = FT_Load_Glyph(fds->face, p_index, FT_LOAD_NO_BITMAP | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); - ERR_FAIL_COND_V(error, false); - - r_points.clear(); - r_contours.clear(); - - float h = fds->ascent; - float scale = (1.0 / 64.0) / oversampling * fds->scale_color_font; - for (short i = 0; i < fds->face->glyph->outline.n_points; i++) { - r_points.push_back(Vector3(fds->face->glyph->outline.points[i].x * scale, h - fds->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fds->face->glyph->outline.tags[i]))); - } - for (short i = 0; i < fds->face->glyph->outline.n_contours; i++) { - r_contours.push_back(fds->face->glyph->outline.contours[i]); - } - r_orientation = (FT_Outline_Get_Orientation(&fds->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); - return true; -} - -DynamicFontDataAdvanced::~DynamicFontDataAdvanced() { - clear_cache(); - if (library != nullptr) { - FT_Done_FreeType(library); - } -} - -#endif // MODULE_FREETYPE_ENABLED diff --git a/modules/text_server_adv/dynamic_font_adv.h b/modules/text_server_adv/dynamic_font_adv.h deleted file mode 100644 index b3f97bb029..0000000000 --- a/modules/text_server_adv/dynamic_font_adv.h +++ /dev/null @@ -1,195 +0,0 @@ -/*************************************************************************/ -/* dynamic_font_adv.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 DYNAMIC_FONT_ADV_H -#define DYNAMIC_FONT_ADV_H - -#include "font_adv.h" - -#include "modules/modules_enabled.gen.h" -#ifdef MODULE_FREETYPE_ENABLED - -#include <ft2build.h> -#include FT_FREETYPE_H -#include FT_TRUETYPE_TABLES_H - -#include <hb-ft.h> -#include <hb-ot.h> - -struct DynamicFontDataAdvanced : public FontDataAdvanced { - _THREAD_SAFE_CLASS_ - -private: - struct CharTexture { - Vector<uint8_t> imgdata; - int texture_size = 0; - Vector<int> offsets; - Ref<ImageTexture> texture; - }; - - struct Character { - bool found = false; - int texture_idx = 0; - Rect2 rect; - Rect2 rect_uv; - Vector2 align; - Vector2 advance = Vector2(-1, -1); - - static Character not_found(); - }; - - struct TexturePosition { - int index = 0; - int x = 0; - int y = 0; - }; - - struct CacheID { - union { - struct { - uint32_t size : 16; - uint32_t outline_size : 16; - }; - uint32_t key = 0; - }; - bool operator<(CacheID right) const { - return key < right.key; - } - }; - - struct DataAtSize { - FT_Face face = nullptr; - TT_OS2 *os2 = nullptr; - FT_StreamRec stream; - - int size = 0; - float scale_color_font = 1.f; - float ascent = 0.0; - float descent = 0.0; - float underline_position = 0.0; - float underline_thickness = 0.0; - - Vector<CharTexture> textures; - HashMap<uint32_t, Character> glyph_map; - - hb_font_t *hb_handle = nullptr; - ~DataAtSize() { - if (hb_handle != nullptr) { - hb_font_destroy(hb_handle); - } - if (face != nullptr) { - FT_Done_Face(face); - } - } - }; - - FT_Library library = nullptr; - - // Source data. - const uint8_t *font_mem = nullptr; - int font_mem_size = 0; - String font_path; - Vector<uint8_t> font_mem_cache; - - Map<int32_t, double> variations; - - float rect_margin = 1.f; - int base_size = 16; - float oversampling = 1.f; - bool antialiased = true; - bool force_autohinter = false; - TextServer::Hinting hinting = TextServer::HINTING_LIGHT; - - Map<CacheID, DataAtSize *> size_cache; - Map<CacheID, DataAtSize *> size_cache_outline; - - DataAtSize *get_data_for_size(int p_size, int p_outline_size = 0); - - TexturePosition find_texture_pos_for_glyph(DataAtSize *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height); - Character bitmap_to_character(DataAtSize *p_data, FT_Bitmap bitmap, int yofs, int xofs, const Vector2 &advance); - _FORCE_INLINE_ void update_glyph(int p_size, uint32_t p_index); - _FORCE_INLINE_ void update_glyph_outline(int p_size, int p_outline_size, uint32_t p_index); - -public: - virtual void clear_cache() override; - - virtual Error load_from_file(const String &p_filename, int p_base_size) override; - virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) override; - - virtual float get_height(int p_size) const override; - virtual float get_ascent(int p_size) const override; - virtual float get_descent(int p_size) const override; - - virtual Dictionary get_feature_list() const override; - virtual Dictionary get_variation_list() const override; - - virtual void set_variation(const String &p_name, double p_value) override; - virtual double get_variation(const String &p_name) const override; - - virtual float get_underline_position(int p_size) const override; - virtual float get_underline_thickness(int p_size) const override; - - virtual void set_antialiased(bool p_antialiased) override; - virtual bool get_antialiased() const override; - - virtual void set_hinting(TextServer::Hinting p_hinting) override; - virtual TextServer::Hinting get_hinting() const override; - - virtual void set_force_autohinter(bool p_enabled) override; - virtual bool get_force_autohinter() const override; - - virtual void set_distance_field_hint(bool p_distance_field) override{}; - virtual bool get_distance_field_hint() const override { return false; }; - - virtual bool has_outline() const override; - virtual float get_base_size() const override; - - virtual bool is_script_supported(uint32_t p_script) const override; - - virtual bool has_char(char32_t p_char) const override; - virtual String get_supported_chars() const override; - virtual float get_font_scale(int p_size) const override; - - virtual hb_font_t *get_hb_handle(int p_size) override; - virtual uint32_t get_glyph_index(char32_t p_char, char32_t p_variation_selector) const override; - virtual Vector2 get_advance(uint32_t p_index, int p_size) const override; - virtual Vector2 get_kerning(uint32_t p_char, uint32_t p_next, int p_size) const override; - - virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; - virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; - - virtual bool get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; - - virtual ~DynamicFontDataAdvanced() override; -}; - -#endif // MODULE_FREETYPE_ENABLED - -#endif // DYNAMIC_FONT_ADV_H diff --git a/modules/text_server_adv/font_adv.h b/modules/text_server_adv/font_adv.h deleted file mode 100644 index 4fadefc569..0000000000 --- a/modules/text_server_adv/font_adv.h +++ /dev/null @@ -1,115 +0,0 @@ -/*************************************************************************/ -/* font_adv.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 FONT_ADV_H -#define FONT_ADV_H - -#include "servers/text_server.h" - -#include <hb.h> - -struct FontDataAdvanced { - Map<String, bool> lang_support_overrides; - Map<String, bool> script_support_overrides; - bool valid = false; - int spacing_space = 0; - int spacing_glyph = 0; - - virtual void clear_cache() = 0; - - virtual Error load_from_file(const String &p_filename, int p_base_size) { return ERR_CANT_CREATE; }; - virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) { return ERR_CANT_CREATE; }; - virtual Error bitmap_new(float p_height, float p_ascent, int p_base_size) { return ERR_CANT_CREATE; }; - - virtual void bitmap_add_texture(const Ref<Texture> &p_texture) { ERR_FAIL_MSG("Not supported."); }; - virtual void bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { ERR_FAIL_MSG("Not supported."); }; - virtual void bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) { ERR_FAIL_MSG("Not supported."); }; - - virtual float get_height(int p_size) const = 0; - virtual float get_ascent(int p_size) const = 0; - virtual float get_descent(int p_size) const = 0; - - virtual Dictionary get_feature_list() const { return Dictionary(); }; - virtual Dictionary get_variation_list() const { return Dictionary(); }; - - virtual void set_variation(const String &p_name, double p_value){}; - virtual double get_variation(const String &p_name) const { return 0; }; - - virtual float get_underline_position(int p_size) const = 0; - virtual float get_underline_thickness(int p_size) const = 0; - - virtual int get_spacing_space() const { return spacing_space; }; - virtual void set_spacing_space(int p_value) { - spacing_space = p_value; - clear_cache(); - }; - - virtual int get_spacing_glyph() const { return spacing_glyph; }; - virtual void set_spacing_glyph(int p_value) { - spacing_glyph = p_value; - clear_cache(); - }; - - virtual void set_antialiased(bool p_antialiased) = 0; - virtual bool get_antialiased() const = 0; - - virtual void set_hinting(TextServer::Hinting p_hinting) = 0; - virtual TextServer::Hinting get_hinting() const = 0; - - virtual void set_distance_field_hint(bool p_distance_field) = 0; - virtual bool get_distance_field_hint() const = 0; - - virtual void set_force_autohinter(bool p_enabeld) = 0; - virtual bool get_force_autohinter() const = 0; - - virtual bool has_outline() const = 0; - virtual float get_base_size() const = 0; - - virtual bool is_lang_supported(const String &p_lang) const { return true; }; - virtual bool is_script_supported(uint32_t p_script) const { return true; }; - - virtual bool has_char(char32_t p_char) const = 0; - virtual String get_supported_chars() const = 0; - virtual float get_font_scale(int p_size) const { return 1.0f; }; - - virtual hb_font_t *get_hb_handle(int p_size) = 0; - virtual uint32_t get_glyph_index(char32_t p_char, char32_t p_variation_selector) const = 0; - virtual Vector2 get_advance(uint32_t p_char, int p_size) const = 0; - virtual Vector2 get_kerning(uint32_t p_char, uint32_t p_next, int p_size) const = 0; - - virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const = 0; - virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const = 0; - - virtual bool get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { return false; }; - - virtual ~FontDataAdvanced(){}; -}; - -#endif // FONT_ADV_H diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 9ecb0de5b8..19e94adf68 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -29,15 +29,213 @@ /*************************************************************************/ #include "text_server_adv.h" -#include "bitmap_font_adv.h" -#include "dynamic_font_adv.h" +#include "core/string/print_string.h" #include "core/string/translation.h" #ifdef ICU_STATIC_DATA #include "thirdparty/icu4c/icudata.gen.h" #endif +#ifdef MODULE_MSDFGEN_ENABLED +#include "core/ShapeDistanceFinder.h" +#include "core/contour-combiners.h" +#include "core/edge-selectors.h" +#include "msdfgen.h" +#endif + +/*************************************************************************/ +/* hb_bmp_font_t HarfBuzz Bitmap font interface */ +/*************************************************************************/ + +hb_font_funcs_t *TextServerAdvanced::funcs = nullptr; + +TextServerAdvanced::hb_bmp_font_t *TextServerAdvanced::_hb_bmp_font_create(TextServerAdvanced::FontDataForSizeAdvanced *p_face, bool p_unref) { + hb_bmp_font_t *bm_font = memnew(hb_bmp_font_t); + + if (!bm_font) { + return nullptr; + } + + bm_font->face = p_face; + bm_font->unref = p_unref; + + return bm_font; +} + +void TextServerAdvanced::_hb_bmp_font_destroy(void *p_data) { + hb_bmp_font_t *bm_font = reinterpret_cast<hb_bmp_font_t *>(p_data); + memdelete(bm_font); +} + +hb_bool_t TextServerAdvanced::hb_bmp_get_nominal_glyph(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_unicode, hb_codepoint_t *r_glyph, void *p_user_data) { + const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(p_font_data); + + if (!bm_font->face) { + return false; + } + + if (!bm_font->face->glyph_map.has(p_unicode)) { + if (bm_font->face->glyph_map.has(0xF000u + p_unicode)) { + *r_glyph = 0xF000u + p_unicode; + return true; + } else { + return false; + } + } + + *r_glyph = p_unicode; + return true; +} + +hb_position_t TextServerAdvanced::hb_bmp_get_glyph_h_advance(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, void *p_user_data) { + const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(p_font_data); + + if (!bm_font->face) { + return 0; + } + + if (!bm_font->face->glyph_map.has(p_glyph)) { + return 0; + } + + return bm_font->face->glyph_map[p_glyph].advance.x * 64; +} + +hb_position_t TextServerAdvanced::hb_bmp_get_glyph_v_advance(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, void *p_user_data) { + const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(p_font_data); + + if (!bm_font->face) { + return 0; + } + + if (!bm_font->face->glyph_map.has(p_glyph)) { + return 0; + } + + return -bm_font->face->glyph_map[p_glyph].advance.y * 64; +} + +hb_position_t TextServerAdvanced::hb_bmp_get_glyph_h_kerning(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_left_glyph, hb_codepoint_t p_right_glyph, void *p_user_data) { + const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(p_font_data); + + if (!bm_font->face) { + return 0; + } + + if (!bm_font->face->kerning_map.has(Vector2i(p_left_glyph, p_right_glyph))) { + return 0; + } + + return bm_font->face->kerning_map[Vector2i(p_left_glyph, p_right_glyph)].x * 64; +} + +hb_position_t TextServerAdvanced::hb_bmp_get_glyph_v_kerning(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_left_glyph, hb_codepoint_t p_right_glyph, void *p_user_data) { + const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(p_font_data); + + if (!bm_font->face) { + return 0; + } + + if (!bm_font->face->kerning_map.has(Vector2i(p_left_glyph, p_right_glyph))) { + return 0; + } + + return bm_font->face->kerning_map[Vector2i(p_left_glyph, p_right_glyph)].y * 64; +} + +hb_bool_t TextServerAdvanced::hb_bmp_get_glyph_v_origin(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, hb_position_t *r_x, hb_position_t *r_y, void *p_user_data) { + const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(p_font_data); + + if (!bm_font->face) { + return false; + } + + if (!bm_font->face->glyph_map.has(p_glyph)) { + return false; + } + + *r_x = bm_font->face->glyph_map[p_glyph].advance.x * 32; + *r_y = -bm_font->face->ascent * 64; + + return true; +} + +hb_bool_t TextServerAdvanced::hb_bmp_get_glyph_extents(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, hb_glyph_extents_t *r_extents, void *p_user_data) { + const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(p_font_data); + + if (!bm_font->face) { + return false; + } + + if (!bm_font->face->glyph_map.has(p_glyph)) { + return false; + } + + r_extents->x_bearing = 0; + r_extents->y_bearing = 0; + r_extents->width = bm_font->face->glyph_map[p_glyph].rect.size.x * 64; + r_extents->height = bm_font->face->glyph_map[p_glyph].rect.size.y * 64; + + return true; +} + +hb_bool_t TextServerAdvanced::hb_bmp_get_font_h_extents(hb_font_t *p_font, void *p_font_data, hb_font_extents_t *r_metrics, void *p_user_data) { + const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(p_font_data); + + if (!bm_font->face) { + return false; + } + + r_metrics->ascender = bm_font->face->ascent; + r_metrics->descender = bm_font->face->descent; + r_metrics->line_gap = 0; + + return true; +} + +void TextServerAdvanced::hb_bmp_create_font_funcs() { + if (funcs == nullptr) { + funcs = hb_font_funcs_create(); + + hb_font_funcs_set_font_h_extents_func(funcs, hb_bmp_get_font_h_extents, nullptr, nullptr); + hb_font_funcs_set_nominal_glyph_func(funcs, hb_bmp_get_nominal_glyph, nullptr, nullptr); + hb_font_funcs_set_glyph_h_advance_func(funcs, hb_bmp_get_glyph_h_advance, nullptr, nullptr); + hb_font_funcs_set_glyph_v_advance_func(funcs, hb_bmp_get_glyph_v_advance, nullptr, nullptr); + hb_font_funcs_set_glyph_v_origin_func(funcs, hb_bmp_get_glyph_v_origin, nullptr, nullptr); + hb_font_funcs_set_glyph_h_kerning_func(funcs, hb_bmp_get_glyph_h_kerning, nullptr, nullptr); + hb_font_funcs_set_glyph_v_kerning_func(funcs, hb_bmp_get_glyph_v_kerning, nullptr, nullptr); + hb_font_funcs_set_glyph_extents_func(funcs, hb_bmp_get_glyph_extents, nullptr, nullptr); + + hb_font_funcs_make_immutable(funcs); + } +} + +void TextServerAdvanced::hb_bmp_free_font_funcs() { + if (funcs != nullptr) { + hb_font_funcs_destroy(funcs); + funcs = nullptr; + } +} + +void TextServerAdvanced::_hb_bmp_font_set_funcs(hb_font_t *p_font, TextServerAdvanced::FontDataForSizeAdvanced *p_face, bool p_unref) { + hb_font_set_funcs(p_font, funcs, _hb_bmp_font_create(p_face, p_unref), _hb_bmp_font_destroy); +} + +hb_font_t *TextServerAdvanced::hb_bmp_font_create(TextServerAdvanced::FontDataForSizeAdvanced *p_face, hb_destroy_func_t p_destroy) { + hb_font_t *font; + hb_face_t *face = hb_face_create(nullptr, 0); + + font = hb_font_create(face); + hb_face_destroy(face); + _hb_bmp_font_set_funcs(font, p_face, false); + return font; +} + +/*************************************************************************/ +/* Character properties. */ +/*************************************************************************/ + _FORCE_INLINE_ bool is_ain(char32_t p_chr) { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_AIN; } @@ -242,276 +440,270 @@ bool TextServerAdvanced::is_locale_right_to_left(const String &p_locale) { } } -struct FeatureInfo { - int32_t tag; - String name; -}; +static Map<StringName, int32_t> feature_sets; -static FeatureInfo feature_set[] = { +static void _insert_feature_sets() { // Registered OpenType feature tags. - { HB_TAG('a', 'a', 'l', 't'), "access_all_alternates" }, - { HB_TAG('a', 'b', 'v', 'f'), "above_base_forms" }, - { HB_TAG('a', 'b', 'v', 'm'), "above_base_mark_positioning" }, - { HB_TAG('a', 'b', 'v', 's'), "above_base_substitutions" }, - { HB_TAG('a', 'f', 'r', 'c'), "alternative_fractions" }, - { HB_TAG('a', 'k', 'h', 'n'), "akhands" }, - { HB_TAG('b', 'l', 'w', 'f'), "below_base_forms" }, - { HB_TAG('b', 'l', 'w', 'm'), "below_base_mark_positioning" }, - { HB_TAG('b', 'l', 'w', 's'), "below_base_substitutions" }, - { HB_TAG('c', 'a', 'l', 't'), "contextual_alternates" }, - { HB_TAG('c', 'a', 's', 'e'), "case_sensitive_forms" }, - { HB_TAG('c', 'c', 'm', 'p'), "glyph_composition" }, - { HB_TAG('c', 'f', 'a', 'r'), "conjunct_form_after_ro" }, - { HB_TAG('c', 'j', 'c', 't'), "conjunct_forms" }, - { HB_TAG('c', 'l', 'i', 'g'), "contextual_ligatures" }, - { HB_TAG('c', 'p', 'c', 't'), "centered_cjk_punctuation" }, - { HB_TAG('c', 'p', 's', 'p'), "capital_spacing" }, - { HB_TAG('c', 's', 'w', 'h'), "contextual_swash" }, - { HB_TAG('c', 'u', 'r', 's'), "cursive_positioning" }, - { HB_TAG('c', 'v', '0', '1'), "character_variant_01" }, - { HB_TAG('c', 'v', '0', '2'), "character_variant_02" }, - { HB_TAG('c', 'v', '0', '3'), "character_variant_03" }, - { HB_TAG('c', 'v', '0', '4'), "character_variant_04" }, - { HB_TAG('c', 'v', '0', '5'), "character_variant_05" }, - { HB_TAG('c', 'v', '0', '6'), "character_variant_06" }, - { HB_TAG('c', 'v', '0', '7'), "character_variant_07" }, - { HB_TAG('c', 'v', '0', '8'), "character_variant_08" }, - { HB_TAG('c', 'v', '0', '9'), "character_variant_09" }, - { HB_TAG('c', 'v', '1', '0'), "character_variant_10" }, - { HB_TAG('c', 'v', '1', '1'), "character_variant_11" }, - { HB_TAG('c', 'v', '1', '2'), "character_variant_12" }, - { HB_TAG('c', 'v', '1', '3'), "character_variant_13" }, - { HB_TAG('c', 'v', '1', '4'), "character_variant_14" }, - { HB_TAG('c', 'v', '1', '5'), "character_variant_15" }, - { HB_TAG('c', 'v', '1', '6'), "character_variant_16" }, - { HB_TAG('c', 'v', '1', '7'), "character_variant_17" }, - { HB_TAG('c', 'v', '1', '8'), "character_variant_18" }, - { HB_TAG('c', 'v', '1', '9'), "character_variant_19" }, - { HB_TAG('c', 'v', '2', '0'), "character_variant_20" }, - { HB_TAG('c', 'v', '2', '1'), "character_variant_21" }, - { HB_TAG('c', 'v', '2', '2'), "character_variant_22" }, - { HB_TAG('c', 'v', '2', '3'), "character_variant_23" }, - { HB_TAG('c', 'v', '2', '4'), "character_variant_24" }, - { HB_TAG('c', 'v', '2', '5'), "character_variant_25" }, - { HB_TAG('c', 'v', '2', '6'), "character_variant_26" }, - { HB_TAG('c', 'v', '2', '7'), "character_variant_27" }, - { HB_TAG('c', 'v', '2', '8'), "character_variant_28" }, - { HB_TAG('c', 'v', '2', '9'), "character_variant_29" }, - { HB_TAG('c', 'v', '3', '0'), "character_variant_30" }, - { HB_TAG('c', 'v', '3', '1'), "character_variant_31" }, - { HB_TAG('c', 'v', '3', '2'), "character_variant_32" }, - { HB_TAG('c', 'v', '3', '3'), "character_variant_33" }, - { HB_TAG('c', 'v', '3', '4'), "character_variant_34" }, - { HB_TAG('c', 'v', '3', '5'), "character_variant_35" }, - { HB_TAG('c', 'v', '3', '6'), "character_variant_36" }, - { HB_TAG('c', 'v', '3', '7'), "character_variant_37" }, - { HB_TAG('c', 'v', '3', '8'), "character_variant_38" }, - { HB_TAG('c', 'v', '3', '9'), "character_variant_39" }, - { HB_TAG('c', 'v', '4', '0'), "character_variant_40" }, - { HB_TAG('c', 'v', '4', '1'), "character_variant_41" }, - { HB_TAG('c', 'v', '4', '2'), "character_variant_42" }, - { HB_TAG('c', 'v', '4', '3'), "character_variant_43" }, - { HB_TAG('c', 'v', '4', '4'), "character_variant_44" }, - { HB_TAG('c', 'v', '4', '5'), "character_variant_45" }, - { HB_TAG('c', 'v', '4', '6'), "character_variant_46" }, - { HB_TAG('c', 'v', '4', '7'), "character_variant_47" }, - { HB_TAG('c', 'v', '4', '8'), "character_variant_48" }, - { HB_TAG('c', 'v', '4', '9'), "character_variant_49" }, - { HB_TAG('c', 'v', '5', '0'), "character_variant_50" }, - { HB_TAG('c', 'v', '5', '1'), "character_variant_51" }, - { HB_TAG('c', 'v', '5', '2'), "character_variant_52" }, - { HB_TAG('c', 'v', '5', '3'), "character_variant_53" }, - { HB_TAG('c', 'v', '5', '4'), "character_variant_54" }, - { HB_TAG('c', 'v', '5', '5'), "character_variant_55" }, - { HB_TAG('c', 'v', '5', '6'), "character_variant_56" }, - { HB_TAG('c', 'v', '5', '7'), "character_variant_57" }, - { HB_TAG('c', 'v', '5', '8'), "character_variant_58" }, - { HB_TAG('c', 'v', '5', '9'), "character_variant_59" }, - { HB_TAG('c', 'v', '6', '0'), "character_variant_60" }, - { HB_TAG('c', 'v', '6', '1'), "character_variant_61" }, - { HB_TAG('c', 'v', '6', '2'), "character_variant_62" }, - { HB_TAG('c', 'v', '6', '3'), "character_variant_63" }, - { HB_TAG('c', 'v', '6', '4'), "character_variant_64" }, - { HB_TAG('c', 'v', '6', '5'), "character_variant_65" }, - { HB_TAG('c', 'v', '6', '6'), "character_variant_66" }, - { HB_TAG('c', 'v', '6', '7'), "character_variant_67" }, - { HB_TAG('c', 'v', '6', '8'), "character_variant_68" }, - { HB_TAG('c', 'v', '6', '9'), "character_variant_69" }, - { HB_TAG('c', 'v', '7', '0'), "character_variant_70" }, - { HB_TAG('c', 'v', '7', '1'), "character_variant_71" }, - { HB_TAG('c', 'v', '7', '2'), "character_variant_72" }, - { HB_TAG('c', 'v', '7', '3'), "character_variant_73" }, - { HB_TAG('c', 'v', '7', '4'), "character_variant_74" }, - { HB_TAG('c', 'v', '7', '5'), "character_variant_75" }, - { HB_TAG('c', 'v', '7', '6'), "character_variant_76" }, - { HB_TAG('c', 'v', '7', '7'), "character_variant_77" }, - { HB_TAG('c', 'v', '7', '8'), "character_variant_78" }, - { HB_TAG('c', 'v', '7', '9'), "character_variant_79" }, - { HB_TAG('c', 'v', '8', '0'), "character_variant_80" }, - { HB_TAG('c', 'v', '8', '1'), "character_variant_81" }, - { HB_TAG('c', 'v', '8', '2'), "character_variant_82" }, - { HB_TAG('c', 'v', '8', '3'), "character_variant_83" }, - { HB_TAG('c', 'v', '8', '4'), "character_variant_84" }, - { HB_TAG('c', 'v', '8', '5'), "character_variant_85" }, - { HB_TAG('c', 'v', '8', '6'), "character_variant_86" }, - { HB_TAG('c', 'v', '8', '7'), "character_variant_87" }, - { HB_TAG('c', 'v', '8', '8'), "character_variant_88" }, - { HB_TAG('c', 'v', '8', '9'), "character_variant_89" }, - { HB_TAG('c', 'v', '9', '0'), "character_variant_90" }, - { HB_TAG('c', 'v', '9', '1'), "character_variant_91" }, - { HB_TAG('c', 'v', '9', '2'), "character_variant_92" }, - { HB_TAG('c', 'v', '9', '3'), "character_variant_93" }, - { HB_TAG('c', 'v', '9', '4'), "character_variant_94" }, - { HB_TAG('c', 'v', '9', '5'), "character_variant_95" }, - { HB_TAG('c', 'v', '9', '6'), "character_variant_96" }, - { HB_TAG('c', 'v', '9', '7'), "character_variant_97" }, - { HB_TAG('c', 'v', '9', '8'), "character_variant_98" }, - { HB_TAG('c', 'v', '9', '9'), "character_variant_99" }, - { HB_TAG('c', '2', 'p', 'c'), "petite_capitals_from_capitals" }, - { HB_TAG('c', '2', 's', 'c'), "small_capitals_from_capitals" }, - { HB_TAG('d', 'i', 's', 't'), "distances" }, - { HB_TAG('d', 'l', 'i', 'g'), "discretionary_ligatures" }, - { HB_TAG('d', 'n', 'o', 'm'), "denominators" }, - { HB_TAG('d', 't', 'l', 's'), "dotless_forms" }, - { HB_TAG('e', 'x', 'p', 't'), "expert_forms" }, - { HB_TAG('f', 'a', 'l', 't'), "final_glyph_on_line_alternates" }, - { HB_TAG('f', 'i', 'n', '2'), "terminal_forms_2" }, - { HB_TAG('f', 'i', 'n', '3'), "terminal_forms_3" }, - { HB_TAG('f', 'i', 'n', 'a'), "terminal_forms" }, - { HB_TAG('f', 'l', 'a', 'c'), "flattened_accent_forms" }, - { HB_TAG('f', 'r', 'a', 'c'), "fractions" }, - { HB_TAG('f', 'w', 'i', 'd'), "full_widths" }, - { HB_TAG('h', 'a', 'l', 'f'), "half_forms" }, - { HB_TAG('h', 'a', 'l', 'n'), "halant_forms" }, - { HB_TAG('h', 'a', 'l', 't'), "alternate_half_widths" }, - { HB_TAG('h', 'i', 's', 't'), "historical_forms" }, - { HB_TAG('h', 'k', 'n', 'a'), "horizontal_kana_alternates" }, - { HB_TAG('h', 'l', 'i', 'g'), "historical_ligatures" }, - { HB_TAG('h', 'n', 'g', 'l'), "hangul" }, - { HB_TAG('h', 'o', 'j', 'o'), "hojo_kanji_forms" }, - { HB_TAG('h', 'w', 'i', 'd'), "half_widths" }, - { HB_TAG('i', 'n', 'i', 't'), "initial_forms" }, - { HB_TAG('i', 's', 'o', 'l'), "isolated_forms" }, - { HB_TAG('i', 't', 'a', 'l'), "italics" }, - { HB_TAG('j', 'a', 'l', 't'), "justification_alternates" }, - { HB_TAG('j', 'p', '7', '8'), "jis78_forms" }, - { HB_TAG('j', 'p', '8', '3'), "jis83_forms" }, - { HB_TAG('j', 'p', '9', '0'), "jis90_forms" }, - { HB_TAG('j', 'p', '0', '4'), "jis2004_forms" }, - { HB_TAG('k', 'e', 'r', 'n'), "kerning" }, - { HB_TAG('l', 'f', 'b', 'd'), "left_bounds" }, - { HB_TAG('l', 'i', 'g', 'a'), "standard_ligatures" }, - { HB_TAG('l', 'j', 'm', 'o'), "leading_jamo_forms" }, - { HB_TAG('l', 'n', 'u', 'm'), "lining_figures" }, - { HB_TAG('l', 'o', 'c', 'l'), "localized_forms" }, - { HB_TAG('l', 't', 'r', 'a'), "left_to_right_alternates" }, - { HB_TAG('l', 't', 'r', 'm'), "left_to_right_mirrored_forms" }, - { HB_TAG('m', 'a', 'r', 'k'), "mark_positioning" }, - { HB_TAG('m', 'e', 'd', '2'), "medial_forms_2" }, - { HB_TAG('m', 'e', 'd', 'i'), "medial_forms" }, - { HB_TAG('m', 'g', 'r', 'k'), "mathematical_greek" }, - { HB_TAG('m', 'k', 'm', 'k'), "mark_to_mark_positioning" }, - { HB_TAG('m', 's', 'e', 't'), "mark_positioning_via_substitution" }, - { HB_TAG('n', 'a', 'l', 't'), "alternate_annotation_forms" }, - { HB_TAG('n', 'l', 'c', 'k'), "nlc_kanji_forms" }, - { HB_TAG('n', 'u', 'k', 't'), "nukta_forms" }, - { HB_TAG('n', 'u', 'm', 'r'), "numerators" }, - { HB_TAG('o', 'n', 'u', 'm'), "oldstyle_figures" }, - { HB_TAG('o', 'p', 'b', 'd'), "optical_bounds" }, - { HB_TAG('o', 'r', 'd', 'n'), "ordinals" }, - { HB_TAG('o', 'r', 'n', 'm'), "ornaments" }, - { HB_TAG('p', 'a', 'l', 't'), "proportional_alternate_widths" }, - { HB_TAG('p', 'c', 'a', 'p'), "petite_capitals" }, - { HB_TAG('p', 'k', 'n', 'a'), "proportional_kana" }, - { HB_TAG('p', 'n', 'u', 'm'), "proportional_figures" }, - { HB_TAG('p', 'r', 'e', 'f'), "pre_base_forms" }, - { HB_TAG('p', 'r', 'e', 's'), "pre_base_substitutions" }, - { HB_TAG('p', 's', 't', 'f'), "post_base_forms" }, - { HB_TAG('p', 's', 't', 's'), "post_base_substitutions" }, - { HB_TAG('p', 'w', 'i', 'd'), "proportional_widths" }, - { HB_TAG('q', 'w', 'i', 'd'), "quarter_widths" }, - { HB_TAG('r', 'a', 'n', 'd'), "randomize" }, - { HB_TAG('r', 'c', 'l', 't'), "required_contextual_alternates" }, - { HB_TAG('r', 'k', 'r', 'f'), "rakar_forms" }, - { HB_TAG('r', 'l', 'i', 'g'), "required_ligatures" }, - { HB_TAG('r', 'p', 'h', 'f'), "reph_forms" }, - { HB_TAG('r', 't', 'b', 'd'), "right_bounds" }, - { HB_TAG('r', 't', 'l', 'a'), "right_to_left_alternates" }, - { HB_TAG('r', 't', 'l', 'm'), "right_to_left_mirrored_forms" }, - { HB_TAG('r', 'u', 'b', 'y'), "ruby_notation_forms" }, - { HB_TAG('r', 'v', 'r', 'n'), "required_variation_alternates" }, - { HB_TAG('s', 'a', 'l', 't'), "stylistic_alternates" }, - { HB_TAG('s', 'i', 'n', 'f'), "scientific_inferiors" }, - { HB_TAG('s', 'i', 'z', 'e'), "optical_size" }, - { HB_TAG('s', 'm', 'c', 'p'), "small_capitals" }, - { HB_TAG('s', 'm', 'p', 'l'), "simplified_forms" }, - { HB_TAG('s', 's', '0', '1'), "stylistic_set_01" }, - { HB_TAG('s', 's', '0', '2'), "stylistic_set_02" }, - { HB_TAG('s', 's', '0', '3'), "stylistic_set_03" }, - { HB_TAG('s', 's', '0', '4'), "stylistic_set_04" }, - { HB_TAG('s', 's', '0', '5'), "stylistic_set_05" }, - { HB_TAG('s', 's', '0', '6'), "stylistic_set_06" }, - { HB_TAG('s', 's', '0', '7'), "stylistic_set_07" }, - { HB_TAG('s', 's', '0', '8'), "stylistic_set_08" }, - { HB_TAG('s', 's', '0', '9'), "stylistic_set_09" }, - { HB_TAG('s', 's', '1', '0'), "stylistic_set_10" }, - { HB_TAG('s', 's', '1', '1'), "stylistic_set_11" }, - { HB_TAG('s', 's', '1', '2'), "stylistic_set_12" }, - { HB_TAG('s', 's', '1', '3'), "stylistic_set_13" }, - { HB_TAG('s', 's', '1', '4'), "stylistic_set_14" }, - { HB_TAG('s', 's', '1', '5'), "stylistic_set_15" }, - { HB_TAG('s', 's', '1', '6'), "stylistic_set_16" }, - { HB_TAG('s', 's', '1', '7'), "stylistic_set_17" }, - { HB_TAG('s', 's', '1', '8'), "stylistic_set_18" }, - { HB_TAG('s', 's', '1', '9'), "stylistic_set_19" }, - { HB_TAG('s', 's', '2', '0'), "stylistic_set_20" }, - { HB_TAG('s', 's', 't', 'y'), "math_script_style_alternates" }, - { HB_TAG('s', 't', 'c', 'h'), "stretching_glyph_decomposition" }, - { HB_TAG('s', 'u', 'b', 's'), "subscript" }, - { HB_TAG('s', 'u', 'p', 's'), "superscript" }, - { HB_TAG('s', 'w', 's', 'h'), "swash" }, - { HB_TAG('t', 'i', 't', 'l'), "titling" }, - { HB_TAG('t', 'j', 'm', 'o'), "trailing_jamo_forms" }, - { HB_TAG('t', 'n', 'a', 'm'), "traditional_name_forms" }, - { HB_TAG('t', 'n', 'u', 'm'), "tabular_figures" }, - { HB_TAG('t', 'r', 'a', 'd'), "traditional_forms" }, - { HB_TAG('t', 'w', 'i', 'd'), "third_widths" }, - { HB_TAG('u', 'n', 'i', 'c'), "unicase" }, - { HB_TAG('v', 'a', 'l', 't'), "alternate_vertical_metrics" }, - { HB_TAG('v', 'a', 't', 'u'), "vattu_variants" }, - { HB_TAG('v', 'e', 'r', 't'), "vertical_writing" }, - { HB_TAG('v', 'h', 'a', 'l'), "alternate_vertical_half_metrics" }, - { HB_TAG('v', 'j', 'm', 'o'), "vowel_jamo_forms" }, - { HB_TAG('v', 'k', 'n', 'a'), "vertical_kana_alternates" }, - { HB_TAG('v', 'k', 'r', 'n'), "vertical_kerning" }, - { HB_TAG('v', 'p', 'a', 'l'), "proportional_alternate_vertical_metrics" }, - { HB_TAG('v', 'r', 't', '2'), "vertical_alternates_and_rotation" }, - { HB_TAG('v', 'r', 't', 'r'), "vertical_alternates_for_rotation" }, - { HB_TAG('z', 'e', 'r', 'o'), "slashed_zero" }, - // Registered OpenType variation tags. - { HB_TAG('i', 't', 'a', 'l'), "italic" }, - { HB_TAG('o', 'p', 's', 'z'), "optical_size" }, - { HB_TAG('s', 'l', 'n', 't'), "slant" }, - { HB_TAG('w', 'd', 't', 'h'), "width" }, - { HB_TAG('w', 'g', 'h', 't'), "weight" }, - { 0, String() }, -}; - -int32_t TextServerAdvanced::name_to_tag(const String &p_name) { - for (int i = 0; feature_set[i].tag != 0; i++) { - if (feature_set[i].name == p_name) { - return feature_set[i].tag; - } + feature_sets.insert("access_all_alternates", HB_TAG('a', 'a', 'l', 't')); + feature_sets.insert("above_base_forms", HB_TAG('a', 'b', 'v', 'f')); + feature_sets.insert("above_base_mark_positioning", HB_TAG('a', 'b', 'v', 'm')); + feature_sets.insert("above_base_substitutions", HB_TAG('a', 'b', 'v', 's')); + feature_sets.insert("alternative_fractions", HB_TAG('a', 'f', 'r', 'c')); + feature_sets.insert("akhands", HB_TAG('a', 'k', 'h', 'n')); + feature_sets.insert("below_base_forms", HB_TAG('b', 'l', 'w', 'f')); + feature_sets.insert("below_base_mark_positioning", HB_TAG('b', 'l', 'w', 'm')); + feature_sets.insert("below_base_substitutions", HB_TAG('b', 'l', 'w', 's')); + feature_sets.insert("contextual_alternates", HB_TAG('c', 'a', 'l', 't')); + feature_sets.insert("case_sensitive_forms", HB_TAG('c', 'a', 's', 'e')); + feature_sets.insert("glyph_composition", HB_TAG('c', 'c', 'm', 'p')); + feature_sets.insert("conjunct_form_after_ro", HB_TAG('c', 'f', 'a', 'r')); + feature_sets.insert("conjunct_forms", HB_TAG('c', 'j', 'c', 't')); + feature_sets.insert("contextual_ligatures", HB_TAG('c', 'l', 'i', 'g')); + feature_sets.insert("centered_cjk_punctuation", HB_TAG('c', 'p', 'c', 't')); + feature_sets.insert("capital_spacing", HB_TAG('c', 'p', 's', 'p')); + feature_sets.insert("contextual_swash", HB_TAG('c', 's', 'w', 'h')); + feature_sets.insert("cursive_positioning", HB_TAG('c', 'u', 'r', 's')); + feature_sets.insert("character_variant_01", HB_TAG('c', 'v', '0', '1')); + feature_sets.insert("character_variant_02", HB_TAG('c', 'v', '0', '2')); + feature_sets.insert("character_variant_03", HB_TAG('c', 'v', '0', '3')); + feature_sets.insert("character_variant_04", HB_TAG('c', 'v', '0', '4')); + feature_sets.insert("character_variant_05", HB_TAG('c', 'v', '0', '5')); + feature_sets.insert("character_variant_06", HB_TAG('c', 'v', '0', '6')); + feature_sets.insert("character_variant_07", HB_TAG('c', 'v', '0', '7')); + feature_sets.insert("character_variant_08", HB_TAG('c', 'v', '0', '8')); + feature_sets.insert("character_variant_09", HB_TAG('c', 'v', '0', '9')); + feature_sets.insert("character_variant_10", HB_TAG('c', 'v', '1', '0')); + feature_sets.insert("character_variant_11", HB_TAG('c', 'v', '1', '1')); + feature_sets.insert("character_variant_12", HB_TAG('c', 'v', '1', '2')); + feature_sets.insert("character_variant_13", HB_TAG('c', 'v', '1', '3')); + feature_sets.insert("character_variant_14", HB_TAG('c', 'v', '1', '4')); + feature_sets.insert("character_variant_15", HB_TAG('c', 'v', '1', '5')); + feature_sets.insert("character_variant_16", HB_TAG('c', 'v', '1', '6')); + feature_sets.insert("character_variant_17", HB_TAG('c', 'v', '1', '7')); + feature_sets.insert("character_variant_18", HB_TAG('c', 'v', '1', '8')); + feature_sets.insert("character_variant_19", HB_TAG('c', 'v', '1', '9')); + feature_sets.insert("character_variant_20", HB_TAG('c', 'v', '2', '0')); + feature_sets.insert("character_variant_21", HB_TAG('c', 'v', '2', '1')); + feature_sets.insert("character_variant_22", HB_TAG('c', 'v', '2', '2')); + feature_sets.insert("character_variant_23", HB_TAG('c', 'v', '2', '3')); + feature_sets.insert("character_variant_24", HB_TAG('c', 'v', '2', '4')); + feature_sets.insert("character_variant_25", HB_TAG('c', 'v', '2', '5')); + feature_sets.insert("character_variant_26", HB_TAG('c', 'v', '2', '6')); + feature_sets.insert("character_variant_27", HB_TAG('c', 'v', '2', '7')); + feature_sets.insert("character_variant_28", HB_TAG('c', 'v', '2', '8')); + feature_sets.insert("character_variant_29", HB_TAG('c', 'v', '2', '9')); + feature_sets.insert("character_variant_30", HB_TAG('c', 'v', '3', '0')); + feature_sets.insert("character_variant_31", HB_TAG('c', 'v', '3', '1')); + feature_sets.insert("character_variant_32", HB_TAG('c', 'v', '3', '2')); + feature_sets.insert("character_variant_33", HB_TAG('c', 'v', '3', '3')); + feature_sets.insert("character_variant_34", HB_TAG('c', 'v', '3', '4')); + feature_sets.insert("character_variant_35", HB_TAG('c', 'v', '3', '5')); + feature_sets.insert("character_variant_36", HB_TAG('c', 'v', '3', '6')); + feature_sets.insert("character_variant_37", HB_TAG('c', 'v', '3', '7')); + feature_sets.insert("character_variant_38", HB_TAG('c', 'v', '3', '8')); + feature_sets.insert("character_variant_39", HB_TAG('c', 'v', '3', '9')); + feature_sets.insert("character_variant_40", HB_TAG('c', 'v', '4', '0')); + feature_sets.insert("character_variant_41", HB_TAG('c', 'v', '4', '1')); + feature_sets.insert("character_variant_42", HB_TAG('c', 'v', '4', '2')); + feature_sets.insert("character_variant_43", HB_TAG('c', 'v', '4', '3')); + feature_sets.insert("character_variant_44", HB_TAG('c', 'v', '4', '4')); + feature_sets.insert("character_variant_45", HB_TAG('c', 'v', '4', '5')); + feature_sets.insert("character_variant_46", HB_TAG('c', 'v', '4', '6')); + feature_sets.insert("character_variant_47", HB_TAG('c', 'v', '4', '7')); + feature_sets.insert("character_variant_48", HB_TAG('c', 'v', '4', '8')); + feature_sets.insert("character_variant_49", HB_TAG('c', 'v', '4', '9')); + feature_sets.insert("character_variant_50", HB_TAG('c', 'v', '5', '0')); + feature_sets.insert("character_variant_51", HB_TAG('c', 'v', '5', '1')); + feature_sets.insert("character_variant_52", HB_TAG('c', 'v', '5', '2')); + feature_sets.insert("character_variant_53", HB_TAG('c', 'v', '5', '3')); + feature_sets.insert("character_variant_54", HB_TAG('c', 'v', '5', '4')); + feature_sets.insert("character_variant_55", HB_TAG('c', 'v', '5', '5')); + feature_sets.insert("character_variant_56", HB_TAG('c', 'v', '5', '6')); + feature_sets.insert("character_variant_57", HB_TAG('c', 'v', '5', '7')); + feature_sets.insert("character_variant_58", HB_TAG('c', 'v', '5', '8')); + feature_sets.insert("character_variant_59", HB_TAG('c', 'v', '5', '9')); + feature_sets.insert("character_variant_60", HB_TAG('c', 'v', '6', '0')); + feature_sets.insert("character_variant_61", HB_TAG('c', 'v', '6', '1')); + feature_sets.insert("character_variant_62", HB_TAG('c', 'v', '6', '2')); + feature_sets.insert("character_variant_63", HB_TAG('c', 'v', '6', '3')); + feature_sets.insert("character_variant_64", HB_TAG('c', 'v', '6', '4')); + feature_sets.insert("character_variant_65", HB_TAG('c', 'v', '6', '5')); + feature_sets.insert("character_variant_66", HB_TAG('c', 'v', '6', '6')); + feature_sets.insert("character_variant_67", HB_TAG('c', 'v', '6', '7')); + feature_sets.insert("character_variant_68", HB_TAG('c', 'v', '6', '8')); + feature_sets.insert("character_variant_69", HB_TAG('c', 'v', '6', '9')); + feature_sets.insert("character_variant_70", HB_TAG('c', 'v', '7', '0')); + feature_sets.insert("character_variant_71", HB_TAG('c', 'v', '7', '1')); + feature_sets.insert("character_variant_72", HB_TAG('c', 'v', '7', '2')); + feature_sets.insert("character_variant_73", HB_TAG('c', 'v', '7', '3')); + feature_sets.insert("character_variant_74", HB_TAG('c', 'v', '7', '4')); + feature_sets.insert("character_variant_75", HB_TAG('c', 'v', '7', '5')); + feature_sets.insert("character_variant_76", HB_TAG('c', 'v', '7', '6')); + feature_sets.insert("character_variant_77", HB_TAG('c', 'v', '7', '7')); + feature_sets.insert("character_variant_78", HB_TAG('c', 'v', '7', '8')); + feature_sets.insert("character_variant_79", HB_TAG('c', 'v', '7', '9')); + feature_sets.insert("character_variant_80", HB_TAG('c', 'v', '8', '0')); + feature_sets.insert("character_variant_81", HB_TAG('c', 'v', '8', '1')); + feature_sets.insert("character_variant_82", HB_TAG('c', 'v', '8', '2')); + feature_sets.insert("character_variant_83", HB_TAG('c', 'v', '8', '3')); + feature_sets.insert("character_variant_84", HB_TAG('c', 'v', '8', '4')); + feature_sets.insert("character_variant_85", HB_TAG('c', 'v', '8', '5')); + feature_sets.insert("character_variant_86", HB_TAG('c', 'v', '8', '6')); + feature_sets.insert("character_variant_87", HB_TAG('c', 'v', '8', '7')); + feature_sets.insert("character_variant_88", HB_TAG('c', 'v', '8', '8')); + feature_sets.insert("character_variant_89", HB_TAG('c', 'v', '8', '9')); + feature_sets.insert("character_variant_90", HB_TAG('c', 'v', '9', '0')); + feature_sets.insert("character_variant_91", HB_TAG('c', 'v', '9', '1')); + feature_sets.insert("character_variant_92", HB_TAG('c', 'v', '9', '2')); + feature_sets.insert("character_variant_93", HB_TAG('c', 'v', '9', '3')); + feature_sets.insert("character_variant_94", HB_TAG('c', 'v', '9', '4')); + feature_sets.insert("character_variant_95", HB_TAG('c', 'v', '9', '5')); + feature_sets.insert("character_variant_96", HB_TAG('c', 'v', '9', '6')); + feature_sets.insert("character_variant_97", HB_TAG('c', 'v', '9', '7')); + feature_sets.insert("character_variant_98", HB_TAG('c', 'v', '9', '8')); + feature_sets.insert("character_variant_99", HB_TAG('c', 'v', '9', '9')); + feature_sets.insert("petite_capitals_from_capitals", HB_TAG('c', '2', 'p', 'c')); + feature_sets.insert("small_capitals_from_capitals", HB_TAG('c', '2', 's', 'c')); + feature_sets.insert("distances", HB_TAG('d', 'i', 's', 't')); + feature_sets.insert("discretionary_ligatures", HB_TAG('d', 'l', 'i', 'g')); + feature_sets.insert("denominators", HB_TAG('d', 'n', 'o', 'm')); + feature_sets.insert("dotless_forms", HB_TAG('d', 't', 'l', 's')); + feature_sets.insert("expert_forms", HB_TAG('e', 'x', 'p', 't')); + feature_sets.insert("final_glyph_on_line_alternates", HB_TAG('f', 'a', 'l', 't')); + feature_sets.insert("terminal_forms_2", HB_TAG('f', 'i', 'n', '2')); + feature_sets.insert("terminal_forms_3", HB_TAG('f', 'i', 'n', '3')); + feature_sets.insert("terminal_forms", HB_TAG('f', 'i', 'n', 'a')); + feature_sets.insert("flattened_accent_forms", HB_TAG('f', 'l', 'a', 'c')); + feature_sets.insert("fractions", HB_TAG('f', 'r', 'a', 'c')); + feature_sets.insert("full_widths", HB_TAG('f', 'w', 'i', 'd')); + feature_sets.insert("half_forms", HB_TAG('h', 'a', 'l', 'f')); + feature_sets.insert("halant_forms", HB_TAG('h', 'a', 'l', 'n')); + feature_sets.insert("alternate_half_widths", HB_TAG('h', 'a', 'l', 't')); + feature_sets.insert("historical_forms", HB_TAG('h', 'i', 's', 't')); + feature_sets.insert("horizontal_kana_alternates", HB_TAG('h', 'k', 'n', 'a')); + feature_sets.insert("historical_ligatures", HB_TAG('h', 'l', 'i', 'g')); + feature_sets.insert("hangul", HB_TAG('h', 'n', 'g', 'l')); + feature_sets.insert("hojo_kanji_forms", HB_TAG('h', 'o', 'j', 'o')); + feature_sets.insert("half_widths", HB_TAG('h', 'w', 'i', 'd')); + feature_sets.insert("initial_forms", HB_TAG('i', 'n', 'i', 't')); + feature_sets.insert("isolated_forms", HB_TAG('i', 's', 'o', 'l')); + feature_sets.insert("italics", HB_TAG('i', 't', 'a', 'l')); + feature_sets.insert("justification_alternates", HB_TAG('j', 'a', 'l', 't')); + feature_sets.insert("jis78_forms", HB_TAG('j', 'p', '7', '8')); + feature_sets.insert("jis83_forms", HB_TAG('j', 'p', '8', '3')); + feature_sets.insert("jis90_forms", HB_TAG('j', 'p', '9', '0')); + feature_sets.insert("jis2004_forms", HB_TAG('j', 'p', '0', '4')); + feature_sets.insert("kerning", HB_TAG('k', 'e', 'r', 'n')); + feature_sets.insert("left_bounds", HB_TAG('l', 'f', 'b', 'd')); + feature_sets.insert("standard_ligatures", HB_TAG('l', 'i', 'g', 'a')); + feature_sets.insert("leading_jamo_forms", HB_TAG('l', 'j', 'm', 'o')); + feature_sets.insert("lining_figures", HB_TAG('l', 'n', 'u', 'm')); + feature_sets.insert("localized_forms", HB_TAG('l', 'o', 'c', 'l')); + feature_sets.insert("left_to_right_alternates", HB_TAG('l', 't', 'r', 'a')); + feature_sets.insert("left_to_right_mirrored_forms", HB_TAG('l', 't', 'r', 'm')); + feature_sets.insert("mark_positioning", HB_TAG('m', 'a', 'r', 'k')); + feature_sets.insert("medial_forms_2", HB_TAG('m', 'e', 'd', '2')); + feature_sets.insert("medial_forms", HB_TAG('m', 'e', 'd', 'i')); + feature_sets.insert("mathematical_greek", HB_TAG('m', 'g', 'r', 'k')); + feature_sets.insert("mark_to_mark_positioning", HB_TAG('m', 'k', 'm', 'k')); + feature_sets.insert("mark_positioning_via_substitution", HB_TAG('m', 's', 'e', 't')); + feature_sets.insert("alternate_annotation_forms", HB_TAG('n', 'a', 'l', 't')); + feature_sets.insert("nlc_kanji_forms", HB_TAG('n', 'l', 'c', 'k')); + feature_sets.insert("nukta_forms", HB_TAG('n', 'u', 'k', 't')); + feature_sets.insert("numerators", HB_TAG('n', 'u', 'm', 'r')); + feature_sets.insert("oldstyle_figures", HB_TAG('o', 'n', 'u', 'm')); + feature_sets.insert("optical_bounds", HB_TAG('o', 'p', 'b', 'd')); + feature_sets.insert("ordinals", HB_TAG('o', 'r', 'd', 'n')); + feature_sets.insert("ornaments", HB_TAG('o', 'r', 'n', 'm')); + feature_sets.insert("proportional_alternate_widths", HB_TAG('p', 'a', 'l', 't')); + feature_sets.insert("petite_capitals", HB_TAG('p', 'c', 'a', 'p')); + feature_sets.insert("proportional_kana", HB_TAG('p', 'k', 'n', 'a')); + feature_sets.insert("proportional_figures", HB_TAG('p', 'n', 'u', 'm')); + feature_sets.insert("pre_base_forms", HB_TAG('p', 'r', 'e', 'f')); + feature_sets.insert("pre_base_substitutions", HB_TAG('p', 'r', 'e', 's')); + feature_sets.insert("post_base_forms", HB_TAG('p', 's', 't', 'f')); + feature_sets.insert("post_base_substitutions", HB_TAG('p', 's', 't', 's')); + feature_sets.insert("proportional_widths", HB_TAG('p', 'w', 'i', 'd')); + feature_sets.insert("quarter_widths", HB_TAG('q', 'w', 'i', 'd')); + feature_sets.insert("randomize", HB_TAG('r', 'a', 'n', 'd')); + feature_sets.insert("required_contextual_alternates", HB_TAG('r', 'c', 'l', 't')); + feature_sets.insert("rakar_forms", HB_TAG('r', 'k', 'r', 'f')); + feature_sets.insert("required_ligatures", HB_TAG('r', 'l', 'i', 'g')); + feature_sets.insert("reph_forms", HB_TAG('r', 'p', 'h', 'f')); + feature_sets.insert("right_bounds", HB_TAG('r', 't', 'b', 'd')); + feature_sets.insert("right_to_left_alternates", HB_TAG('r', 't', 'l', 'a')); + feature_sets.insert("right_to_left_mirrored_forms", HB_TAG('r', 't', 'l', 'm')); + feature_sets.insert("ruby_notation_forms", HB_TAG('r', 'u', 'b', 'y')); + feature_sets.insert("required_variation_alternates", HB_TAG('r', 'v', 'r', 'n')); + feature_sets.insert("stylistic_alternates", HB_TAG('s', 'a', 'l', 't')); + feature_sets.insert("scientific_inferiors", HB_TAG('s', 'i', 'n', 'f')); + feature_sets.insert("optical_size", HB_TAG('s', 'i', 'z', 'e')); + feature_sets.insert("small_capitals", HB_TAG('s', 'm', 'c', 'p')); + feature_sets.insert("simplified_forms", HB_TAG('s', 'm', 'p', 'l')); + feature_sets.insert("stylistic_set_01", HB_TAG('s', 's', '0', '1')); + feature_sets.insert("stylistic_set_02", HB_TAG('s', 's', '0', '2')); + feature_sets.insert("stylistic_set_03", HB_TAG('s', 's', '0', '3')); + feature_sets.insert("stylistic_set_04", HB_TAG('s', 's', '0', '4')); + feature_sets.insert("stylistic_set_05", HB_TAG('s', 's', '0', '5')); + feature_sets.insert("stylistic_set_06", HB_TAG('s', 's', '0', '6')); + feature_sets.insert("stylistic_set_07", HB_TAG('s', 's', '0', '7')); + feature_sets.insert("stylistic_set_08", HB_TAG('s', 's', '0', '8')); + feature_sets.insert("stylistic_set_09", HB_TAG('s', 's', '0', '9')); + feature_sets.insert("stylistic_set_10", HB_TAG('s', 's', '1', '0')); + feature_sets.insert("stylistic_set_11", HB_TAG('s', 's', '1', '1')); + feature_sets.insert("stylistic_set_12", HB_TAG('s', 's', '1', '2')); + feature_sets.insert("stylistic_set_13", HB_TAG('s', 's', '1', '3')); + feature_sets.insert("stylistic_set_14", HB_TAG('s', 's', '1', '4')); + feature_sets.insert("stylistic_set_15", HB_TAG('s', 's', '1', '5')); + feature_sets.insert("stylistic_set_16", HB_TAG('s', 's', '1', '6')); + feature_sets.insert("stylistic_set_17", HB_TAG('s', 's', '1', '7')); + feature_sets.insert("stylistic_set_18", HB_TAG('s', 's', '1', '8')); + feature_sets.insert("stylistic_set_19", HB_TAG('s', 's', '1', '9')); + feature_sets.insert("stylistic_set_20", HB_TAG('s', 's', '2', '0')); + feature_sets.insert("math_script_style_alternates", HB_TAG('s', 's', 't', 'y')); + feature_sets.insert("stretching_glyph_decomposition", HB_TAG('s', 't', 'c', 'h')); + feature_sets.insert("subscript", HB_TAG('s', 'u', 'b', 's')); + feature_sets.insert("superscript", HB_TAG('s', 'u', 'p', 's')); + feature_sets.insert("swash", HB_TAG('s', 'w', 's', 'h')); + feature_sets.insert("titling", HB_TAG('t', 'i', 't', 'l')); + feature_sets.insert("trailing_jamo_forms", HB_TAG('t', 'j', 'm', 'o')); + feature_sets.insert("traditional_name_forms", HB_TAG('t', 'n', 'a', 'm')); + feature_sets.insert("tabular_figures", HB_TAG('t', 'n', 'u', 'm')); + feature_sets.insert("traditional_forms", HB_TAG('t', 'r', 'a', 'd')); + feature_sets.insert("third_widths", HB_TAG('t', 'w', 'i', 'd')); + feature_sets.insert("unicase", HB_TAG('u', 'n', 'i', 'c')); + feature_sets.insert("alternate_vertical_metrics", HB_TAG('v', 'a', 'l', 't')); + feature_sets.insert("vattu_variants", HB_TAG('v', 'a', 't', 'u')); + feature_sets.insert("vertical_writing", HB_TAG('v', 'e', 'r', 't')); + feature_sets.insert("alternate_vertical_half_metrics", HB_TAG('v', 'h', 'a', 'l')); + feature_sets.insert("vowel_jamo_forms", HB_TAG('v', 'j', 'm', 'o')); + feature_sets.insert("vertical_kana_alternates", HB_TAG('v', 'k', 'n', 'a')); + feature_sets.insert("vertical_kerning", HB_TAG('v', 'k', 'r', 'n')); + feature_sets.insert("proportional_alternate_vertical_metrics", HB_TAG('v', 'p', 'a', 'l')); + feature_sets.insert("vertical_alternates_and_rotation", HB_TAG('v', 'r', 't', '2')); + feature_sets.insert("vertical_alternates_for_rotation", HB_TAG('v', 'r', 't', 'r')); + feature_sets.insert("slashed_zero", HB_TAG('z', 'e', 'r', 'o')); + // Registered OpenType variation tag. + feature_sets.insert("italic", HB_TAG('i', 't', 'a', 'l')); + feature_sets.insert("optical_size", HB_TAG('o', 'p', 's', 'z')); + feature_sets.insert("slant", HB_TAG('s', 'l', 'n', 't')); + feature_sets.insert("width", HB_TAG('w', 'd', 't', 'h')); + feature_sets.insert("weight", HB_TAG('w', 'g', 'h', 't')); +} + +int32_t TextServerAdvanced::name_to_tag(const String &p_name) const { + if (feature_sets.has(p_name)) { + return feature_sets[p_name]; } // No readable name, use tag string. return hb_tag_from_string(p_name.replace("custom_", "").ascii().get_data(), -1); } -String TextServerAdvanced::tag_to_name(int32_t p_tag) { - for (int i = 0; feature_set[i].tag != 0; i++) { - if (feature_set[i].tag == p_tag) { - return feature_set[i].name; +String TextServerAdvanced::tag_to_name(int32_t p_tag) const { + for (const KeyValue<StringName, int32_t> &E : feature_sets) { + if (E.value == p_tag) { + return E.key; } } @@ -523,426 +715,2094 @@ String TextServerAdvanced::tag_to_name(int32_t p_tag) { } /*************************************************************************/ -/* Font interface */ +/* Font Glyph Rendering */ /*************************************************************************/ -RID TextServerAdvanced::create_font_system(const String &p_name, int p_base_size) { - ERR_FAIL_V_MSG(RID(), "System fonts are not supported by this text server."); +_FORCE_INLINE_ TextServerAdvanced::FontTexturePosition TextServerAdvanced::find_texture_pos_for_glyph(FontDataForSizeAdvanced *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) const { + FontTexturePosition ret; + ret.index = -1; + + int mw = p_width; + int mh = p_height; + + for (int i = 0; i < p_data->textures.size(); i++) { + const FontTexture &ct = p_data->textures[i]; + + if (RenderingServer::get_singleton() != nullptr) { + if (ct.texture->get_format() != p_image_format) { + continue; + } + } + + if (mw > ct.texture_w || mh > ct.texture_h) { // Too big for this texture. + continue; + } + + ret.y = 0x7FFFFFFF; + ret.x = 0; + + for (int j = 0; j < ct.texture_w - mw; j++) { + int max_y = 0; + + for (int k = j; k < j + mw; k++) { + int y = ct.offsets[k]; + if (y > max_y) { + max_y = y; + } + } + + if (max_y < ret.y) { + ret.y = max_y; + ret.x = j; + } + } + + if (ret.y == 0x7FFFFFFF || ret.y + mh > ct.texture_h) { + continue; // Fail, could not fit it here. + } + + ret.index = i; + break; + } + + if (ret.index == -1) { + // Could not find texture to fit, create one. + ret.x = 0; + ret.y = 0; + + int texsize = MAX(p_data->size.x * p_data->oversampling * 8, 256); + if (mw > texsize) { + texsize = mw; // Special case, adapt to it? + } + if (mh > texsize) { + texsize = mh; // Special case, adapt to it? + } + + texsize = next_power_of_2(texsize); + + texsize = MIN(texsize, 4096); + + FontTexture tex; + tex.texture_w = texsize; + tex.texture_h = texsize; + tex.format = p_image_format; + tex.imgdata.resize(texsize * texsize * p_color_size); + + { + // Zero texture. + uint8_t *w = tex.imgdata.ptrw(); + ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.imgdata.size(), ret); + // Initialize the texture to all-white pixels to prevent artifacts when the + // font is displayed at a non-default scale with filtering enabled. + if (p_color_size == 2) { + for (int i = 0; i < texsize * texsize * p_color_size; i += 2) { // FORMAT_LA8, BW font. + w[i + 0] = 255; + w[i + 1] = 0; + } + } else if (p_color_size == 4) { + for (int i = 0; i < texsize * texsize * p_color_size; i += 4) { // FORMAT_RGBA8, Color font, Multichannel(+True) SDF. + w[i + 0] = 255; + w[i + 1] = 255; + w[i + 2] = 255; + w[i + 3] = 0; + } + } else { + ERR_FAIL_V(ret); + } + } + tex.offsets.resize(texsize); + for (int i = 0; i < texsize; i++) { // Zero offsets. + tex.offsets.write[i] = 0; + } + + p_data->textures.push_back(tex); + ret.index = p_data->textures.size() - 1; + } + + return ret; } -RID TextServerAdvanced::create_font_resource(const String &p_filename, int p_base_size) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = nullptr; - if (p_filename.get_extension() == "fnt" || p_filename.get_extension() == "font") { - fd = memnew(BitmapFontDataAdvanced); +#ifdef MODULE_MSDFGEN_ENABLED + +struct MSContext { + msdfgen::Point2 position; + msdfgen::Shape *shape; + msdfgen::Contour *contour; +}; + +class DistancePixelConversion { + double invRange; + +public: + _FORCE_INLINE_ explicit DistancePixelConversion(double range) : + invRange(1 / range) {} + _FORCE_INLINE_ void operator()(float *pixels, const msdfgen::MultiAndTrueDistance &distance) const { + pixels[0] = float(invRange * distance.r + .5); + pixels[1] = float(invRange * distance.g + .5); + pixels[2] = float(invRange * distance.b + .5); + pixels[3] = float(invRange * distance.a + .5); + } +}; + +struct MSDFThreadData { + msdfgen::Bitmap<float, 4> *output; + msdfgen::Shape *shape; + msdfgen::Projection *projection; + DistancePixelConversion *distancePixelConversion; +}; + +static msdfgen::Point2 ft_point2(const FT_Vector &vector) { + return msdfgen::Point2(vector.x / 60.0f, vector.y / 60.0f); +} + +static int ft_move_to(const FT_Vector *to, void *user) { + MSContext *context = reinterpret_cast<MSContext *>(user); + if (!(context->contour && context->contour->edges.empty())) { + context->contour = &context->shape->addContour(); + } + context->position = ft_point2(*to); + return 0; +} + +static int ft_line_to(const FT_Vector *to, void *user) { + MSContext *context = reinterpret_cast<MSContext *>(user); + msdfgen::Point2 endpoint = ft_point2(*to); + if (endpoint != context->position) { + context->contour->addEdge(new msdfgen::LinearSegment(context->position, endpoint)); + context->position = endpoint; + } + return 0; +} + +static int ft_conic_to(const FT_Vector *control, const FT_Vector *to, void *user) { + MSContext *context = reinterpret_cast<MSContext *>(user); + context->contour->addEdge(new msdfgen::QuadraticSegment(context->position, ft_point2(*control), ft_point2(*to))); + context->position = ft_point2(*to); + return 0; +} + +static int ft_cubic_to(const FT_Vector *control1, const FT_Vector *control2, const FT_Vector *to, void *user) { + MSContext *context = reinterpret_cast<MSContext *>(user); + context->contour->addEdge(new msdfgen::CubicSegment(context->position, ft_point2(*control1), ft_point2(*control2), ft_point2(*to))); + context->position = ft_point2(*to); + return 0; +} + +void TextServerAdvanced::_generateMTSDF_threaded(uint32_t y, void *p_td) const { + MSDFThreadData *td = (MSDFThreadData *)p_td; + + msdfgen::ShapeDistanceFinder<msdfgen::OverlappingContourCombiner<msdfgen::MultiAndTrueDistanceSelector>> distanceFinder(*td->shape); + int row = td->shape->inverseYAxis ? td->output->height() - y - 1 : y; + for (int col = 0; col < td->output->width(); ++col) { + int x = (y % 2) ? td->output->width() - col - 1 : col; + msdfgen::Point2 p = td->projection->unproject(msdfgen::Point2(x + .5, y + .5)); + msdfgen::MultiAndTrueDistance distance = distanceFinder.distance(p); + td->distancePixelConversion->operator()(td->output->operator()(x, row), distance); + } +} + +_FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_msdf(FontDataAdvanced *p_font_data, FontDataForSizeAdvanced *p_data, int p_pixel_range, int p_rect_margin, FT_Outline *outline, const Vector2 &advance) const { + msdfgen::Shape shape; + + shape.contours.clear(); + shape.inverseYAxis = false; + + MSContext context = {}; + context.shape = &shape; + FT_Outline_Funcs ft_functions; + ft_functions.move_to = &ft_move_to; + ft_functions.line_to = &ft_line_to; + ft_functions.conic_to = &ft_conic_to; + ft_functions.cubic_to = &ft_cubic_to; + ft_functions.shift = 0; + ft_functions.delta = 0; + + int error = FT_Outline_Decompose(outline, &ft_functions, &context); + ERR_FAIL_COND_V_MSG(error, FontGlyph(), "FreeType: Outline decomposition error: '" + String(FT_Error_String(error)) + "'."); + if (!shape.contours.empty() && shape.contours.back().edges.empty()) { + shape.contours.pop_back(); + } + + if (FT_Outline_Get_Orientation(outline) == 1) { + for (int i = 0; i < (int)shape.contours.size(); ++i) { + shape.contours[i].reverse(); + } + } + + shape.inverseYAxis = true; + shape.normalize(); + + msdfgen::Shape::Bounds bounds = shape.getBounds(p_pixel_range); + + FontGlyph chr; + chr.found = true; + chr.advance = advance.round(); + + if (shape.validate() && shape.contours.size() > 0) { + int w = (bounds.r - bounds.l); + int h = (bounds.t - bounds.b); + + int mw = w + p_rect_margin * 2; + int mh = h + p_rect_margin * 2; + + ERR_FAIL_COND_V(mw > 4096, FontGlyph()); + ERR_FAIL_COND_V(mh > 4096, FontGlyph()); + + FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, 4, Image::FORMAT_RGBA8, mw, mh); + ERR_FAIL_COND_V(tex_pos.index < 0, FontGlyph()); + FontTexture &tex = p_data->textures.write[tex_pos.index]; + + edgeColoringSimple(shape, 3.0); // Max. angle. + msdfgen::Bitmap<float, 4> image(w, h); // Texture size. + //msdfgen::generateMTSDF(image, shape, p_pixel_range, 1.0, msdfgen::Vector2(-bounds.l, -bounds.b)); // Range, scale, translation. + + DistancePixelConversion distancePixelConversion(p_pixel_range); + msdfgen::Projection projection(msdfgen::Vector2(1.0, 1.0), msdfgen::Vector2(-bounds.l, -bounds.b)); + msdfgen::MSDFGeneratorConfig config(true, msdfgen::ErrorCorrectionConfig()); + + MSDFThreadData td; + td.output = ℑ + td.shape = &shape; + td.projection = &projection; + td.distancePixelConversion = &distancePixelConversion; + + if (p_font_data->work_pool.get_thread_count() == 0) { + p_font_data->work_pool.init(); + } + p_font_data->work_pool.do_work(h, this, &TextServerAdvanced::_generateMTSDF_threaded, &td); + + msdfgen::msdfErrorCorrection(image, shape, projection, p_pixel_range, config); + + { + uint8_t *wr = tex.imgdata.ptrw(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + int ofs = ((i + tex_pos.y + p_rect_margin) * tex.texture_w + j + tex_pos.x + p_rect_margin) * 4; + ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), FontGlyph()); + wr[ofs + 0] = (uint8_t)(CLAMP(image(j, i)[0] * 256.f, 0.f, 255.f)); + wr[ofs + 1] = (uint8_t)(CLAMP(image(j, i)[1] * 256.f, 0.f, 255.f)); + wr[ofs + 2] = (uint8_t)(CLAMP(image(j, i)[2] * 256.f, 0.f, 255.f)); + wr[ofs + 3] = (uint8_t)(CLAMP(image(j, i)[3] * 256.f, 0.f, 255.f)); + //wr[ofs + 0] = 100; + //wr[ofs + 1] = 100; + //wr[ofs + 2] = 100; + //wr[ofs + 3] = 100; + } + } + } + + // Blit to image and texture. + { + if (RenderingServer::get_singleton() != nullptr) { + Ref<Image> img = memnew(Image(tex.texture_w, tex.texture_h, 0, Image::FORMAT_RGBA8, tex.imgdata)); + if (tex.texture.is_null()) { + tex.texture.instantiate(); + tex.texture->create_from_image(img); + } else { + tex.texture->update(img); + } + } + } + + // Update height array. + for (int k = tex_pos.x; k < tex_pos.x + mw; k++) { + tex.offsets.write[k] = tex_pos.y + mh; + } + + chr.texture_idx = tex_pos.index; + + chr.uv_rect = Rect2(tex_pos.x + p_rect_margin, tex_pos.y + p_rect_margin, w, h); + chr.rect.position = Vector2(bounds.l, -bounds.t); + chr.rect.size = chr.uv_rect.size; + } + return chr; +} +#endif + #ifdef MODULE_FREETYPE_ENABLED - } else if (p_filename.get_extension() == "ttf" || p_filename.get_extension() == "otf" || p_filename.get_extension() == "woff") { - fd = memnew(DynamicFontDataAdvanced); +_FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitmap(FontDataForSizeAdvanced *p_data, int p_rect_margin, FT_Bitmap bitmap, int yofs, int xofs, const Vector2 &advance) const { + int w = bitmap.width; + int h = bitmap.rows; + + int mw = w + p_rect_margin * 2; + int mh = h + p_rect_margin * 2; + + ERR_FAIL_COND_V(mw > 4096, FontGlyph()); + ERR_FAIL_COND_V(mh > 4096, FontGlyph()); + + int color_size = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2; + Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8; + + FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, color_size, require_format, mw, mh); + ERR_FAIL_COND_V(tex_pos.index < 0, FontGlyph()); + + // Fit character in char texture. + + FontTexture &tex = p_data->textures.write[tex_pos.index]; + + { + uint8_t *wr = tex.imgdata.ptrw(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + int ofs = ((i + tex_pos.y + p_rect_margin) * tex.texture_w + j + tex_pos.x + p_rect_margin) * color_size; + ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), FontGlyph()); + switch (bitmap.pixel_mode) { + case FT_PIXEL_MODE_MONO: { + int byte = i * bitmap.pitch + (j >> 3); + int bit = 1 << (7 - (j % 8)); + wr[ofs + 0] = 255; //grayscale as 1 + wr[ofs + 1] = (bitmap.buffer[byte] & bit) ? 255 : 0; + } break; + case FT_PIXEL_MODE_GRAY: + wr[ofs + 0] = 255; //grayscale as 1 + wr[ofs + 1] = bitmap.buffer[i * bitmap.pitch + j]; + //wr[ofs + 1] = 100; + break; + case FT_PIXEL_MODE_BGRA: { + int ofs_color = i * bitmap.pitch + (j << 2); + wr[ofs + 2] = bitmap.buffer[ofs_color + 0]; + wr[ofs + 1] = bitmap.buffer[ofs_color + 1]; + wr[ofs + 0] = bitmap.buffer[ofs_color + 2]; + wr[ofs + 3] = bitmap.buffer[ofs_color + 3]; + } break; + default: + ERR_FAIL_V_MSG(FontGlyph(), "Font uses unsupported pixel format: " + itos(bitmap.pixel_mode) + "."); + break; + } + } + } + } + + // Blit to image and texture. + { + if (RenderingServer::get_singleton() != nullptr) { + Ref<Image> img = memnew(Image(tex.texture_w, tex.texture_h, 0, require_format, tex.imgdata)); + + if (tex.texture.is_null()) { + tex.texture.instantiate(); + tex.texture->create_from_image(img); + } else { + tex.texture->update(img); + } + } + } + + // Update height array. + for (int k = tex_pos.x; k < tex_pos.x + mw; k++) { + tex.offsets.write[k] = tex_pos.y + mh; + } + + FontGlyph chr; + chr.advance = (advance * p_data->scale / p_data->oversampling).round(); + chr.texture_idx = tex_pos.index; + chr.found = true; + + chr.uv_rect = Rect2(tex_pos.x + p_rect_margin, tex_pos.y + p_rect_margin, w, h); + chr.rect.position = (Vector2(xofs, -yofs) * p_data->scale / p_data->oversampling).round(); + chr.rect.size = chr.uv_rect.size * p_data->scale / p_data->oversampling; + return chr; +} #endif - } else { - return RID(); + +/*************************************************************************/ +/* Font Cache */ +/*************************************************************************/ + +_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontDataAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false); + + FontDataForSizeAdvanced *fd = p_font_data->cache[p_size]; + if (fd->glyph_map.has(p_glyph)) { + return fd->glyph_map[p_glyph].found; } - Error err = fd->load_from_file(p_filename, p_base_size); - if (err != OK) { - memdelete(fd); - return RID(); + if (p_glyph == 0) { // Non graphical or invalid glyph, do not render. + fd->glyph_map[p_glyph] = FontGlyph(); + return true; } - return font_owner.make_rid(fd); +#ifdef MODULE_FREETYPE_ENABLED + FontGlyph gl; + if (fd->face) { + FT_Int32 flags = FT_LOAD_DEFAULT; + + bool outline = p_size.y > 0; + switch (p_font_data->hinting) { + case TextServer::HINTING_NONE: + flags |= FT_LOAD_NO_HINTING; + break; + case TextServer::HINTING_LIGHT: + flags |= FT_LOAD_TARGET_LIGHT; + break; + default: + flags |= FT_LOAD_TARGET_NORMAL; + break; + } + if (p_font_data->force_autohinter) { + flags |= FT_LOAD_FORCE_AUTOHINT; + } + if (outline) { + flags |= FT_LOAD_NO_BITMAP; + } else if (FT_HAS_COLOR(fd->face)) { + flags |= FT_LOAD_COLOR; + } + + FT_Fixed v, h; + FT_Get_Advance(fd->face, p_glyph, flags, &h); + FT_Get_Advance(fd->face, p_glyph, flags | FT_LOAD_VERTICAL_LAYOUT, &v); + + int error = FT_Load_Glyph(fd->face, p_glyph, flags); + if (error) { + fd->glyph_map[p_glyph] = FontGlyph(); + ERR_FAIL_V_MSG(false, "FreeType: Failed to load glyph."); + } + + if (!outline) { + if (!p_font_data->msdf) { + error = FT_Render_Glyph(fd->face->glyph, p_font_data->antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); + } + FT_GlyphSlot slot = fd->face->glyph; + if (!error) { + if (p_font_data->msdf) { +#ifdef MODULE_MSDFGEN_ENABLED + gl = rasterize_msdf(p_font_data, fd, p_font_data->msdf_range, rect_range, &slot->outline, Vector2((h + (1 << 9)) >> 10, (v + (1 << 9)) >> 10) / 64.0); +#else + fd->glyph_map[p_glyph] = FontGlyph(); + ERR_FAIL_V_MSG(false, "Compiled without MSDFGEN support!"); +#endif + } else { + gl = rasterize_bitmap(fd, rect_range, slot->bitmap, slot->bitmap_top, slot->bitmap_left, Vector2((h + (1 << 9)) >> 10, (v + (1 << 9)) >> 10) / 64.0); + } + } + } else { + FT_Stroker stroker; + if (FT_Stroker_New(library, &stroker) != 0) { + fd->glyph_map[p_glyph] = FontGlyph(); + ERR_FAIL_V_MSG(false, "FreeType: Failed to load glyph stroker."); + } + + FT_Stroker_Set(stroker, (int)(fd->size.y * fd->oversampling * 16.0), FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0); + FT_Glyph glyph; + FT_BitmapGlyph glyph_bitmap; + + if (FT_Get_Glyph(fd->face->glyph, &glyph) != 0) { + goto cleanup_stroker; + } + if (FT_Glyph_Stroke(&glyph, stroker, 1) != 0) { + goto cleanup_glyph; + } + if (FT_Glyph_To_Bitmap(&glyph, p_font_data->antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, nullptr, 1) != 0) { + goto cleanup_glyph; + } + glyph_bitmap = (FT_BitmapGlyph)glyph; + gl = rasterize_bitmap(fd, rect_range, glyph_bitmap->bitmap, glyph_bitmap->top, glyph_bitmap->left, Vector2()); + + cleanup_glyph: + FT_Done_Glyph(glyph); + cleanup_stroker: + FT_Stroker_Done(stroker); + } + fd->glyph_map[p_glyph] = gl; + return gl.found; + } +#endif + fd->glyph_map[p_glyph] = FontGlyph(); + return false; } -RID TextServerAdvanced::create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = nullptr; - if (p_type == "fnt" || p_type == "font") { - fd = memnew(BitmapFontDataAdvanced); +_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontDataAdvanced *p_font_data, const Vector2i &p_size) const { + if (p_font_data->cache.has(p_size)) { + return true; + } + + FontDataForSizeAdvanced *fd = memnew(FontDataForSizeAdvanced); + fd->size = p_size; + if (p_font_data->data_ptr) { + // Init dynamic font. #ifdef MODULE_FREETYPE_ENABLED - } else if (p_type == "ttf" || p_type == "otf" || p_type == "woff") { - fd = memnew(DynamicFontDataAdvanced); + int error = 0; + if (!library) { + error = FT_Init_FreeType(&library); + ERR_FAIL_COND_V_MSG(error != 0, false, TTR("FreeType: Error initializing library:") + " '" + String(FT_Error_String(error)) + "'."); + } + + memset(&fd->stream, 0, sizeof(FT_StreamRec)); + fd->stream.base = (unsigned char *)p_font_data->data_ptr; + fd->stream.size = p_font_data->data_size; + fd->stream.pos = 0; + + FT_Open_Args fargs; + memset(&fargs, 0, sizeof(FT_Open_Args)); + fargs.memory_base = (unsigned char *)p_font_data->data_ptr; + fargs.memory_size = p_font_data->data_size; + fargs.flags = FT_OPEN_MEMORY; + fargs.stream = &fd->stream; + error = FT_Open_Face(library, &fargs, 0, &fd->face); + if (error) { + FT_Done_Face(fd->face); + fd->face = nullptr; + ERR_FAIL_V_MSG(false, TTR("FreeType: Error loading font:") + " '" + String(FT_Error_String(error)) + "'."); + } + fd->hb_handle = hb_ft_font_create(fd->face, nullptr); + if (fd->hb_handle == nullptr) { + FT_Done_Face(fd->face); + fd->face = nullptr; + ERR_FAIL_V_MSG(false, TTR("HarfBuzz: Error creating FreeType font object.")); + } + + if (p_font_data->msdf) { + fd->oversampling = 1.0f; + fd->size.x = p_font_data->msdf_source_size; + } else if (p_font_data->oversampling <= 0.0f) { + fd->oversampling = TS->font_get_global_oversampling(); + } else { + fd->oversampling = p_font_data->oversampling; + } + + if (FT_HAS_COLOR(fd->face) && fd->face->num_fixed_sizes > 0) { + int best_match = 0; + int diff = ABS(fd->size.x - ((int64_t)fd->face->available_sizes[0].width)); + fd->scale = real_t(fd->size.x * fd->oversampling) / fd->face->available_sizes[0].width; + for (int i = 1; i < fd->face->num_fixed_sizes; i++) { + int ndiff = ABS(fd->size.x - ((int64_t)fd->face->available_sizes[i].width)); + if (ndiff < diff) { + best_match = i; + diff = ndiff; + fd->scale = real_t(fd->size.x * fd->oversampling) / fd->face->available_sizes[i].width; + } + } + FT_Select_Size(fd->face, best_match); + } else { + FT_Set_Pixel_Sizes(fd->face, 0, fd->size.x * fd->oversampling); + } + + fd->ascent = (fd->face->size->metrics.ascender / 64.0) / fd->oversampling * fd->scale; + fd->descent = (-fd->face->size->metrics.descender / 64.0) / fd->oversampling * fd->scale; + fd->underline_position = (-FT_MulFix(fd->face->underline_position, fd->face->size->metrics.y_scale) / 64.0) / fd->oversampling * fd->scale; + fd->underline_thickness = (FT_MulFix(fd->face->underline_thickness, fd->face->size->metrics.y_scale) / 64.0) / fd->oversampling * fd->scale; + + if (!p_font_data->face_init) { + // Get supported scripts from OpenType font data. + p_font_data->supported_scripts.clear(); + unsigned int count = hb_ot_layout_table_get_script_tags(hb_font_get_face(fd->hb_handle), HB_OT_TAG_GSUB, 0, nullptr, nullptr); + if (count != 0) { + hb_tag_t *script_tags = (hb_tag_t *)memalloc(count * sizeof(hb_tag_t)); + hb_ot_layout_table_get_script_tags(hb_font_get_face(fd->hb_handle), HB_OT_TAG_GSUB, 0, &count, script_tags); + for (unsigned int i = 0; i < count; i++) { + p_font_data->supported_scripts.insert(script_tags[i]); + } + memfree(script_tags); + } + count = hb_ot_layout_table_get_script_tags(hb_font_get_face(fd->hb_handle), HB_OT_TAG_GPOS, 0, nullptr, nullptr); + if (count != 0) { + hb_tag_t *script_tags = (hb_tag_t *)memalloc(count * sizeof(hb_tag_t)); + hb_ot_layout_table_get_script_tags(hb_font_get_face(fd->hb_handle), HB_OT_TAG_GPOS, 0, &count, script_tags); + for (unsigned int i = 0; i < count; i++) { + p_font_data->supported_scripts.insert(script_tags[i]); + } + memfree(script_tags); + } + + // Get supported scripts from OS2 table. + TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(fd->face, FT_SFNT_OS2); + if (os2) { + if ((os2->ulUnicodeRange1 & 1L << 4) || (os2->ulUnicodeRange1 & 1L << 5) || (os2->ulUnicodeRange1 & 1L << 6) || (os2->ulUnicodeRange1 & 1L << 31) || (os2->ulUnicodeRange2 & 1L << 0) || (os2->ulUnicodeRange2 & 1L << 1) || (os2->ulUnicodeRange2 & 1L << 2) || (os2->ulUnicodeRange2 & 1L << 3) || (os2->ulUnicodeRange2 & 1L << 4) || (os2->ulUnicodeRange2 & 1L << 5) || (os2->ulUnicodeRange2 & 1L << 6) || (os2->ulUnicodeRange2 & 1L << 7) || (os2->ulUnicodeRange2 & 1L << 8) || (os2->ulUnicodeRange2 & 1L << 9) || (os2->ulUnicodeRange2 & 1L << 10) || (os2->ulUnicodeRange2 & 1L << 11) || (os2->ulUnicodeRange2 & 1L << 12) || (os2->ulUnicodeRange2 & 1L << 13) || (os2->ulUnicodeRange2 & 1L << 14) || (os2->ulUnicodeRange2 & 1L << 15) || (os2->ulUnicodeRange2 & 1L << 30) || (os2->ulUnicodeRange3 & 1L << 0) || (os2->ulUnicodeRange3 & 1L << 1) || (os2->ulUnicodeRange3 & 1L << 2) || (os2->ulUnicodeRange3 & 1L << 4) || (os2->ulUnicodeRange3 & 1L << 5) || (os2->ulUnicodeRange3 & 1L << 18) || (os2->ulUnicodeRange3 & 1L << 24) || (os2->ulUnicodeRange3 & 1L << 25) || (os2->ulUnicodeRange3 & 1L << 26) || (os2->ulUnicodeRange3 & 1L << 27) || (os2->ulUnicodeRange3 & 1L << 28) || (os2->ulUnicodeRange4 & 1L << 3) || (os2->ulUnicodeRange4 & 1L << 6) || (os2->ulUnicodeRange4 & 1L << 15) || (os2->ulUnicodeRange4 & 1L << 23) || (os2->ulUnicodeRange4 & 1L << 24) || (os2->ulUnicodeRange4 & 1L << 26)) { + p_font_data->supported_scripts.insert(HB_SCRIPT_COMMON); + } + if ((os2->ulUnicodeRange1 & 1L << 0) || (os2->ulUnicodeRange1 & 1L << 1) || (os2->ulUnicodeRange1 & 1L << 2) || (os2->ulUnicodeRange1 & 1L << 3) || (os2->ulUnicodeRange1 & 1L << 29)) { + p_font_data->supported_scripts.insert(HB_SCRIPT_LATIN); + } + if ((os2->ulUnicodeRange1 & 1L << 7) || (os2->ulUnicodeRange1 & 1L << 30)) { + p_font_data->supported_scripts.insert(HB_SCRIPT_GREEK); + } + if (os2->ulUnicodeRange1 & 1L << 8) { + p_font_data->supported_scripts.insert(HB_SCRIPT_COPTIC); + } + if (os2->ulUnicodeRange1 & 1L << 9) { + p_font_data->supported_scripts.insert(HB_SCRIPT_CYRILLIC); + } + if (os2->ulUnicodeRange1 & 1L << 10) { + p_font_data->supported_scripts.insert(HB_SCRIPT_ARMENIAN); + } + if (os2->ulUnicodeRange1 & 1L << 11) { + p_font_data->supported_scripts.insert(HB_SCRIPT_HEBREW); + } + if (os2->ulUnicodeRange1 & 1L << 12) { + p_font_data->supported_scripts.insert(HB_SCRIPT_VAI); + } + if ((os2->ulUnicodeRange1 & 1L << 13) || (os2->ulUnicodeRange2 & 1L << 31) || (os2->ulUnicodeRange3 & 1L << 3)) { + p_font_data->supported_scripts.insert(HB_SCRIPT_ARABIC); + } + if (os2->ulUnicodeRange1 & 1L << 14) { + p_font_data->supported_scripts.insert(HB_SCRIPT_NKO); + } + if (os2->ulUnicodeRange1 & 1L << 15) { + p_font_data->supported_scripts.insert(HB_SCRIPT_DEVANAGARI); + } + if (os2->ulUnicodeRange1 & 1L << 16) { + p_font_data->supported_scripts.insert(HB_SCRIPT_BENGALI); + } + if (os2->ulUnicodeRange1 & 1L << 17) { + p_font_data->supported_scripts.insert(HB_SCRIPT_GURMUKHI); + } + if (os2->ulUnicodeRange1 & 1L << 18) { + p_font_data->supported_scripts.insert(HB_SCRIPT_GUJARATI); + } + if (os2->ulUnicodeRange1 & 1L << 19) { + p_font_data->supported_scripts.insert(HB_SCRIPT_ORIYA); + } + if (os2->ulUnicodeRange1 & 1L << 20) { + p_font_data->supported_scripts.insert(HB_SCRIPT_TAMIL); + } + if (os2->ulUnicodeRange1 & 1L << 21) { + p_font_data->supported_scripts.insert(HB_SCRIPT_TELUGU); + } + if (os2->ulUnicodeRange1 & 1L << 22) { + p_font_data->supported_scripts.insert(HB_SCRIPT_KANNADA); + } + if (os2->ulUnicodeRange1 & 1L << 23) { + p_font_data->supported_scripts.insert(HB_SCRIPT_MALAYALAM); + } + if (os2->ulUnicodeRange1 & 1L << 24) { + p_font_data->supported_scripts.insert(HB_SCRIPT_THAI); + } + if (os2->ulUnicodeRange1 & 1L << 25) { + p_font_data->supported_scripts.insert(HB_SCRIPT_LAO); + } + if (os2->ulUnicodeRange1 & 1L << 26) { + p_font_data->supported_scripts.insert(HB_SCRIPT_GEORGIAN); + } + if (os2->ulUnicodeRange1 & 1L << 27) { + p_font_data->supported_scripts.insert(HB_SCRIPT_BALINESE); + } + if ((os2->ulUnicodeRange1 & 1L << 28) || (os2->ulUnicodeRange2 & 1L << 20) || (os2->ulUnicodeRange2 & 1L << 24)) { + p_font_data->supported_scripts.insert(HB_SCRIPT_HANGUL); + } + if ((os2->ulUnicodeRange2 & 1L << 21) || (os2->ulUnicodeRange2 & 1L << 22) || (os2->ulUnicodeRange2 & 1L << 23) || (os2->ulUnicodeRange2 & 1L << 26) || (os2->ulUnicodeRange2 & 1L << 27) || (os2->ulUnicodeRange2 & 1L << 29)) { + p_font_data->supported_scripts.insert(HB_SCRIPT_HAN); + } + if (os2->ulUnicodeRange2 & 1L << 17) { + p_font_data->supported_scripts.insert(HB_SCRIPT_HIRAGANA); + } + if (os2->ulUnicodeRange2 & 1L << 18) { + p_font_data->supported_scripts.insert(HB_SCRIPT_KATAKANA); + } + if (os2->ulUnicodeRange2 & 1L << 19) { + p_font_data->supported_scripts.insert(HB_SCRIPT_BOPOMOFO); + } + if (os2->ulUnicodeRange3 & 1L << 6) { + p_font_data->supported_scripts.insert(HB_SCRIPT_TIBETAN); + } + if (os2->ulUnicodeRange3 & 1L << 7) { + p_font_data->supported_scripts.insert(HB_SCRIPT_SYRIAC); + } + if (os2->ulUnicodeRange3 & 1L << 8) { + p_font_data->supported_scripts.insert(HB_SCRIPT_THAANA); + } + if (os2->ulUnicodeRange3 & 1L << 9) { + p_font_data->supported_scripts.insert(HB_SCRIPT_SINHALA); + } + if (os2->ulUnicodeRange3 & 1L << 10) { + p_font_data->supported_scripts.insert(HB_SCRIPT_MYANMAR); + } + if (os2->ulUnicodeRange3 & 1L << 11) { + p_font_data->supported_scripts.insert(HB_SCRIPT_ETHIOPIC); + } + if (os2->ulUnicodeRange3 & 1L << 12) { + p_font_data->supported_scripts.insert(HB_SCRIPT_CHEROKEE); + } + if (os2->ulUnicodeRange3 & 1L << 13) { + p_font_data->supported_scripts.insert(HB_SCRIPT_CANADIAN_SYLLABICS); + } + if (os2->ulUnicodeRange3 & 1L << 14) { + p_font_data->supported_scripts.insert(HB_SCRIPT_OGHAM); + } + if (os2->ulUnicodeRange3 & 1L << 15) { + p_font_data->supported_scripts.insert(HB_SCRIPT_RUNIC); + } + if (os2->ulUnicodeRange3 & 1L << 16) { + p_font_data->supported_scripts.insert(HB_SCRIPT_KHMER); + } + if (os2->ulUnicodeRange3 & 1L << 17) { + p_font_data->supported_scripts.insert(HB_SCRIPT_MONGOLIAN); + } + if (os2->ulUnicodeRange3 & 1L << 19) { + p_font_data->supported_scripts.insert(HB_SCRIPT_YI); + } + if (os2->ulUnicodeRange3 & 1L << 20) { + p_font_data->supported_scripts.insert(HB_SCRIPT_HANUNOO); + p_font_data->supported_scripts.insert(HB_SCRIPT_TAGBANWA); + p_font_data->supported_scripts.insert(HB_SCRIPT_BUHID); + p_font_data->supported_scripts.insert(HB_SCRIPT_TAGALOG); + } + if (os2->ulUnicodeRange3 & 1L << 21) { + p_font_data->supported_scripts.insert(HB_SCRIPT_OLD_ITALIC); + } + if (os2->ulUnicodeRange3 & 1L << 22) { + p_font_data->supported_scripts.insert(HB_SCRIPT_GOTHIC); + } + if (os2->ulUnicodeRange3 & 1L << 23) { + p_font_data->supported_scripts.insert(HB_SCRIPT_DESERET); + } + if (os2->ulUnicodeRange3 & 1L << 29) { + p_font_data->supported_scripts.insert(HB_SCRIPT_LIMBU); + } + if (os2->ulUnicodeRange3 & 1L << 30) { + p_font_data->supported_scripts.insert(HB_SCRIPT_TAI_LE); + } + if (os2->ulUnicodeRange3 & 1L << 31) { + p_font_data->supported_scripts.insert(HB_SCRIPT_NEW_TAI_LUE); + } + if (os2->ulUnicodeRange4 & 1L << 0) { + p_font_data->supported_scripts.insert(HB_SCRIPT_BUGINESE); + } + if (os2->ulUnicodeRange4 & 1L << 1) { + p_font_data->supported_scripts.insert(HB_SCRIPT_GLAGOLITIC); + } + if (os2->ulUnicodeRange4 & 1L << 2) { + p_font_data->supported_scripts.insert(HB_SCRIPT_TIFINAGH); + } + if (os2->ulUnicodeRange4 & 1L << 4) { + p_font_data->supported_scripts.insert(HB_SCRIPT_SYLOTI_NAGRI); + } + if (os2->ulUnicodeRange4 & 1L << 5) { + p_font_data->supported_scripts.insert(HB_SCRIPT_LINEAR_B); + } + if (os2->ulUnicodeRange4 & 1L << 7) { + p_font_data->supported_scripts.insert(HB_SCRIPT_UGARITIC); + } + if (os2->ulUnicodeRange4 & 1L << 8) { + p_font_data->supported_scripts.insert(HB_SCRIPT_OLD_PERSIAN); + } + if (os2->ulUnicodeRange4 & 1L << 9) { + p_font_data->supported_scripts.insert(HB_SCRIPT_SHAVIAN); + } + if (os2->ulUnicodeRange4 & 1L << 10) { + p_font_data->supported_scripts.insert(HB_SCRIPT_OSMANYA); + } + if (os2->ulUnicodeRange4 & 1L << 11) { + p_font_data->supported_scripts.insert(HB_SCRIPT_CYPRIOT); + } + if (os2->ulUnicodeRange4 & 1L << 12) { + p_font_data->supported_scripts.insert(HB_SCRIPT_KHAROSHTHI); + } + if (os2->ulUnicodeRange4 & 1L << 13) { + p_font_data->supported_scripts.insert(HB_SCRIPT_TAI_VIET); + } + if (os2->ulUnicodeRange4 & 1L << 14) { + p_font_data->supported_scripts.insert(HB_SCRIPT_CUNEIFORM); + } + if (os2->ulUnicodeRange4 & 1L << 16) { + p_font_data->supported_scripts.insert(HB_SCRIPT_SUNDANESE); + } + if (os2->ulUnicodeRange4 & 1L << 17) { + p_font_data->supported_scripts.insert(HB_SCRIPT_LEPCHA); + } + if (os2->ulUnicodeRange4 & 1L << 18) { + p_font_data->supported_scripts.insert(HB_SCRIPT_OL_CHIKI); + } + if (os2->ulUnicodeRange4 & 1L << 19) { + p_font_data->supported_scripts.insert(HB_SCRIPT_SAURASHTRA); + } + if (os2->ulUnicodeRange4 & 1L << 20) { + p_font_data->supported_scripts.insert(HB_SCRIPT_KAYAH_LI); + } + if (os2->ulUnicodeRange4 & 1L << 21) { + p_font_data->supported_scripts.insert(HB_SCRIPT_REJANG); + } + if (os2->ulUnicodeRange4 & 1L << 22) { + p_font_data->supported_scripts.insert(HB_SCRIPT_CHAM); + } + if (os2->ulUnicodeRange4 & 1L << 25) { + p_font_data->supported_scripts.insert(HB_SCRIPT_ANATOLIAN_HIEROGLYPHS); + } + } + + // Read OpenType feature tags. + p_font_data->supported_features.clear(); + count = hb_ot_layout_table_get_feature_tags(hb_font_get_face(fd->hb_handle), HB_OT_TAG_GSUB, 0, nullptr, nullptr); + if (count != 0) { + hb_tag_t *feature_tags = (hb_tag_t *)memalloc(count * sizeof(hb_tag_t)); + hb_ot_layout_table_get_feature_tags(hb_font_get_face(fd->hb_handle), HB_OT_TAG_GSUB, 0, &count, feature_tags); + for (unsigned int i = 0; i < count; i++) { + p_font_data->supported_features[feature_tags[i]] = 1; + } + memfree(feature_tags); + } + count = hb_ot_layout_table_get_feature_tags(hb_font_get_face(fd->hb_handle), HB_OT_TAG_GPOS, 0, nullptr, nullptr); + if (count != 0) { + hb_tag_t *feature_tags = (hb_tag_t *)memalloc(count * sizeof(hb_tag_t)); + hb_ot_layout_table_get_feature_tags(hb_font_get_face(fd->hb_handle), HB_OT_TAG_GPOS, 0, &count, feature_tags); + for (unsigned int i = 0; i < count; i++) { + p_font_data->supported_features[feature_tags[i]] = 1; + } + memfree(feature_tags); + } + + // Read OpenType variations. + p_font_data->supported_varaitions.clear(); + if (fd->face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { + FT_MM_Var *amaster; + FT_Get_MM_Var(fd->face, &amaster); + for (FT_UInt i = 0; i < amaster->num_axis; i++) { + p_font_data->supported_varaitions[(int32_t)amaster->axis[i].tag] = Vector3i(amaster->axis[i].minimum / 65536, amaster->axis[i].maximum / 65536, amaster->axis[i].def / 65536); + } + FT_Done_MM_Var(library, amaster); + } + p_font_data->face_init = true; + } + + // Write variations. + if (fd->face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { + FT_MM_Var *amaster; + + FT_Get_MM_Var(fd->face, &amaster); + + Vector<hb_variation_t> hb_vars; + Vector<FT_Fixed> coords; + coords.resize(amaster->num_axis); + + FT_Get_Var_Design_Coordinates(fd->face, coords.size(), coords.ptrw()); + + for (FT_UInt i = 0; i < amaster->num_axis; i++) { + hb_variation_t var; + + // Reset to default. + var.tag = amaster->axis[i].tag; + var.value = (double)amaster->axis[i].def / 65536.f; + coords.write[i] = amaster->axis[i].def; + + if (p_font_data->variation_coordinates.has(var.tag)) { + var.value = p_font_data->variation_coordinates[var.tag]; + coords.write[i] = CLAMP(var.value * 65536.f, amaster->axis[i].minimum, amaster->axis[i].maximum); + } + + if (p_font_data->variation_coordinates.has(tag_to_name(var.tag))) { + var.value = p_font_data->variation_coordinates[tag_to_name(var.tag)]; + coords.write[i] = CLAMP(var.value * 65536.f, amaster->axis[i].minimum, amaster->axis[i].maximum); + } + + hb_vars.push_back(var); + } + + FT_Set_Var_Design_Coordinates(fd->face, coords.size(), coords.ptrw()); + hb_font_set_variations(fd->hb_handle, hb_vars.is_empty() ? nullptr : &hb_vars[0], hb_vars.size()); + FT_Done_MM_Var(library, amaster); + } +#else + ERR_FAIL_V_MSG(false, TTR("FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); #endif } else { - return RID(); + // Init bitmap font. + fd->hb_handle = hb_bmp_font_create(fd, nullptr); + if (!fd->hb_handle) { + ERR_FAIL_V_MSG(false, TTR("HarfBuzz: Error creating bitmap font object.")); + } } + p_font_data->cache[p_size] = fd; + return true; +} - Error err = fd->load_from_memory(p_data, p_size, p_base_size); - if (err != OK) { - memdelete(fd); - return RID(); +_FORCE_INLINE_ void TextServerAdvanced::_font_clear_cache(FontDataAdvanced *p_font_data) { + for (const Map<Vector2i, FontDataForSizeAdvanced *>::Element *E = p_font_data->cache.front(); E; E = E->next()) { + memdelete(E->get()); } + p_font_data->cache.clear(); + p_font_data->face_init = false; + p_font_data->supported_features.clear(); + p_font_data->supported_varaitions.clear(); + p_font_data->supported_scripts.clear(); +} + +hb_font_t *TextServerAdvanced::_font_get_hb_handle(RID p_font_rid, int p_size) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, nullptr); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), nullptr); + + return fd->cache[size]->hb_handle; +} + +RID TextServerAdvanced::create_font() { + FontDataAdvanced *fd = memnew(FontDataAdvanced); return font_owner.make_rid(fd); } -RID TextServerAdvanced::create_font_bitmap(float p_height, float p_ascent, int p_base_size) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = memnew(BitmapFontDataAdvanced); - Error err = fd->bitmap_new(p_height, p_ascent, p_base_size); - if (err != OK) { - memdelete(fd); - return RID(); +void TextServerAdvanced::font_set_data(RID p_font_rid, const PackedByteArray &p_data) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + _font_clear_cache(fd); + fd->data = p_data; + fd->data_ptr = fd->data.ptr(); + fd->data_size = fd->data.size(); +} + +void TextServerAdvanced::font_set_data_ptr(RID p_font_rid, const uint8_t *p_data_ptr, size_t p_data_size) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + _font_clear_cache(fd); + fd->data.clear(); + fd->data_ptr = p_data_ptr; + fd->data_size = p_data_size; +} + +void TextServerAdvanced::font_set_antialiased(RID p_font_rid, bool p_antialiased) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->antialiased != p_antialiased) { + _font_clear_cache(fd); + fd->antialiased = p_antialiased; } +} - return font_owner.make_rid(fd); +bool TextServerAdvanced::font_is_antialiased(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->antialiased; } -void TextServerAdvanced::font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +void TextServerAdvanced::font_set_multichannel_signed_distance_field(RID p_font_rid, bool p_msdf) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->bitmap_add_texture(p_texture); + + MutexLock lock(fd->mutex); + if (fd->msdf != p_msdf) { + _font_clear_cache(fd); + fd->msdf = p_msdf; + } } -void TextServerAdvanced::font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +bool TextServerAdvanced::font_is_multichannel_signed_distance_field(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->msdf; +} + +void TextServerAdvanced::font_set_msdf_pixel_range(RID p_font_rid, int p_msdf_pixel_range) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->bitmap_add_char(p_char, p_texture_idx, p_rect, p_align, p_advance); + + MutexLock lock(fd->mutex); + if (fd->msdf_range != p_msdf_pixel_range) { + _font_clear_cache(fd); + fd->msdf_range = p_msdf_pixel_range; + } } -void TextServerAdvanced::font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +int TextServerAdvanced::font_get_msdf_pixel_range(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->msdf_range; +} + +void TextServerAdvanced::font_set_msdf_size(RID p_font_rid, int p_msdf_size) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->bitmap_add_kerning_pair(p_A, p_B, p_kerning); + + MutexLock lock(fd->mutex); + if (fd->msdf_source_size != p_msdf_size) { + _font_clear_cache(fd); + fd->msdf_source_size = p_msdf_size; + } } -float TextServerAdvanced::font_get_height(RID p_font, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); +int TextServerAdvanced::font_get_msdf_size(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->msdf_source_size; +} + +void TextServerAdvanced::font_set_fixed_size(RID p_font_rid, int p_fixed_size) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->fixed_size != p_fixed_size) { + fd->fixed_size = p_fixed_size; + } +} + +int TextServerAdvanced::font_get_fixed_size(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->fixed_size; +} + +void TextServerAdvanced::font_set_force_autohinter(RID p_font_rid, bool p_force_autohinter) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->force_autohinter != p_force_autohinter) { + _font_clear_cache(fd); + fd->force_autohinter = p_force_autohinter; + } +} + +bool TextServerAdvanced::font_is_force_autohinter(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->force_autohinter; +} + +void TextServerAdvanced::font_set_hinting(RID p_font_rid, TextServer::Hinting p_hinting) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->hinting != p_hinting) { + _font_clear_cache(fd); + fd->hinting = p_hinting; + } +} + +TextServer::Hinting TextServerAdvanced::font_get_hinting(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, HINTING_NONE); + + MutexLock lock(fd->mutex); + return fd->hinting; +} + +void TextServerAdvanced::font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->variation_coordinates != p_variation_coordinates) { + _font_clear_cache(fd); + fd->variation_coordinates = p_variation_coordinates; + } +} + +Dictionary TextServerAdvanced::font_get_variation_coordinates(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Dictionary()); + + MutexLock lock(fd->mutex); + return fd->variation_coordinates; +} + +void TextServerAdvanced::font_set_oversampling(RID p_font_rid, real_t p_oversampling) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->oversampling != p_oversampling) { + _font_clear_cache(fd); + fd->oversampling = p_oversampling; + } +} + +real_t TextServerAdvanced::font_get_oversampling(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_height(p_size); + + MutexLock lock(fd->mutex); + return fd->oversampling; } -float TextServerAdvanced::font_get_ascent(RID p_font, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); +Array TextServerAdvanced::font_get_size_cache_list(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Array()); + + MutexLock lock(fd->mutex); + Array ret; + for (const Map<Vector2i, FontDataForSizeAdvanced *>::Element *E = fd->cache.front(); E; E = E->next()) { + ret.push_back(E->key()); + } + return ret; +} + +void TextServerAdvanced::font_clear_size_cache(RID p_font_rid) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + for (const Map<Vector2i, FontDataForSizeAdvanced *>::Element *E = fd->cache.front(); E; E = E->next()) { + memdelete(E->get()); + } + fd->cache.clear(); +} + +void TextServerAdvanced::font_remove_size_cache(RID p_font_rid, const Vector2i &p_size) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->cache.has(p_size)) { + memdelete(fd->cache[p_size]); + fd->cache.erase(p_size); + } +} + +void TextServerAdvanced::font_set_ascent(RID p_font_rid, int p_size, real_t p_ascent) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->ascent = p_ascent; +} + +real_t TextServerAdvanced::font_get_ascent(RID p_font_rid, int p_size) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_ascent(p_size); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.f); + + if (fd->msdf) { + return fd->cache[size]->ascent * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->ascent; + } } -float TextServerAdvanced::font_get_descent(RID p_font, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); +void TextServerAdvanced::font_set_descent(RID p_font_rid, int p_size, real_t p_descent) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->descent = p_descent; +} + +real_t TextServerAdvanced::font_get_descent(RID p_font_rid, int p_size) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_descent(p_size); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.f); + + if (fd->msdf) { + return fd->cache[size]->descent * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->descent; + } } -float TextServerAdvanced::font_get_underline_position(RID p_font, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); +void TextServerAdvanced::font_set_underline_position(RID p_font_rid, int p_size, real_t p_underline_position) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->underline_position = p_underline_position; +} + +real_t TextServerAdvanced::font_get_underline_position(RID p_font_rid, int p_size) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_underline_position(p_size); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.f); + + if (fd->msdf) { + return fd->cache[size]->underline_position * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->underline_position; + } } -float TextServerAdvanced::font_get_underline_thickness(RID p_font, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); +void TextServerAdvanced::font_set_underline_thickness(RID p_font_rid, int p_size, real_t p_underline_thickness) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->underline_thickness = p_underline_thickness; +} + +real_t TextServerAdvanced::font_get_underline_thickness(RID p_font_rid, int p_size) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_underline_thickness(p_size); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.f); + + if (fd->msdf) { + return fd->cache[size]->underline_thickness * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->underline_thickness; + } } -int TextServerAdvanced::font_get_spacing_space(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, 0); - return fd->get_spacing_space(); +void TextServerAdvanced::font_set_scale(RID p_font_rid, int p_size, real_t p_scale) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->scale = p_scale; } -void TextServerAdvanced::font_set_spacing_space(RID p_font, int p_value) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +real_t TextServerAdvanced::font_get_scale(RID p_font_rid, int p_size) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, 0.f); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.f); + + if (fd->msdf) { + return fd->cache[size]->scale * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->scale / fd->cache[size]->oversampling; + } +} + +void TextServerAdvanced::font_set_spacing(RID p_font_rid, int p_size, TextServer::SpacingType p_spacing, int p_value) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_spacing_space(p_value); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + switch (p_spacing) { + case TextServer::SPACING_GLYPH: { + fd->cache[size]->spacing_glyph = p_value; + } break; + case TextServer::SPACING_SPACE: { + fd->cache[size]->spacing_space = p_value; + } break; + default: { + ERR_FAIL_MSG("Invalid spacing type: " + itos(p_spacing)); + } break; + } } -int TextServerAdvanced::font_get_spacing_glyph(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); +int TextServerAdvanced::font_get_spacing(RID p_font_rid, int p_size, TextServer::SpacingType p_spacing) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0); - return fd->get_spacing_glyph(); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + + switch (p_spacing) { + case TextServer::SPACING_GLYPH: { + if (fd->msdf) { + return fd->cache[size]->spacing_glyph * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->spacing_glyph; + } + } break; + case TextServer::SPACING_SPACE: { + if (fd->msdf) { + return fd->cache[size]->spacing_space * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->spacing_space; + } + } break; + default: { + ERR_FAIL_V_MSG(0, "Invalid spacing type: " + itos(p_spacing)); + } break; + } + return 0; } -void TextServerAdvanced::font_set_spacing_glyph(RID p_font, int p_value) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +int TextServerAdvanced::font_get_texture_count(RID p_font_rid, const Vector2i &p_size) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, 0); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + + return fd->cache[size]->textures.size(); +} + +void TextServerAdvanced::font_clear_textures(RID p_font_rid, const Vector2i &p_size) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_spacing_glyph(p_value); + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->textures.clear(); } -void TextServerAdvanced::font_set_antialiased(RID p_font, bool p_antialiased) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +void TextServerAdvanced::font_remove_texture(RID p_font_rid, const Vector2i &p_size, int p_texture_index) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_antialiased(p_antialiased); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size()); + + fd->cache[size]->textures.remove(p_texture_index); } -Dictionary TextServerAdvanced::font_get_feature_list(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, Dictionary()); - return fd->get_feature_list(); +void TextServerAdvanced::font_set_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + ERR_FAIL_COND(p_image.is_null()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + ERR_FAIL_COND(p_texture_index < 0); + if (p_texture_index >= fd->cache[size]->textures.size()) { + fd->cache[size]->textures.resize(p_texture_index + 1); + } + + FontTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + + tex.imgdata = p_image->get_data(); + tex.texture_w = p_image->get_width(); + tex.texture_h = p_image->get_height(); + tex.format = p_image->get_format(); + + Ref<Image> img = memnew(Image(tex.texture_w, tex.texture_h, 0, tex.format, tex.imgdata)); + tex.texture = Ref<ImageTexture>(); + tex.texture.instantiate(); + tex.texture->create_from_image(img); } -bool TextServerAdvanced::font_get_antialiased(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, false); - return fd->get_antialiased(); +Ref<Image> TextServerAdvanced::font_get_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Ref<Image>()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>()); + ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>()); + + const FontTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + Ref<Image> img = memnew(Image(tex.texture_w, tex.texture_h, 0, tex.format, tex.imgdata)); + + return img; } -Dictionary TextServerAdvanced::font_get_variation_list(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, Dictionary()); - return fd->get_variation_list(); +void TextServerAdvanced::font_set_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + if (p_texture_index >= fd->cache[size]->textures.size()) { + fd->cache[size]->textures.resize(p_texture_index + 1); + } + + FontTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + tex.offsets = p_offset; } -void TextServerAdvanced::font_set_variation(RID p_font, const String &p_name, double p_value) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +PackedInt32Array TextServerAdvanced::font_get_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, PackedInt32Array()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array()); + ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array()); + + const FontTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + return tex.offsets; +} + +Array TextServerAdvanced::font_get_glyph_list(RID p_font_rid, const Vector2i &p_size) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Array()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Array()); + + Array ret; + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + const int32_t *E = nullptr; + while ((E = gl.next(E))) { + ret.push_back(*E); + } + return ret; +} + +void TextServerAdvanced::font_clear_glyphs(RID p_font_rid, const Vector2i &p_size) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_variation(p_name, p_value); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + fd->cache[size]->glyph_map.clear(); } -double TextServerAdvanced::font_get_variation(RID p_font, const String &p_name) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, 0); - return fd->get_variation(p_name); +void TextServerAdvanced::font_remove_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + fd->cache[size]->glyph_map.erase(p_glyph); } -void TextServerAdvanced::font_set_distance_field_hint(RID p_font, bool p_distance_field) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +Vector2 TextServerAdvanced::font_get_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Vector2()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + if (!_ensure_glyph(fd, size, p_glyph)) { + return Vector2(); // Invalid or non graphicl glyph, do not display errors. + } + + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + if (fd->msdf) { + return gl[p_glyph].advance * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return gl[p_glyph].advance; + } +} + +void TextServerAdvanced::font_set_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph, const Vector2 &p_advance) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_distance_field_hint(p_distance_field); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + gl[p_glyph].advance = p_advance; + gl[p_glyph].found = true; } -bool TextServerAdvanced::font_get_distance_field_hint(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, false); - return fd->get_distance_field_hint(); +Vector2 TextServerAdvanced::font_get_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Vector2()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + if (!_ensure_glyph(fd, size, p_glyph)) { + return Vector2(); // Invalid or non graphicl glyph, do not display errors. + } + + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + if (fd->msdf) { + return gl[p_glyph].rect.position * (real_t)p_size.x / (real_t)fd->msdf_source_size; + } else { + return gl[p_glyph].rect.position; + } } -void TextServerAdvanced::font_set_hinting(RID p_font, TextServer::Hinting p_hinting) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +void TextServerAdvanced::font_set_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_hinting(p_hinting); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + gl[p_glyph].rect.position = p_offset; + gl[p_glyph].found = true; } -TextServer::Hinting TextServerAdvanced::font_get_hinting(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, TextServer::HINTING_NONE); - return fd->get_hinting(); +Vector2 TextServerAdvanced::font_get_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Vector2()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + if (!_ensure_glyph(fd, size, p_glyph)) { + return Vector2(); // Invalid or non graphicl glyph, do not display errors. + } + + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + if (fd->msdf) { + return gl[p_glyph].rect.size * (real_t)p_size.x / (real_t)fd->msdf_source_size; + } else { + return gl[p_glyph].rect.size; + } } -void TextServerAdvanced::font_set_force_autohinter(RID p_font, bool p_enabeld) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +void TextServerAdvanced::font_set_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_force_autohinter(p_enabeld); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + gl[p_glyph].rect.size = p_gl_size; + gl[p_glyph].found = true; } -bool TextServerAdvanced::font_get_force_autohinter(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); +Rect2 TextServerAdvanced::font_get_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Rect2()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2()); + if (!_ensure_glyph(fd, size, p_glyph)) { + return Rect2(); // Invalid or non graphicl glyph, do not display errors. + } + + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + return gl[p_glyph].uv_rect; +} + +void TextServerAdvanced::font_set_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + gl[p_glyph].uv_rect = p_uv_rect; + gl[p_glyph].found = true; +} + +int TextServerAdvanced::font_get_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, -1); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1); + if (!_ensure_glyph(fd, size, p_glyph)) { + return -1; // Invalid or non graphicl glyph, do not display errors. + } + + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + return gl[p_glyph].texture_idx; +} + +void TextServerAdvanced::font_set_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + gl[p_glyph].texture_idx = p_texture_idx; + gl[p_glyph].found = true; +} + +bool TextServerAdvanced::font_get_glyph_contours(RID p_font_rid, int p_size, int32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, false); - return fd->get_force_autohinter(); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), false); + +#ifdef MODULE_FREETYPE_ENABLED + int error = FT_Load_Glyph(fd->cache[size]->face, p_index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); + ERR_FAIL_COND_V(error, false); + + r_points.clear(); + r_contours.clear(); + + real_t h = fd->cache[size]->ascent; + real_t scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale; + if (fd->msdf) { + scale = scale * (real_t)p_size / (real_t)fd->msdf_source_size; + } + for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) { + r_points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, h - fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i]))); + } + for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) { + r_contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]); + } + r_orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); +#else + return false; +#endif + return true; } -bool TextServerAdvanced::font_has_char(RID p_font, char32_t p_char) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); +Array TextServerAdvanced::font_get_kerning_list(RID p_font_rid, int p_size) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Array()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Array()); + + Array ret; + for (const Map<Vector2i, Vector2>::Element *E = fd->cache[size]->kerning_map.front(); E; E = E->next()) { + ret.push_back(E->key()); + } + return ret; +} + +void TextServerAdvanced::font_clear_kerning_map(RID p_font_rid, int p_size) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->kerning_map.clear(); +} + +void TextServerAdvanced::font_remove_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->kerning_map.erase(p_glyph_pair); +} + +void TextServerAdvanced::font_set_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning; +} + +Vector2 TextServerAdvanced::font_get_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Vector2()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + + const Map<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map; + + if (kern.has(p_glyph_pair)) { + if (fd->msdf) { + return kern[p_glyph_pair] * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return kern[p_glyph_pair]; + } + } else { +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face) { + FT_Vector delta; + FT_Get_Kerning(fd->cache[size]->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta); + if (fd->msdf) { + return Vector2(delta.x, delta.y) * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return Vector2(delta.x, delta.y); + } + } +#endif + } + return Vector2(); +} + +int32_t TextServerAdvanced::font_get_glyph_index(RID p_font_rid, int p_size, char32_t p_char, char32_t p_variation_selector) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, 0); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face) { + if (p_variation_selector) { + return FT_Face_GetCharVariantIndex(fd->cache[size]->face, p_char, p_variation_selector); + } else { + return FT_Get_Char_Index(fd->cache[size]->face, p_char); + } + } else { + return 0; + } +#else + return (int32_t)p_char; +#endif +} + +bool TextServerAdvanced::font_has_char(RID p_font_rid, char32_t p_char) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, false); - return fd->has_char(p_char); + + MutexLock lock(fd->mutex); + if (fd->cache.is_empty()) { + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false); + } + FontDataForSizeAdvanced *at_size = fd->cache.front()->get(); + +#ifdef MODULE_FREETYPE_ENABLED + if (at_size && at_size->face) { + return FT_Get_Char_Index(at_size->face, p_char) != 0; + } +#endif + return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false; } -String TextServerAdvanced::font_get_supported_chars(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); +String TextServerAdvanced::font_get_supported_chars(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, String()); - return fd->get_supported_chars(); + + MutexLock lock(fd->mutex); + if (fd->cache.is_empty()) { + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String()); + } + FontDataForSizeAdvanced *at_size = fd->cache.front()->get(); + + String chars; +#ifdef MODULE_FREETYPE_ENABLED + if (at_size && at_size->face) { + FT_UInt gindex; + FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex); + while (gindex != 0) { + if (charcode != 0) { + chars += char32_t(charcode); + } + charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex); + } + return chars; + } +#endif + if (at_size) { + const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map; + const int32_t *E = nullptr; + while ((E = gl.next(E))) { + chars += char32_t(*E); + } + } + return chars; } -bool TextServerAdvanced::font_has_outline(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, false); - return fd->has_outline(); +void TextServerAdvanced::font_render_range(RID p_font_rid, const Vector2i &p_size, char32_t p_start, char32_t p_end) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + for (char32_t i = p_start; i <= p_end; i++) { +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face) { + _ensure_glyph(fd, size, FT_Get_Char_Index(fd->cache[size]->face, i)); + continue; + } +#endif + _ensure_glyph(fd, size, (int32_t)i); + } } -float TextServerAdvanced::font_get_base_size(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_base_size(); +void TextServerAdvanced::font_render_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_index) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + ERR_FAIL_COND(!_ensure_glyph(fd, size, p_index)); } -bool TextServerAdvanced::font_is_language_supported(RID p_font, const String &p_language) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, false); - if (fd->lang_support_overrides.has(p_language)) { - return fd->lang_support_overrides[p_language]; - } else { - Vector<String> tags = p_language.replace("-", "_").split("_"); - if (tags.size() > 0) { - if (fd->lang_support_overrides.has(tags[0])) { - return fd->lang_support_overrides[tags[0]]; +void TextServerAdvanced::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + if (!_ensure_glyph(fd, size, p_index)) { + return; // // Invalid or non graphicl glyph, do not display errors, nothing to draw. + } + + const FontGlyph &gl = fd->cache[size]->glyph_map[p_index]; + if (gl.found) { + ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + + if (gl.texture_idx != -1) { + Color modulate = p_color; +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face && FT_HAS_COLOR(fd->cache[size]->face)) { + modulate.r = modulate.g = modulate.b = 1.0; + } +#endif + if (RenderingServer::get_singleton() != nullptr) { + RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + if (fd->msdf) { + Point2 cpos = p_pos; + cpos += gl.rect.position * (real_t)p_size / (real_t)fd->msdf_source_size; + Size2 csize = gl.rect.size * (real_t)p_size / (real_t)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range); + } else { + Point2i cpos = p_pos; + cpos += gl.rect.position; + Size2i csize = gl.rect.size; + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + } } } - return fd->is_lang_supported(p_language); } } -void TextServerAdvanced::font_set_language_support_override(RID p_font, const String &p_language, bool p_supported) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +void TextServerAdvanced::font_draw_glyph_outline(RID p_font_rid, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->lang_support_overrides[p_language] = p_supported; + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + if (!_ensure_glyph(fd, size, p_index)) { + return; // // Invalid or non graphicl glyph, do not display errors, nothing to draw. + } + + const FontGlyph &gl = fd->cache[size]->glyph_map[p_index]; + if (gl.found) { + ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + + if (gl.texture_idx != -1) { + Color modulate = p_color; +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face && FT_HAS_COLOR(fd->cache[size]->face)) { + modulate.r = modulate.g = modulate.b = 1.0; + } +#endif + if (RenderingServer::get_singleton() != nullptr) { + RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + if (fd->msdf) { + Point2 cpos = p_pos; + cpos += gl.rect.position * (real_t)p_size / (real_t)fd->msdf_source_size; + Size2 csize = gl.rect.size * (real_t)p_size / (real_t)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size * 2, fd->msdf_range); + } else { + Point2i cpos = p_pos; + cpos += gl.rect.position; + Size2i csize = gl.rect.size; + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + } + } + } + } } -bool TextServerAdvanced::font_get_language_support_override(RID p_font, const String &p_language) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +bool TextServerAdvanced::font_is_language_supported(RID p_font_rid, const String &p_language) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, false); - return fd->lang_support_overrides[p_language]; + + MutexLock lock(fd->mutex); + if (fd->language_support_overrides.has(p_language)) { + return fd->language_support_overrides[p_language]; + } else { + return true; + } } -void TextServerAdvanced::font_remove_language_support_override(RID p_font, const String &p_language) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +void TextServerAdvanced::font_set_language_support_override(RID p_font_rid, const String &p_language, bool p_supported) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->lang_support_overrides.erase(p_language); + + MutexLock lock(fd->mutex); + fd->language_support_overrides[p_language] = p_supported; } -Vector<String> TextServerAdvanced::font_get_language_support_overrides(RID p_font) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +bool TextServerAdvanced::font_get_language_support_override(RID p_font_rid, const String &p_language) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->language_support_overrides[p_language]; +} + +void TextServerAdvanced::font_remove_language_support_override(RID p_font_rid, const String &p_language) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + fd->language_support_overrides.erase(p_language); +} + +Vector<String> TextServerAdvanced::font_get_language_support_overrides(RID p_font_rid) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, Vector<String>()); - Vector<String> ret; - for (Map<String, bool>::Element *E = fd->lang_support_overrides.front(); E; E = E->next()) { - ret.push_back(E->key()); + + MutexLock lock(fd->mutex); + Vector<String> out; + for (const Map<String, bool>::Element *E = fd->language_support_overrides.front(); E; E = E->next()) { + out.push_back(E->key()); } - return ret; + return out; } -bool TextServerAdvanced::font_is_script_supported(RID p_font, const String &p_script) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); +bool TextServerAdvanced::font_is_script_supported(RID p_font_rid, const String &p_script) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); if (fd->script_support_overrides.has(p_script)) { return fd->script_support_overrides[p_script]; } else { - hb_script_t scr = hb_script_from_string(p_script.ascii().get_data(), -1); - return fd->is_script_supported(scr); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), false); + return fd->supported_scripts.has(TS->name_to_tag(p_script)); } } -void TextServerAdvanced::font_set_script_support_override(RID p_font, const String &p_script, bool p_supported) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +void TextServerAdvanced::font_set_script_support_override(RID p_font_rid, const String &p_script, bool p_supported) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); fd->script_support_overrides[p_script] = p_supported; } -bool TextServerAdvanced::font_get_script_support_override(RID p_font, const String &p_script) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +bool TextServerAdvanced::font_get_script_support_override(RID p_font_rid, const String &p_script) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); return fd->script_support_overrides[p_script]; } -void TextServerAdvanced::font_remove_script_support_override(RID p_font, const String &p_script) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +void TextServerAdvanced::font_remove_script_support_override(RID p_font_rid, const String &p_script) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); fd->script_support_overrides.erase(p_script); } -Vector<String> TextServerAdvanced::font_get_script_support_overrides(RID p_font) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +Vector<String> TextServerAdvanced::font_get_script_support_overrides(RID p_font_rid) { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, Vector<String>()); - Vector<String> ret; - for (Map<String, bool>::Element *E = fd->script_support_overrides.front(); E; E = E->next()) { - ret.push_back(E->key()); - } - return ret; -} -uint32_t TextServerAdvanced::font_get_glyph_index(RID p_font, char32_t p_char, char32_t p_variation_selector) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, 0); - return fd->get_glyph_index(p_char, p_variation_selector); + MutexLock lock(fd->mutex); + Vector<String> out; + for (const Map<String, bool>::Element *E = fd->script_support_overrides.front(); E; E = E->next()) { + out.push_back(E->key()); + } + return out; } -Vector2 TextServerAdvanced::font_get_glyph_advance(RID p_font, uint32_t p_index, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, Vector2()); - return fd->get_advance(p_index, p_size); -} +Dictionary TextServerAdvanced::font_supported_feature_list(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Dictionary()); -Vector2 TextServerAdvanced::font_get_glyph_kerning(RID p_font, uint32_t p_index_a, uint32_t p_index_b, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, Vector2()); - return fd->get_kerning(p_index_a, p_index_b, p_size); + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + return fd->supported_features; } -Vector2 TextServerAdvanced::font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, Vector2()); - return fd->draw_glyph(p_canvas, p_size, p_pos, p_index, p_color); -} - -Vector2 TextServerAdvanced::font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, Vector2()); - return fd->draw_glyph_outline(p_canvas, p_size, p_outline_size, p_pos, p_index, p_color); -} +Dictionary TextServerAdvanced::font_supported_variation_list(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Dictionary()); -bool TextServerAdvanced::font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, false); - return fd->get_glyph_contours(p_size, p_index, r_points, r_contours, r_orientation); + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + return fd->supported_varaitions; } -float TextServerAdvanced::font_get_oversampling() const { +real_t TextServerAdvanced::font_get_global_oversampling() const { return oversampling; } -void TextServerAdvanced::font_set_oversampling(float p_oversampling) { +void TextServerAdvanced::font_set_global_oversampling(real_t p_oversampling) { _THREAD_SAFE_METHOD_ if (oversampling != p_oversampling) { oversampling = p_oversampling; List<RID> fonts; font_owner.get_owned_list(&fonts); + bool font_cleared = false; for (const RID &E : fonts) { - font_owner.getornull(E)->clear_cache(); + if (!font_is_multichannel_signed_distance_field(E) && font_get_oversampling(E) <= 0) { + font_clear_size_cache(E); + font_cleared = true; + } } - List<RID> text_bufs; - shaped_owner.get_owned_list(&text_bufs); - for (const RID &E : text_bufs) { - invalidate(shaped_owner.getornull(E)); + if (font_cleared) { + List<RID> text_bufs; + shaped_owner.get_owned_list(&text_bufs); + for (const RID &E : text_bufs) { + invalidate(shaped_owner.getornull(E)); + } } } } -Vector<String> TextServerAdvanced::get_system_fonts() const { - return Vector<String>(); -} - /*************************************************************************/ /* Shaped text buffer interface */ /*************************************************************************/ @@ -1029,10 +2889,10 @@ RID TextServerAdvanced::create_shaped_text(TextServer::Direction p_direction, Te } void TextServerAdvanced::shaped_text_clear(RID p_shaped) { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND(!sd); + MutexLock lock(sd->mutex); sd->parent = RID(); sd->start = 0; sd->end = 0; @@ -1044,10 +2904,10 @@ void TextServerAdvanced::shaped_text_clear(RID p_shaped) { } void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Direction p_direction) { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND(!sd); + MutexLock lock(sd->mutex); if (sd->direction != p_direction) { if (sd->parent != RID()) { full_copy(sd); @@ -1058,16 +2918,18 @@ void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Dir } TextServer::Direction TextServerAdvanced::shaped_text_get_direction(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, TextServer::DIRECTION_LTR); + + MutexLock lock(sd->mutex); return sd->direction; } void TextServerAdvanced::shaped_text_set_bidi_override(RID p_shaped, const Vector<Vector2i> &p_override) { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND(!sd); + + MutexLock lock(sd->mutex); if (sd->parent != RID()) { full_copy(sd); } @@ -1076,9 +2938,10 @@ void TextServerAdvanced::shaped_text_set_bidi_override(RID p_shaped, const Vecto } void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND(!sd); + + MutexLock lock(sd->mutex); if (sd->orientation != p_orientation) { if (sd->parent != RID()) { full_copy(sd); @@ -1089,9 +2952,10 @@ void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::O } void TextServerAdvanced::shaped_text_set_preserve_invalid(RID p_shaped, bool p_enabled) { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND(!sd); + + MutexLock lock(sd->mutex); ERR_FAIL_COND(sd->parent != RID()); if (sd->preserve_invalid != p_enabled) { sd->preserve_invalid = p_enabled; @@ -1100,16 +2964,18 @@ void TextServerAdvanced::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e } bool TextServerAdvanced::shaped_text_get_preserve_invalid(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); return sd->preserve_invalid; } void TextServerAdvanced::shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND(!sd); + + MutexLock lock(sd->mutex); if (sd->preserve_control != p_enabled) { if (sd->parent != RID()) { full_copy(sd); @@ -1120,25 +2986,31 @@ void TextServerAdvanced::shaped_text_set_preserve_control(RID p_shaped, bool p_e } bool TextServerAdvanced::shaped_text_get_preserve_control(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); return sd->preserve_control; } TextServer::Orientation TextServerAdvanced::shaped_text_get_orientation(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL); + + MutexLock lock(sd->mutex); return sd->orientation; } bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); ERR_FAIL_COND_V(p_size <= 0, false); + MutexLock lock(sd->mutex); + for (int i = 0; i < p_fonts.size(); i++) { + ERR_FAIL_COND_V(!font_owner.getornull(p_fonts[i]), false); + } + if (p_text.is_empty()) { return true; } @@ -1194,9 +3066,10 @@ bool TextServerAdvanced::shaped_text_add_object(RID p_shaped, Variant p_key, con } bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align) { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), false); sd->objects[p_key].rect.size = p_size; sd->objects[p_key].inline_align = p_inline_align; @@ -1231,14 +3104,13 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, sd->glyphs.write[i].advance = sd->objects[key].rect.size.y; } } else { - const FontDataAdvanced *fd = font_owner.getornull(gl.font_rid); - if (fd != nullptr) { + if (gl.font_rid.is_valid()) { if (sd->orientation == ORIENTATION_HORIZONTAL) { - sd->ascent = MAX(sd->ascent, MAX(fd->get_ascent(gl.font_size), -gl.y_off)); - sd->descent = MAX(sd->descent, MAX(fd->get_descent(gl.font_size), gl.y_off)); + sd->ascent = MAX(sd->ascent, MAX(font_get_ascent(gl.font_rid, gl.font_size), -gl.y_off)); + sd->descent = MAX(sd->descent, MAX(font_get_descent(gl.font_rid, gl.font_size), gl.y_off)); } else { - sd->ascent = MAX(sd->ascent, Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5)); - sd->descent = MAX(sd->descent, Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5)); + sd->ascent = MAX(sd->ascent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5)); + sd->descent = MAX(sd->descent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5)); } sd->upos = MAX(sd->upos, font_get_underline_position(gl.font_rid, gl.font_size)); sd->uthk = MAX(sd->uthk, font_get_underline_thickness(gl.font_rid, gl.font_size)); @@ -1257,8 +3129,8 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, } // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; + real_t full_ascent = sd->ascent; + real_t full_descent = sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) { if ((E->get().pos >= sd->start) && (E->get().pos < sd->end)) { if (sd->orientation == ORIENTATION_HORIZONTAL) { @@ -1327,9 +3199,10 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, } RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_length) const { - _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, RID()); + + MutexLock lock(sd->mutex); if (sd->parent != RID()) { return shaped_text_substr(sd->parent, p_start, p_length); } @@ -1363,9 +3236,6 @@ RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_leng int sd_size = sd->glyphs.size(); const Glyph *sd_glyphs = sd->glyphs.ptr(); - const FontDataAdvanced *fd = nullptr; - RID prev_rid = RID(); - for (int ov = 0; ov < sd->bidi_override.size(); ov++) { UErrorCode err = U_ZERO_ERROR; @@ -1423,17 +3293,13 @@ RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_leng new_sd->width += new_sd->objects[key].rect.size.y; } } else { - if (prev_rid != gl.font_rid) { - fd = font_owner.getornull(gl.font_rid); - prev_rid = gl.font_rid; - } - if (fd != nullptr) { + if (gl.font_rid.is_valid()) { if (new_sd->orientation == ORIENTATION_HORIZONTAL) { - new_sd->ascent = MAX(new_sd->ascent, MAX(fd->get_ascent(gl.font_size), -gl.y_off)); - new_sd->descent = MAX(new_sd->descent, MAX(fd->get_descent(gl.font_size), gl.y_off)); + new_sd->ascent = MAX(new_sd->ascent, MAX(font_get_ascent(gl.font_rid, gl.font_size), -gl.y_off)); + new_sd->descent = MAX(new_sd->descent, MAX(font_get_descent(gl.font_rid, gl.font_size), gl.y_off)); } else { - new_sd->ascent = MAX(new_sd->ascent, Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5)); - new_sd->descent = MAX(new_sd->descent, Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5)); + new_sd->ascent = MAX(new_sd->ascent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5)); + new_sd->descent = MAX(new_sd->descent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5)); } } else if (new_sd->preserve_invalid || (new_sd->preserve_control && is_control(gl.index))) { // Glyph not found, replace with hex code box. @@ -1454,8 +3320,8 @@ RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_leng } // Align embedded objects to baseline. - float full_ascent = new_sd->ascent; - float full_descent = new_sd->descent; + real_t full_ascent = new_sd->ascent; + real_t full_descent = new_sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = new_sd->objects.front(); E; E = E->next()) { if ((E->get().pos >= new_sd->start) && (E->get().pos < new_sd->end)) { if (sd->orientation == ORIENTATION_HORIZONTAL) { @@ -1526,16 +3392,18 @@ RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_leng } RID TextServerAdvanced::shaped_text_get_parent(RID p_shaped) const { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, RID()); + + MutexLock lock(sd->mutex); return sd->parent; } -float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, uint8_t /*JustificationFlag*/ p_jst_flags) { - _THREAD_SAFE_METHOD_ +real_t TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, real_t p_width, uint8_t /*JustificationFlag*/ p_jst_flags) { ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } @@ -1572,7 +3440,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, } } - float justification_width; + real_t justification_width; if ((p_jst_flags & JUSTIFICATION_CONSTRAIN_ELLIPSIS) == JUSTIFICATION_CONSTRAIN_ELLIPSIS) { if (sd->overrun_trim_data.trim_pos >= 0) { start_pos = sd->overrun_trim_data.trim_pos; @@ -1612,7 +3480,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, } if ((elongation_count > 0) && ((p_jst_flags & JUSTIFICATION_KASHIDA) == JUSTIFICATION_KASHIDA)) { - float delta_width_per_kashida = (p_width - justification_width) / elongation_count; + real_t delta_width_per_kashida = (p_width - justification_width) / elongation_count; for (int i = start_pos; i <= end_pos; i++) { Glyph &gl = sd->glyphs.write[i]; if (gl.count > 0) { @@ -1627,19 +3495,19 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, } } } - float adv_remain = 0; + real_t adv_remain = 0; if ((space_count > 0) && ((p_jst_flags & JUSTIFICATION_WORD_BOUND) == JUSTIFICATION_WORD_BOUND)) { - float delta_width_per_space = (p_width - justification_width) / space_count; + real_t delta_width_per_space = (p_width - justification_width) / space_count; for (int i = start_pos; i <= end_pos; i++) { Glyph &gl = sd->glyphs.write[i]; if (gl.count > 0) { if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) { - float old_adv = gl.advance; - float new_advance; + real_t old_adv = gl.advance; + real_t new_advance; if ((gl.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) { new_advance = MAX(gl.advance + delta_width_per_space, 0.f); } else { - new_advance = MAX(gl.advance + delta_width_per_space, 0.05 * gl.font_size); + new_advance = MAX(gl.advance + delta_width_per_space, 0.1 * gl.font_size); } gl.advance = new_advance; adv_remain += (new_advance - gl.advance); @@ -1667,10 +3535,11 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, return sd->width; } -float TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const Vector<float> &p_tab_stops) { - _THREAD_SAFE_METHOD_ +real_t TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const Vector<real_t> &p_tab_stops) { ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } @@ -1679,7 +3548,7 @@ float TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const Vector<float } int tab_index = 0; - float off = 0.f; + real_t off = 0.f; int start, end, delta; if (sd->para_direction == DIRECTION_LTR) { @@ -1696,7 +3565,7 @@ float TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const Vector<float for (int i = start; i != end; i += delta) { if ((gl[i].flags & GRAPHEME_IS_TAB) == GRAPHEME_IS_TAB) { - float tab_off = 0.f; + real_t tab_off = 0.f; while (tab_off <= off) { tab_off += p_tab_stops[tab_index]; tab_index++; @@ -1704,7 +3573,7 @@ float TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const Vector<float tab_index = 0; } } - float old_adv = gl[i].advance; + real_t old_adv = gl[i].advance; gl[i].advance = tab_off - off; sd->width += gl[i].advance - old_adv; off = 0; @@ -1716,10 +3585,11 @@ float TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const Vector<float return 0.f; } -void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_trim_flags) { - _THREAD_SAFE_METHOD_ +void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, real_t p_width, uint8_t p_trim_flags) { ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped_line); ERR_FAIL_COND_MSG(!sd, "ShapedTextDataAdvanced invalid."); + + MutexLock lock(sd->mutex); if (!sd->valid) { shaped_text_shape(p_shaped_line); } @@ -1746,18 +3616,18 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl int sd_size = sd->glyphs.size(); RID last_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; int last_gl_font_size = sd_glyphs[sd_size - 1].font_size; - uint32_t dot_gl_idx = font_get_glyph_index(last_gl_font_rid, '.'); - Vector2 dot_adv = font_get_glyph_advance(last_gl_font_rid, dot_gl_idx, last_gl_font_size); - uint32_t whitespace_gl_idx = font_get_glyph_index(last_gl_font_rid, ' '); - Vector2 whitespace_adv = font_get_glyph_advance(last_gl_font_rid, whitespace_gl_idx, last_gl_font_size); + int32_t dot_gl_idx = font_get_glyph_index(last_gl_font_rid, last_gl_font_size, '.'); + Vector2 dot_adv = font_get_glyph_advance(last_gl_font_rid, last_gl_font_size, dot_gl_idx); + int32_t whitespace_gl_idx = font_get_glyph_index(last_gl_font_rid, last_gl_font_size, ' '); + Vector2 whitespace_adv = font_get_glyph_advance(last_gl_font_rid, last_gl_font_size, whitespace_gl_idx); int ellipsis_width = 0; if (add_ellipsis) { - ellipsis_width = 3 * dot_adv.x + font_get_spacing_glyph(last_gl_font_rid) + (cut_per_word ? whitespace_adv.x : 0); + ellipsis_width = 3 * dot_adv.x + font_get_spacing(last_gl_font_rid, last_gl_font_size, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0); } int ell_min_characters = 6; - float width = sd->width; + real_t width = sd->width; bool is_rtl = sd->direction == DIRECTION_RTL || (sd->direction == DIRECTION_AUTO && sd->para_direction == DIRECTION_RTL); @@ -1842,16 +3712,18 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl } TextServer::TrimData TextServerAdvanced::shaped_text_get_trim_data(RID p_shaped) const { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V_MSG(!sd, TrimData(), "ShapedTextDataAdvanced invalid."); + + MutexLock lock(sd->mutex); return sd->overrun_trim_data; } bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); if (!sd->valid) { shaped_text_shape(p_shaped); } @@ -2035,9 +3907,10 @@ _FORCE_INLINE_ int _generate_kashida_justification_opportunies(const String &p_d } bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); if (!sd->valid) { shaped_text_shape(p_shaped); } @@ -2145,8 +4018,8 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { } TextServer::Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, RID p_font, int p_font_size) { - FontDataAdvanced *fd = font_owner.getornull(p_font); - hb_font_t *hb_font = fd->get_hb_handle(p_font_size); + hb_font_t *hb_font = _font_get_hb_handle(p_font, p_font_size); + ERR_FAIL_COND_V(hb_font == nullptr, TextServer::Glyph()); hb_buffer_clear_contents(p_sd->hb_buffer); hb_buffer_set_direction(p_sd->hb_buffer, p_direction); @@ -2171,16 +4044,17 @@ TextServer::Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced gl.font_size = p_font_size; if (glyph_count > 0) { + real_t scale = font_get_scale(p_font, p_font_size); if (p_sd->orientation == ORIENTATION_HORIZONTAL) { - gl.advance = Math::round(glyph_pos[0].x_advance / (64.0 / fd->get_font_scale(p_font_size))); + gl.advance = Math::round(glyph_pos[0].x_advance / (64.0 / scale)); } else { - gl.advance = -Math::round(glyph_pos[0].y_advance / (64.0 / fd->get_font_scale(p_font_size))); + gl.advance = -Math::round(glyph_pos[0].y_advance / (64.0 / scale)); } gl.count = 1; gl.index = glyph_info[0].codepoint; - gl.x_off = Math::round(glyph_pos[0].x_offset / (64.0 / fd->get_font_scale(p_font_size))); - gl.y_off = -Math::round(glyph_pos[0].y_offset / (64.0 / fd->get_font_scale(p_font_size))); + gl.x_off = Math::round(glyph_pos[0].x_offset / (64.0 / scale)); + gl.y_off = -Math::round(glyph_pos[0].y_offset / (64.0 / scale)); if ((glyph_info[0].codepoint != 0) || !u_isgraph(p_char)) { gl.flags |= GRAPHEME_IS_VALID; @@ -2190,13 +4064,8 @@ TextServer::Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced } void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_start, int32_t p_end, hb_script_t p_script, hb_direction_t p_direction, Vector<RID> p_fonts, int p_span, int p_fb_index) { - FontDataAdvanced *fd = nullptr; - if (p_fb_index < p_fonts.size()) { - fd = font_owner.getornull(p_fonts[p_fb_index]); - } - int fs = p_sd->spans[p_span].font_size; - if (fd == nullptr) { + if (p_fb_index >= p_fonts.size()) { // Add fallback glyphs. for (int i = p_start; i < p_end; i++) { if (p_sd->preserve_invalid || (p_sd->preserve_control && is_control(p_sd->text[i]))) { @@ -2227,7 +4096,8 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star return; } - hb_font_t *hb_font = fd->get_hb_handle(fs); + RID f = p_fonts[p_fb_index]; + hb_font_t *hb_font = _font_get_hb_handle(f, fs); ERR_FAIL_COND(hb_font == nullptr); hb_buffer_clear_contents(p_sd->hb_buffer); @@ -2307,18 +4177,19 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star gl.index = glyph_info[i].codepoint; if (gl.index != 0) { + real_t scale = font_get_scale(f, fs); if (p_sd->orientation == ORIENTATION_HORIZONTAL) { - gl.advance = Math::round(glyph_pos[i].x_advance / (64.0 / fd->get_font_scale(fs))); + gl.advance = Math::round(glyph_pos[i].x_advance / (64.0 / scale)); } else { - gl.advance = -Math::round(glyph_pos[i].y_advance / (64.0 / fd->get_font_scale(fs))); + gl.advance = -Math::round(glyph_pos[i].y_advance / (64.0 / scale)); } - gl.x_off = Math::round(glyph_pos[i].x_offset / (64.0 / fd->get_font_scale(fs))); - gl.y_off = -Math::round(glyph_pos[i].y_offset / (64.0 / fd->get_font_scale(fs))); + gl.x_off = Math::round(glyph_pos[i].x_offset / (64.0 / scale)); + gl.y_off = -Math::round(glyph_pos[i].y_offset / (64.0 / scale)); } - if (fd->get_spacing_space() && is_whitespace(p_sd->text[glyph_info[i].cluster])) { - gl.advance += fd->get_spacing_space(); + if (font_get_spacing(f, fs, SPACING_SPACE) && is_whitespace(p_sd->text[glyph_info[i].cluster])) { + gl.advance += font_get_spacing(f, fs, SPACING_SPACE); } else { - gl.advance += fd->get_spacing_glyph(); + gl.advance += font_get_spacing(f, fs, SPACING_GLYPH); } if (p_sd->preserve_control) { @@ -2356,8 +4227,8 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star p_sd->ascent = MAX(p_sd->ascent, -w[i + j].y_off); p_sd->descent = MAX(p_sd->descent, w[i + j].y_off); } else { - p_sd->ascent = MAX(p_sd->ascent, Math::round(fd->get_advance(w[i + j].index, fs).x * 0.5)); - p_sd->descent = MAX(p_sd->descent, Math::round(fd->get_advance(w[i + j].index, fs).x * 0.5)); + p_sd->ascent = MAX(p_sd->ascent, Math::round(font_get_glyph_advance(f, fs, w[i + j].index).x * 0.5)); + p_sd->descent = MAX(p_sd->descent, Math::round(font_get_glyph_advance(f, fs, w[i + j].index).x * 0.5)); } p_sd->width += w[i + j].advance; p_sd->glyphs.push_back(w[i + j]); @@ -2376,18 +4247,18 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star if (failed_subrun_start != p_end + 1) { _shape_run(p_sd, failed_subrun_start, failed_subrun_end, p_script, p_direction, p_fonts, p_span, p_fb_index + 1); } - p_sd->ascent = MAX(p_sd->ascent, fd->get_ascent(fs)); - p_sd->descent = MAX(p_sd->descent, fd->get_descent(fs)); - p_sd->upos = MAX(p_sd->upos, fd->get_underline_position(fs)); - p_sd->uthk = MAX(p_sd->uthk, fd->get_underline_thickness(fs)); + p_sd->ascent = MAX(p_sd->ascent, font_get_ascent(f, fs)); + p_sd->descent = MAX(p_sd->descent, font_get_descent(f, fs)); + p_sd->upos = MAX(p_sd->upos, font_get_underline_position(f, fs)); + p_sd->uthk = MAX(p_sd->uthk, font_get_underline_thickness(f, fs)); } } bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + MutexLock lock(sd->mutex); if (sd->valid) { return true; } @@ -2546,8 +4417,8 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { } // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; + real_t full_ascent = sd->ascent; + real_t full_descent = sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) { if (sd->orientation == ORIENTATION_HORIZONTAL) { switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { @@ -2614,16 +4485,18 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { } bool TextServerAdvanced::shaped_text_is_ready(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); return sd->valid; } Vector<TextServer::Glyph> TextServerAdvanced::shaped_text_get_glyphs(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, Vector<TextServer::Glyph>()); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } @@ -2631,16 +4504,18 @@ Vector<TextServer::Glyph> TextServerAdvanced::shaped_text_get_glyphs(RID p_shape } Vector2i TextServerAdvanced::shaped_text_get_range(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, Vector2i()); + + MutexLock lock(sd->mutex); return Vector2(sd->start, sd->end); } Vector<TextServer::Glyph> TextServerAdvanced::shaped_text_sort_logical(RID p_shaped) { - _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, Vector<TextServer::Glyph>()); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } @@ -2655,10 +4530,11 @@ Vector<TextServer::Glyph> TextServerAdvanced::shaped_text_sort_logical(RID p_sha } Array TextServerAdvanced::shaped_text_get_objects(RID p_shaped) const { - _THREAD_SAFE_METHOD_ Array ret; const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, ret); + + MutexLock lock(sd->mutex); for (const Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) { ret.push_back(E->key()); } @@ -2667,9 +4543,10 @@ Array TextServerAdvanced::shaped_text_get_objects(RID p_shaped) const { } Rect2 TextServerAdvanced::shaped_text_get_object_rect(RID p_shaped, Variant p_key) const { - _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, Rect2()); + + MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2()); if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); @@ -2678,9 +4555,10 @@ Rect2 TextServerAdvanced::shaped_text_get_object_rect(RID p_shaped, Variant p_ke } Size2 TextServerAdvanced::shaped_text_get_size(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, Size2()); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } @@ -2691,40 +4569,44 @@ Size2 TextServerAdvanced::shaped_text_get_size(RID p_shaped) const { } } -float TextServerAdvanced::shaped_text_get_ascent(RID p_shaped) const { - _THREAD_SAFE_METHOD_ +real_t TextServerAdvanced::shaped_text_get_ascent(RID p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } return sd->ascent; } -float TextServerAdvanced::shaped_text_get_descent(RID p_shaped) const { - _THREAD_SAFE_METHOD_ +real_t TextServerAdvanced::shaped_text_get_descent(RID p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } return sd->descent; } -float TextServerAdvanced::shaped_text_get_width(RID p_shaped) const { - _THREAD_SAFE_METHOD_ +real_t TextServerAdvanced::shaped_text_get_width(RID p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } return (sd->text_trimmed ? sd->width_trimmed : sd->width); } -float TextServerAdvanced::shaped_text_get_underline_position(RID p_shaped) const { - _THREAD_SAFE_METHOD_ +real_t TextServerAdvanced::shaped_text_get_underline_position(RID p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } @@ -2732,10 +4614,11 @@ float TextServerAdvanced::shaped_text_get_underline_position(RID p_shaped) const return sd->upos; } -float TextServerAdvanced::shaped_text_get_underline_thickness(RID p_shaped) const { - _THREAD_SAFE_METHOD_ +real_t TextServerAdvanced::shaped_text_get_underline_thickness(RID p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } @@ -2744,31 +4627,76 @@ float TextServerAdvanced::shaped_text_get_underline_thickness(RID p_shaped) cons } struct num_system_data { - String lang; + Set<String> lang; String digits; String percent_sign; String exp; }; static num_system_data num_systems[]{ - { "ar,ar_AR,ar_BH,ar_DJ,ar_EG,ar_ER,ar_IL,ar_IQ,ar_JO,ar_KM,ar_KW,ar_LB,ar_MR,ar_OM,ar_PS,ar_QA,ar_SA,ar_SD,ar_SO,ar_SS,ar_SY,ar_TD,ar_YE", U"٠١٢٣٤٥٦٧٨٩٫", U"٪", U"اس" }, - { "fa,ks,pa_Arab,ps,ug,ur_IN,ur,uz_Arab", U"۰۱۲۳۴۵۶۷۸۹٫", U"٪", U"اس" }, - { "as,bn,mni", U"০১২৩৪৫৬৭৮৯.", U"%", U"e" }, - { "mr,ne", U"०१२३४५६७८९.", U"%", U"e" }, - { "dz", U"༠༡༢༣༤༥༦༧༨༩.", U"%", U"e" }, - { "sat", U"᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙.", U"%", U"e" }, - { "my", U"၀၁၂၃၄၅၆၇၈၉.", U"%", U"e" }, - { String(), String(), String(), String() }, + { Set<String>(), U"٠١٢٣٤٥٦٧٨٩٫", U"٪", U"اس" }, + { Set<String>(), U"۰۱۲۳۴۵۶۷۸۹٫", U"٪", U"اس" }, + { Set<String>(), U"০১২৩৪৫৬৭৮৯.", U"%", U"e" }, + { Set<String>(), U"०१२३४५६७८९.", U"%", U"e" }, + { Set<String>(), U"༠༡༢༣༤༥༦༧༨༩.", U"%", U"e" }, + { Set<String>(), U"᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙.", U"%", U"e" }, + { Set<String>(), U"၀၁၂၃၄၅၆၇၈၉.", U"%", U"e" }, + { Set<String>(), String(), String(), String() }, }; +static void _insert_num_systems_lang() { + num_systems[0].lang.insert(StringName("ar")); + num_systems[0].lang.insert(StringName("ar_AR")); + num_systems[0].lang.insert(StringName("ar_BH")); + num_systems[0].lang.insert(StringName("ar_DJ")); + num_systems[0].lang.insert(StringName("ar_EG")); + num_systems[0].lang.insert(StringName("ar_ER")); + num_systems[0].lang.insert(StringName("ar_IL")); + num_systems[0].lang.insert(StringName("ar_IQ")); + num_systems[0].lang.insert(StringName("ar_JO")); + num_systems[0].lang.insert(StringName("ar_KM")); + num_systems[0].lang.insert(StringName("ar_KW")); + num_systems[0].lang.insert(StringName("ar_LB")); + num_systems[0].lang.insert(StringName("ar_MR")); + num_systems[0].lang.insert(StringName("ar_OM")); + num_systems[0].lang.insert(StringName("ar_PS")); + num_systems[0].lang.insert(StringName("ar_QA")); + num_systems[0].lang.insert(StringName("ar_SA")); + num_systems[0].lang.insert(StringName("ar_SD")); + num_systems[0].lang.insert(StringName("ar_SO")); + num_systems[0].lang.insert(StringName("ar_SS")); + num_systems[0].lang.insert(StringName("ar_SY")); + num_systems[0].lang.insert(StringName("ar_TD")); + num_systems[0].lang.insert(StringName("ar_YE")); + + num_systems[1].lang.insert(StringName("fa")); + num_systems[1].lang.insert(StringName("ks")); + num_systems[1].lang.insert(StringName("pa_Arab")); + num_systems[1].lang.insert(StringName("ug")); + num_systems[1].lang.insert(StringName("ur_IN")); + num_systems[1].lang.insert(StringName("ur")); + num_systems[1].lang.insert(StringName("uz_Arab")); + + num_systems[2].lang.insert(StringName("as")); + num_systems[2].lang.insert(StringName("bn")); + num_systems[2].lang.insert(StringName("mni")); + + num_systems[3].lang.insert(StringName("mr")); + num_systems[3].lang.insert(StringName("ne")); + + num_systems[4].lang.insert(StringName("dz")); + + num_systems[5].lang.insert(StringName("sat")); + + num_systems[6].lang.insert(StringName("my")); +} + String TextServerAdvanced::format_number(const String &p_string, const String &p_language) const { - _THREAD_SAFE_METHOD_ - String lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; + const StringName lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; String res = p_string; - for (int i = 0; num_systems[i].lang != String(); i++) { - Vector<String> langs = num_systems[i].lang.split(","); - if (langs.has(lang)) { + for (int i = 0; num_systems[i].lang.size() != 0; i++) { + if (num_systems[i].lang.has(lang)) { if (num_systems[i].digits == String()) { return p_string; } @@ -2789,13 +4717,11 @@ String TextServerAdvanced::format_number(const String &p_string, const String &p } String TextServerAdvanced::parse_number(const String &p_string, const String &p_language) const { - _THREAD_SAFE_METHOD_ - String lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; + const StringName lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; String res = p_string; - for (int i = 0; num_systems[i].lang != String(); i++) { - Vector<String> langs = num_systems[i].lang.split(","); - if (langs.has(lang)) { + for (int i = 0; num_systems[i].lang.size() != 0; i++) { + if (num_systems[i].lang.has(lang)) { if (num_systems[i].digits == String()) { return p_string; } @@ -2819,12 +4745,10 @@ String TextServerAdvanced::parse_number(const String &p_string, const String &p_ } String TextServerAdvanced::percent_sign(const String &p_language) const { - _THREAD_SAFE_METHOD_ - String lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; + const StringName lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; - for (int i = 0; num_systems[i].lang != String(); i++) { - Vector<String> langs = num_systems[i].lang.split(","); - if (langs.has(lang)) { + for (int i = 0; num_systems[i].lang.size() != 0; i++) { + if (num_systems[i].lang.has(lang)) { if (num_systems[i].percent_sign == String()) { return "%"; } @@ -2844,11 +4768,16 @@ void TextServerAdvanced::register_server() { } TextServerAdvanced::TextServerAdvanced() { + _insert_num_systems_lang(); + _insert_feature_sets(); hb_bmp_create_font_funcs(); } TextServerAdvanced::~TextServerAdvanced() { hb_bmp_free_font_funcs(); + if (library != nullptr) { + FT_Done_FreeType(library); + } u_cleanup(); #ifndef ICU_STATIC_DATA if (icu_data != nullptr) { diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index d19ba41a1a..5989035800 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -39,6 +39,7 @@ #include "servers/text_server.h" #include "core/templates/rid_owner.h" +#include "core/templates/thread_work_pool.h" #include "scene/resources/texture.h" #include "script_iterator.h" @@ -53,11 +54,24 @@ #include <unicode/ustring.h> #include <unicode/utypes.h> +#include "modules/modules_enabled.gen.h" + +#ifdef MODULE_FREETYPE_ENABLED +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_TRUETYPE_TABLES_H +#include FT_STROKER_H +#include FT_ADVANCES_H +#include FT_MULTIPLE_MASTERS_H +#include FT_BBOX_H + +#include <hb-ft.h> +#include <hb-ot.h> +#endif + #include <hb-icu.h> #include <hb.h> -#include "font_adv.h" - class TextServerAdvanced : public TextServer { GDCLASS(TextServerAdvanced, TextServer); _THREAD_SAFE_CLASS_ @@ -65,8 +79,149 @@ class TextServerAdvanced : public TextServer { static String interface_name; static uint32_t interface_features; + // ICU support data. + uint8_t *icu_data = nullptr; + // Font cache data. + +#ifdef MODULE_FREETYPE_ENABLED + mutable FT_Library library = nullptr; +#endif + + const int rect_range = 2; + + struct FontTexture { + Image::Format format; + PackedByteArray imgdata; + int texture_w = 0; + int texture_h = 0; + PackedInt32Array offsets; + Ref<ImageTexture> texture; + }; + + struct FontTexturePosition { + int index = 0; + int x = 0; + int y = 0; + }; + + struct FontGlyph { + bool found = false; + int texture_idx = -1; + Rect2 rect; + Rect2 uv_rect; + Vector2 advance; + }; + + struct FontDataForSizeAdvanced { + real_t ascent = 0.f; + real_t descent = 0.f; + real_t underline_position = 0.f; + real_t underline_thickness = 0.f; + real_t scale = 1.f; + real_t oversampling = 1.f; + + int spacing_glyph = 0; + int spacing_space = 0; + + Vector2i size; + + Vector<FontTexture> textures; + HashMap<int32_t, FontGlyph> glyph_map; + Map<Vector2i, Vector2> kerning_map; + + hb_font_t *hb_handle = nullptr; + +#ifdef MODULE_FREETYPE_ENABLED + FT_Face face = nullptr; + FT_StreamRec stream; +#endif + + ~FontDataForSizeAdvanced() { + if (hb_handle != nullptr) { + hb_font_destroy(hb_handle); + } +#ifdef MODULE_FREETYPE_ENABLED + if (face != nullptr) { + FT_Done_Face(face); + } +#endif + } + }; + + struct FontDataAdvanced { + Mutex mutex; + + bool antialiased = true; + bool msdf = false; + int msdf_range = 14; + int msdf_source_size = 48; + int fixed_size = 0; + bool force_autohinter = false; + TextServer::Hinting hinting = TextServer::HINTING_LIGHT; + Dictionary variation_coordinates; + real_t oversampling = 0.f; + + Map<Vector2i, FontDataForSizeAdvanced *> cache; + + bool face_init = false; + Set<uint32_t> supported_scripts; + Dictionary supported_features; + Dictionary supported_varaitions; + + // Language/script support override. + Map<String, bool> language_support_overrides; + Map<String, bool> script_support_overrides; + + PackedByteArray data; + const uint8_t *data_ptr; + size_t data_size; + mutable ThreadWorkPool work_pool; + + ~FontDataAdvanced() { + work_pool.finish(); + for (const Map<Vector2i, FontDataForSizeAdvanced *>::Element *E = cache.front(); E; E = E->next()) { + memdelete(E->get()); + } + cache.clear(); + } + }; + + _FORCE_INLINE_ FontTexturePosition find_texture_pos_for_glyph(FontDataForSizeAdvanced *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) const; +#ifdef MODULE_MSDFGEN_ENABLED + _FORCE_INLINE_ FontGlyph rasterize_msdf(FontDataAdvanced *p_font_data, FontDataForSizeAdvanced *p_data, int p_pixel_range, int p_rect_margin, FT_Outline *outline, const Vector2 &advance) const; +#endif +#ifdef MODULE_FREETYPE_ENABLED + _FORCE_INLINE_ FontGlyph rasterize_bitmap(FontDataForSizeAdvanced *p_data, int p_rect_margin, FT_Bitmap bitmap, int yofs, int xofs, const Vector2 &advance) const; +#endif + _FORCE_INLINE_ bool _ensure_glyph(FontDataAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const; + _FORCE_INLINE_ bool _ensure_cache_for_size(FontDataAdvanced *p_font_data, const Vector2i &p_size) const; + _FORCE_INLINE_ void _font_clear_cache(FontDataAdvanced *p_font_data); + void _generateMTSDF_threaded(uint32_t y, void *p_td) const; + + _FORCE_INLINE_ Vector2i _get_size(const FontDataAdvanced *p_font_data, int p_size) const { + if (p_font_data->msdf) { + return Vector2i(p_font_data->msdf_source_size, 0); + } else if (p_font_data->fixed_size > 0) { + return Vector2i(p_font_data->fixed_size, 0); + } else { + return Vector2i(p_size, 0); + } + } + + _FORCE_INLINE_ Vector2i _get_size_outline(const FontDataAdvanced *p_font_data, const Vector2i &p_size) const { + if (p_font_data->msdf) { + return Vector2i(p_font_data->msdf_source_size, 0); + } else if (p_font_data->fixed_size > 0) { + return Vector2i(p_font_data->fixed_size, MIN(p_size.y, 1)); + } else { + return p_size; + } + } + + // Shaped text cache data. + struct ShapedTextDataAdvanced : public ShapedTextData { /* Intermediate data */ Char16String utf16; @@ -88,7 +243,9 @@ class TextServerAdvanced : public TextServer { } }; - float oversampling = 1.f; + // Common data. + + real_t oversampling = 1.f; mutable RID_PtrOwner<FontDataAdvanced> font_owner; mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner; @@ -97,6 +254,30 @@ class TextServerAdvanced : public TextServer { void _shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_start, int32_t p_end, hb_script_t p_script, hb_direction_t p_direction, Vector<RID> p_fonts, int p_span, int p_fb_index); TextServer::Glyph _shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, RID p_font, int p_font_size); + // HarfBuzz bitmap font interface. + + static hb_font_funcs_t *funcs; + + struct hb_bmp_font_t { + TextServerAdvanced::FontDataForSizeAdvanced *face = nullptr; + bool unref = false; /* Whether to destroy bm_face when done. */ + }; + + static hb_bmp_font_t *_hb_bmp_font_create(TextServerAdvanced::FontDataForSizeAdvanced *p_face, bool p_unref); + static void _hb_bmp_font_destroy(void *p_data); + static hb_bool_t hb_bmp_get_nominal_glyph(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_unicode, hb_codepoint_t *r_glyph, void *p_user_data); + static hb_position_t hb_bmp_get_glyph_h_advance(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, void *p_user_data); + static hb_position_t hb_bmp_get_glyph_v_advance(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, void *p_user_data); + static hb_position_t hb_bmp_get_glyph_h_kerning(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_left_glyph, hb_codepoint_t p_right_glyph, void *p_user_data); + static hb_position_t hb_bmp_get_glyph_v_kerning(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_left_glyph, hb_codepoint_t p_right_glyph, void *p_user_data); + static hb_bool_t hb_bmp_get_glyph_v_origin(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, hb_position_t *r_x, hb_position_t *r_y, void *p_user_data); + static hb_bool_t hb_bmp_get_glyph_extents(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, hb_glyph_extents_t *r_extents, void *p_user_data); + static hb_bool_t hb_bmp_get_font_h_extents(hb_font_t *p_font, void *p_font_data, hb_font_extents_t *r_metrics, void *p_user_data); + static void hb_bmp_create_font_funcs(); + static void hb_bmp_free_font_funcs(); + static void _hb_bmp_font_set_funcs(hb_font_t *p_font, TextServerAdvanced::FontDataForSizeAdvanced *p_face, bool p_unref); + static hb_font_t *hb_bmp_font_create(TextServerAdvanced::FontDataForSizeAdvanced *p_face, hb_destroy_func_t p_destroy); + protected: static void _bind_methods(){}; @@ -119,81 +300,132 @@ public: virtual bool is_locale_right_to_left(const String &p_locale) override; - virtual int32_t name_to_tag(const String &p_name) override; - virtual String tag_to_name(int32_t p_tag) override; + virtual int32_t name_to_tag(const String &p_name) const override; + virtual String tag_to_name(int32_t p_tag) const override; /* Font interface */ - virtual RID create_font_system(const String &p_name, int p_base_size = 16) override; - virtual RID create_font_resource(const String &p_filename, int p_base_size = 16) override; - virtual RID create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size = 16) override; - virtual RID create_font_bitmap(float p_height, float p_ascent, int p_base_size = 16) override; + virtual RID create_font() override; + + virtual void font_set_data(RID p_font_rid, const PackedByteArray &p_data) override; + virtual void font_set_data_ptr(RID p_font_rid, const uint8_t *p_data_ptr, size_t p_data_size) override; + + virtual void font_set_antialiased(RID p_font_rid, bool p_antialiased) override; + virtual bool font_is_antialiased(RID p_font_rid) const override; + + virtual void font_set_multichannel_signed_distance_field(RID p_font_rid, bool p_msdf) override; + virtual bool font_is_multichannel_signed_distance_field(RID p_font_rid) const override; + + virtual void font_set_msdf_pixel_range(RID p_font_rid, int p_msdf_pixel_range) override; + virtual int font_get_msdf_pixel_range(RID p_font_rid) const override; + + virtual void font_set_msdf_size(RID p_font_rid, int p_msdf_size) override; + virtual int font_get_msdf_size(RID p_font_rid) const override; + + virtual void font_set_fixed_size(RID p_font_rid, int p_fixed_size) override; + virtual int font_get_fixed_size(RID p_font_rid) const override; + + virtual void font_set_force_autohinter(RID p_font_rid, bool p_force_autohinter) override; + virtual bool font_is_force_autohinter(RID p_font_rid) const override; + + virtual void font_set_hinting(RID p_font_rid, TextServer::Hinting p_hinting) override; + virtual TextServer::Hinting font_get_hinting(RID p_font_rid) const override; + + virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override; + virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override; + + virtual void font_set_oversampling(RID p_font_rid, real_t p_oversampling) override; + virtual real_t font_get_oversampling(RID p_font_rid) const override; + + virtual Array font_get_size_cache_list(RID p_font_rid) const override; + virtual void font_clear_size_cache(RID p_font_rid) override; + virtual void font_remove_size_cache(RID p_font_rid, const Vector2i &p_size) override; + + hb_font_t *_font_get_hb_handle(RID p_font, int p_font_size) const; + + virtual void font_set_ascent(RID p_font_rid, int p_size, real_t p_ascent) override; + virtual real_t font_get_ascent(RID p_font_rid, int p_size) const override; + + virtual void font_set_descent(RID p_font_rid, int p_size, real_t p_descent) override; + virtual real_t font_get_descent(RID p_font_rid, int p_size) const override; + + virtual void font_set_underline_position(RID p_font_rid, int p_size, real_t p_underline_position) override; + virtual real_t font_get_underline_position(RID p_font_rid, int p_size) const override; + + virtual void font_set_underline_thickness(RID p_font_rid, int p_size, real_t p_underline_thickness) override; + virtual real_t font_get_underline_thickness(RID p_font_rid, int p_size) const override; + + virtual void font_set_scale(RID p_font_rid, int p_size, real_t p_scale) override; + virtual real_t font_get_scale(RID p_font_rid, int p_size) const override; + + virtual void font_set_spacing(RID p_font_rid, int p_size, SpacingType p_spacing, int p_value) override; + virtual int font_get_spacing(RID p_font_rid, int p_size, SpacingType p_spacing) const override; - virtual void font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) override; - virtual void font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) override; - virtual void font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) override; + virtual int font_get_texture_count(RID p_font_rid, const Vector2i &p_size) const override; + virtual void font_clear_textures(RID p_font_rid, const Vector2i &p_size) override; + virtual void font_remove_texture(RID p_font_rid, const Vector2i &p_size, int p_texture_index) override; - virtual float font_get_height(RID p_font, int p_size) const override; - virtual float font_get_ascent(RID p_font, int p_size) const override; - virtual float font_get_descent(RID p_font, int p_size) const override; + virtual void font_set_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) override; + virtual Ref<Image> font_get_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const override; - virtual float font_get_underline_position(RID p_font, int p_size) const override; - virtual float font_get_underline_thickness(RID p_font, int p_size) const override; + virtual void font_set_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) override; + virtual PackedInt32Array font_get_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const override; - virtual int font_get_spacing_space(RID p_font) const override; - virtual void font_set_spacing_space(RID p_font, int p_value) override; + virtual Array font_get_glyph_list(RID p_font_rid, const Vector2i &p_size) const override; + virtual void font_clear_glyphs(RID p_font_rid, const Vector2i &p_size) override; + virtual void font_remove_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) override; - virtual int font_get_spacing_glyph(RID p_font) const override; - virtual void font_set_spacing_glyph(RID p_font, int p_value) override; + virtual Vector2 font_get_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph, const Vector2 &p_advance) override; - virtual void font_set_antialiased(RID p_font, bool p_antialiased) override; - virtual bool font_get_antialiased(RID p_font) const override; + virtual Vector2 font_get_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) override; - virtual Dictionary font_get_feature_list(RID p_font) const override; - virtual Dictionary font_get_variation_list(RID p_font) const override; + virtual Vector2 font_get_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) override; - virtual void font_set_variation(RID p_font, const String &p_name, double p_value) override; - virtual double font_get_variation(RID p_font, const String &p_name) const override; + virtual Rect2 font_get_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) override; - virtual void font_set_hinting(RID p_font, Hinting p_hinting) override; - virtual Hinting font_get_hinting(RID p_font) const override; + virtual int font_get_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) override; - virtual void font_set_distance_field_hint(RID p_font, bool p_distance_field) override; - virtual bool font_get_distance_field_hint(RID p_font) const override; + virtual bool font_get_glyph_contours(RID p_font, int p_size, int32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; - virtual void font_set_force_autohinter(RID p_font, bool p_enabeld) override; - virtual bool font_get_force_autohinter(RID p_font) const override; + virtual Array font_get_kerning_list(RID p_font_rid, int p_size) const override; + virtual void font_clear_kerning_map(RID p_font_rid, int p_size) override; + virtual void font_remove_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) override; - virtual bool font_has_char(RID p_font, char32_t p_char) const override; - virtual String font_get_supported_chars(RID p_font) const override; + virtual void font_set_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) override; + virtual Vector2 font_get_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) const override; - virtual bool font_has_outline(RID p_font) const override; - virtual float font_get_base_size(RID p_font) const override; + virtual int32_t font_get_glyph_index(RID p_font_rid, int p_size, char32_t p_char, char32_t p_variation_selector = 0) const override; - virtual bool font_is_language_supported(RID p_font, const String &p_language) const override; - virtual void font_set_language_support_override(RID p_font, const String &p_language, bool p_supported) override; - virtual bool font_get_language_support_override(RID p_font, const String &p_language) override; - virtual void font_remove_language_support_override(RID p_font, const String &p_language) override; - Vector<String> font_get_language_support_overrides(RID p_font) override; + virtual bool font_has_char(RID p_font_rid, char32_t p_char) const override; + virtual String font_get_supported_chars(RID p_font_rid) const override; - virtual bool font_is_script_supported(RID p_font, const String &p_script) const override; - virtual void font_set_script_support_override(RID p_font, const String &p_script, bool p_supported) override; - virtual bool font_get_script_support_override(RID p_font, const String &p_script) override; - virtual void font_remove_script_support_override(RID p_font, const String &p_script) override; - Vector<String> font_get_script_support_overrides(RID p_font) override; + virtual void font_render_range(RID p_font, const Vector2i &p_size, char32_t p_start, char32_t p_end) override; + virtual void font_render_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_index) override; - virtual uint32_t font_get_glyph_index(RID p_font, char32_t p_char, char32_t p_variation_selector = 0x0000) const override; - virtual Vector2 font_get_glyph_advance(RID p_font, uint32_t p_index, int p_size) const override; - virtual Vector2 font_get_glyph_kerning(RID p_font, uint32_t p_index_a, uint32_t p_index_b, int p_size) const override; + virtual void font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; + virtual void font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; - virtual Vector2 font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; - virtual Vector2 font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; + virtual bool font_is_language_supported(RID p_font_rid, const String &p_language) const override; + virtual void font_set_language_support_override(RID p_font_rid, const String &p_language, bool p_supported) override; + virtual bool font_get_language_support_override(RID p_font_rid, const String &p_language) override; + virtual void font_remove_language_support_override(RID p_font_rid, const String &p_language) override; + virtual Vector<String> font_get_language_support_overrides(RID p_font_rid) override; - virtual bool font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; + virtual bool font_is_script_supported(RID p_font_rid, const String &p_script) const override; + virtual void font_set_script_support_override(RID p_font_rid, const String &p_script, bool p_supported) override; + virtual bool font_get_script_support_override(RID p_font_rid, const String &p_script) override; + virtual void font_remove_script_support_override(RID p_font_rid, const String &p_script) override; + virtual Vector<String> font_get_script_support_overrides(RID p_font_rid) override; - virtual float font_get_oversampling() const override; - virtual void font_set_oversampling(float p_oversampling) override; + virtual Dictionary font_supported_feature_list(RID p_font_rid) const override; + virtual Dictionary font_supported_variation_list(RID p_font_rid) const override; - virtual Vector<String> get_system_fonts() const override; + virtual real_t font_get_global_oversampling() const override; + virtual void font_set_global_oversampling(real_t p_oversampling) override; /* Shaped text buffer interface */ @@ -222,14 +454,14 @@ public: virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; - virtual float shaped_text_fit_to_width(RID p_shaped, float p_width, uint8_t /*JustificationFlag*/ p_jst_flags = JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA) override; - virtual float shaped_text_tab_align(RID p_shaped, const Vector<float> &p_tab_stops) override; + virtual real_t shaped_text_fit_to_width(RID p_shaped, real_t p_width, uint8_t /*JustificationFlag*/ p_jst_flags = JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA) override; + virtual real_t shaped_text_tab_align(RID p_shaped, const Vector<real_t> &p_tab_stops) override; virtual bool shaped_text_shape(RID p_shaped) override; virtual bool shaped_text_update_breaks(RID p_shaped) override; virtual bool shaped_text_update_justification_ops(RID p_shaped) override; - virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_trim_flags) override; + virtual void shaped_text_overrun_trim_to_width(RID p_shaped, real_t p_width, uint8_t p_trim_flags) override; virtual TrimData shaped_text_get_trim_data(RID p_shaped) const override; virtual bool shaped_text_is_ready(RID p_shaped) const override; @@ -244,11 +476,11 @@ public: virtual Rect2 shaped_text_get_object_rect(RID p_shaped, Variant p_key) const override; virtual Size2 shaped_text_get_size(RID p_shaped) const override; - virtual float shaped_text_get_ascent(RID p_shaped) const override; - virtual float shaped_text_get_descent(RID p_shaped) const override; - virtual float shaped_text_get_width(RID p_shaped) const override; - virtual float shaped_text_get_underline_position(RID p_shaped) const override; - virtual float shaped_text_get_underline_thickness(RID p_shaped) const override; + virtual real_t shaped_text_get_ascent(RID p_shaped) const override; + virtual real_t shaped_text_get_descent(RID p_shaped) const override; + virtual real_t shaped_text_get_width(RID p_shaped) const override; + virtual real_t shaped_text_get_underline_position(RID p_shaped) const override; + virtual real_t shaped_text_get_underline_thickness(RID p_shaped) const override; virtual String format_number(const String &p_string, const String &p_language = "") const override; virtual String parse_number(const String &p_string, const String &p_language = "") const override; diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub index 03eccbe7bd..31d1db6167 100644 --- a/modules/text_server_fb/SCsub +++ b/modules/text_server_fb/SCsub @@ -3,11 +3,23 @@ Import("env") Import("env_modules") +freetype_enabled = env.module_check_dependencies("text_server_fb", ["freetype"], True) +msdngen_enabled = env.module_check_dependencies("text_server_fb", ["msdfgen"], True) + env_text_server_fb = env_modules.Clone() -env_text_server_fb.Append( - CPPPATH=[ - "#thirdparty/freetype/include", - ] -) + +if msdngen_enabled: + env_text_server_fb.Append( + CPPPATH=[ + "#thirdparty/msdfgen", + ] + ) + +if freetype_enabled: + env_text_server_fb.Append( + CPPPATH=[ + "#thirdparty/freetype/include", + ] + ) env_text_server_fb.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/text_server_fb/bitmap_font_fb.cpp b/modules/text_server_fb/bitmap_font_fb.cpp deleted file mode 100644 index 313f170f04..0000000000 --- a/modules/text_server_fb/bitmap_font_fb.cpp +++ /dev/null @@ -1,352 +0,0 @@ -/*************************************************************************/ -/* bitmap_font_fb.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 "bitmap_font_fb.h" - -Error BitmapFontDataFallback::load_from_file(const String &p_filename, int p_base_size) { - _THREAD_SAFE_METHOD_ - //fnt format used by angelcode bmfont - //http://www.angelcode.com/products/bmfont/ - - FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!f, ERR_FILE_NOT_FOUND, "Can't open font: " + p_filename + "."); - - while (true) { - String line = f->get_line(); - - int delimiter = line.find(" "); - String type = line.substr(0, delimiter); - int pos = delimiter + 1; - Map<String, String> keys; - - while (pos < line.size() && line[pos] == ' ') { - pos++; - } - - while (pos < line.size()) { - int eq = line.find("=", pos); - if (eq == -1) { - break; - } - String key = line.substr(pos, eq - pos); - int end = -1; - String value; - if (line[eq + 1] == '"') { - end = line.find("\"", eq + 2); - if (end == -1) { - break; - } - value = line.substr(eq + 2, end - 1 - eq - 1); - pos = end + 1; - } else { - end = line.find(" ", eq + 1); - if (end == -1) { - end = line.size(); - } - value = line.substr(eq + 1, end - eq); - pos = end; - } - - while (pos < line.size() && line[pos] == ' ') { - pos++; - } - - keys[key] = value; - } - - if (type == "info") { - if (keys.has("size")) { - base_size = keys["size"].to_int(); - } - } else if (type == "common") { - if (keys.has("lineHeight")) { - height = keys["lineHeight"].to_int(); - } - if (keys.has("base")) { - ascent = keys["base"].to_int(); - } - } else if (type == "page") { - if (keys.has("file")) { - String base_dir = p_filename.get_base_dir(); - String file = base_dir.plus_file(keys["file"]); - if (RenderingServer::get_singleton() != nullptr) { - Ref<Texture2D> tex = ResourceLoader::load(file); - if (tex.is_null()) { - ERR_PRINT("Can't load font texture!"); - } else { - ERR_FAIL_COND_V_MSG(tex.is_null(), ERR_FILE_CANT_READ, "It's not a reference to a valid Texture object."); - textures.push_back(tex); - } - } - } - } else if (type == "char") { - Character c; - char32_t idx = 0; - if (keys.has("id")) { - idx = keys["id"].to_int(); - } - if (keys.has("x")) { - c.rect.position.x = keys["x"].to_int(); - } - if (keys.has("y")) { - c.rect.position.y = keys["y"].to_int(); - } - if (keys.has("width")) { - c.rect.size.width = keys["width"].to_int(); - } - if (keys.has("height")) { - c.rect.size.height = keys["height"].to_int(); - } - if (keys.has("xoffset")) { - c.align.x = keys["xoffset"].to_int(); - } - if (keys.has("yoffset")) { - c.align.y = keys["yoffset"].to_int(); - } - if (keys.has("page")) { - c.texture_idx = keys["page"].to_int(); - } - if (keys.has("xadvance")) { - c.advance.x = keys["xadvance"].to_int(); - } - if (keys.has("yadvance")) { - c.advance.y = keys["yadvance"].to_int(); - } - if (c.advance.x < 0) { - c.advance.x = c.rect.size.width + 1; - } - if (c.advance.y < 0) { - c.advance.y = c.rect.size.height + 1; - } - char_map[idx] = c; - } else if (type == "kerning") { - KerningPairKey kpk; - float k = 0.0; - if (keys.has("first")) { - kpk.A = keys["first"].to_int(); - } - if (keys.has("second")) { - kpk.B = keys["second"].to_int(); - } - if (keys.has("amount")) { - k = keys["amount"].to_int(); - } - kerning_map[kpk] = k; - } - - if (f->eof_reached()) { - break; - } - } - if (base_size == 0) { - base_size = height; - } - - valid = true; - - memdelete(f); - return OK; -} - -Error BitmapFontDataFallback::bitmap_new(float p_height, float p_ascent, int p_base_size) { - height = p_height; - ascent = p_ascent; - - base_size = p_base_size; - if (base_size == 0) { - base_size = height; - } - - char_map.clear(); - textures.clear(); - kerning_map.clear(); - - valid = true; - - return OK; -} - -void BitmapFontDataFallback::bitmap_add_texture(const Ref<Texture> &p_texture) { - ERR_FAIL_COND(!valid); - ERR_FAIL_COND_MSG(p_texture.is_null(), "It's not a reference to a valid Texture object."); - - textures.push_back(p_texture); -} - -void BitmapFontDataFallback::bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { - ERR_FAIL_COND(!valid); - - Character chr; - chr.rect = p_rect; - chr.texture_idx = p_texture_idx; - if (p_advance < 0) { - chr.advance.x = chr.rect.size.x; - } else { - chr.advance.x = p_advance; - } - chr.align = p_align; - char_map[p_char] = chr; -} - -void BitmapFontDataFallback::bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) { - ERR_FAIL_COND(!valid); - - KerningPairKey kpk; - kpk.A = p_A; - kpk.B = p_B; - - if (p_kerning == 0 && kerning_map.has(kpk)) { - kerning_map.erase(kpk); - } else { - kerning_map[kpk] = p_kerning; - } -} - -float BitmapFontDataFallback::get_height(int p_size) const { - ERR_FAIL_COND_V(!valid, 0.f); - return height * (float(p_size) / float(base_size)); -} - -float BitmapFontDataFallback::get_ascent(int p_size) const { - ERR_FAIL_COND_V(!valid, 0.f); - return ascent * (float(p_size) / float(base_size)); -} - -float BitmapFontDataFallback::get_descent(int p_size) const { - ERR_FAIL_COND_V(!valid, 0.f); - return (height - ascent) * (float(p_size) / float(base_size)); -} - -float BitmapFontDataFallback::get_underline_position(int p_size) const { - ERR_FAIL_COND_V(!valid, 0.f); - return 2 * (float(p_size) / float(base_size)); -} - -float BitmapFontDataFallback::get_underline_thickness(int p_size) const { - ERR_FAIL_COND_V(!valid, 0.f); - return 1 * (float(p_size) / float(base_size)); -} - -void BitmapFontDataFallback::set_distance_field_hint(bool p_distance_field) { - distance_field_hint = p_distance_field; -} - -bool BitmapFontDataFallback::get_distance_field_hint() const { - return distance_field_hint; -} - -float BitmapFontDataFallback::get_base_size() const { - return base_size; -} - -bool BitmapFontDataFallback::has_char(char32_t p_char) const { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(!valid, false); - return char_map.has(p_char); -} - -String BitmapFontDataFallback::get_supported_chars() const { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(!valid, String()); - String chars; - const char32_t *k = nullptr; - while ((k = char_map.next(k))) { - chars += char32_t(*k); - } - return chars; -} - -Vector2 BitmapFontDataFallback::get_advance(char32_t p_char, int p_size) const { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(!valid, Vector2()); - const Character *c = char_map.getptr(p_char); - ERR_FAIL_COND_V(c == nullptr, Vector2()); - - return c->advance * (float(p_size) / float(base_size)); -} - -Vector2 BitmapFontDataFallback::get_kerning(char32_t p_char, char32_t p_next, int p_size) const { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(!valid, Vector2()); - KerningPairKey kpk; - kpk.A = p_char; - kpk.B = p_next; - - const Map<KerningPairKey, int>::Element *E = kerning_map.find(kpk); - if (E) { - return Vector2(-E->get() * (float(p_size) / float(base_size)), 0); - } else { - return Vector2(); - } -} - -Vector2 BitmapFontDataFallback::draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - if (p_index == 0) { - return Vector2(); - } - ERR_FAIL_COND_V(!valid, Vector2()); - const Character *c = char_map.getptr(p_index); - - ERR_FAIL_COND_V(c == nullptr, Vector2()); - ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), Vector2()); - if (c->texture_idx != -1) { - Point2i cpos = p_pos; - cpos += (c->align + Vector2(0, -ascent)) * (float(p_size) / float(base_size)); - Size2i csize = c->rect.size * (float(p_size) / float(base_size)); - if (RenderingServer::get_singleton() != nullptr) { - //if (distance_field_hint) { // Not implemented. - // RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(p_canvas, true); - //} - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), textures[c->texture_idx]->get_rid(), c->rect, p_color, false, false); - //if (distance_field_hint) { - // RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(p_canvas, false); - //} - } - } - - return c->advance * (float(p_size) / float(base_size)); -} - -Vector2 BitmapFontDataFallback::draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - if (p_index == 0) { - return Vector2(); - } - ERR_FAIL_COND_V(!valid, Vector2()); - const Character *c = char_map.getptr(p_index); - - ERR_FAIL_COND_V(c == nullptr, Vector2()); - ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), Vector2()); - - // Not supported, return advance for compatibility. - - return c->advance * (float(p_size) / float(base_size)); -} diff --git a/modules/text_server_fb/bitmap_font_fb.h b/modules/text_server_fb/bitmap_font_fb.h deleted file mode 100644 index 7cd7507ebc..0000000000 --- a/modules/text_server_fb/bitmap_font_fb.h +++ /dev/null @@ -1,111 +0,0 @@ -/*************************************************************************/ -/* bitmap_font_fb.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 BITMAP_FONT_FALLBACK_H -#define BITMAP_FONT_FALLBACK_H - -#include "font_fb.h" - -struct BitmapFontDataFallback : public FontDataFallback { - _THREAD_SAFE_CLASS_ - -private: - Vector<Ref<Texture2D>> textures; - - struct Character { - int texture_idx = 0; - Rect2 rect; - Vector2 align; - Vector2 advance = Vector2(-1, -1); - }; - - struct KerningPairKey { - union { - struct { - uint32_t A, B; - }; - - uint64_t pair = 0; - }; - - _FORCE_INLINE_ bool operator<(const KerningPairKey &p_r) const { return pair < p_r.pair; } - }; - - HashMap<char32_t, Character> char_map; - Map<KerningPairKey, int> kerning_map; - - float height = 0.f; - float ascent = 0.f; - int base_size = 0; - bool distance_field_hint = false; - -public: - virtual void clear_cache() override{}; - - virtual Error load_from_file(const String &p_filename, int p_base_size) override; - virtual Error bitmap_new(float p_height, float p_ascent, int p_base_size) override; - - virtual void bitmap_add_texture(const Ref<Texture> &p_texture) override; - virtual void bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) override; - virtual void bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) override; - - virtual float get_height(int p_size) const override; - virtual float get_ascent(int p_size) const override; - virtual float get_descent(int p_size) const override; - - virtual float get_underline_position(int p_size) const override; - virtual float get_underline_thickness(int p_size) const override; - - virtual void set_antialiased(bool p_antialiased) override{}; - virtual bool get_antialiased() const override { return false; }; - - virtual void set_hinting(TextServer::Hinting p_hinting) override{}; - virtual TextServer::Hinting get_hinting() const override { return TextServer::HINTING_NONE; }; - - virtual void set_distance_field_hint(bool p_distance_field) override; - virtual bool get_distance_field_hint() const override; - - virtual void set_force_autohinter(bool p_enabeld) override{}; - virtual bool get_force_autohinter() const override { return false; }; - - virtual bool has_outline() const override { return false; }; - virtual float get_base_size() const override; - - virtual bool has_char(char32_t p_char) const override; - virtual String get_supported_chars() const override; - - virtual Vector2 get_advance(char32_t p_char, int p_size) const override; - virtual Vector2 get_kerning(char32_t p_char, char32_t p_next, int p_size) const override; - - virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; - virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; -}; - -#endif // BITMAP_FONT_FALLBACK_H diff --git a/modules/text_server_fb/dynamic_font_fb.cpp b/modules/text_server_fb/dynamic_font_fb.cpp deleted file mode 100644 index 7e77987074..0000000000 --- a/modules/text_server_fb/dynamic_font_fb.cpp +++ /dev/null @@ -1,713 +0,0 @@ -/*************************************************************************/ -/* dynamic_font_fb.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 "dynamic_font_fb.h" - -#ifdef MODULE_FREETYPE_ENABLED - -#include FT_STROKER_H -#include FT_ADVANCES_H - -DynamicFontDataFallback::DataAtSize *DynamicFontDataFallback::get_data_for_size(int p_size, int p_outline_size) { - ERR_FAIL_COND_V(!valid, nullptr); - ERR_FAIL_COND_V(p_size < 0 || p_size > UINT16_MAX, nullptr); - ERR_FAIL_COND_V(p_outline_size < 0 || p_outline_size > UINT16_MAX, nullptr); - - CacheID id; - id.size = p_size; - id.outline_size = p_outline_size; - - DataAtSize *fds = nullptr; - Map<CacheID, DataAtSize *>::Element *E = nullptr; - if (p_outline_size != 0) { - E = size_cache_outline.find(id); - } else { - E = size_cache.find(id); - } - - if (E != nullptr) { - fds = E->get(); - } else { - if (font_mem == nullptr && font_path != String()) { - if (!font_mem_cache.is_empty()) { - font_mem = font_mem_cache.ptr(); - font_mem_size = font_mem_cache.size(); - } else { - FileAccess *f = FileAccess::open(font_path, FileAccess::READ); - if (!f) { - ERR_FAIL_V_MSG(nullptr, "Cannot open font file '" + font_path + "'."); - } - - uint64_t len = f->get_length(); - font_mem_cache.resize(len); - f->get_buffer(font_mem_cache.ptrw(), len); - font_mem = font_mem_cache.ptr(); - font_mem_size = len; - f->close(); - } - } - - int error = 0; - fds = memnew(DataAtSize); - if (font_mem) { - memset(&fds->stream, 0, sizeof(FT_StreamRec)); - fds->stream.base = (unsigned char *)font_mem; - fds->stream.size = font_mem_size; - fds->stream.pos = 0; - - FT_Open_Args fargs; - memset(&fargs, 0, sizeof(FT_Open_Args)); - fargs.memory_base = (unsigned char *)font_mem; - fargs.memory_size = font_mem_size; - fargs.flags = FT_OPEN_MEMORY; - fargs.stream = &fds->stream; - error = FT_Open_Face(library, &fargs, 0, &fds->face); - - } else { - memdelete(fds); - ERR_FAIL_V_MSG(nullptr, "DynamicFont uninitialized."); - } - - if (error == FT_Err_Unknown_File_Format) { - memdelete(fds); - ERR_FAIL_V_MSG(nullptr, "Unknown font format."); - - } else if (error) { - memdelete(fds); - ERR_FAIL_V_MSG(nullptr, "Error loading font."); - } - - oversampling = TS->font_get_oversampling(); - - if (FT_HAS_COLOR(fds->face) && fds->face->num_fixed_sizes > 0) { - int best_match = 0; - int diff = ABS(p_size - ((int64_t)fds->face->available_sizes[0].width)); - fds->scale_color_font = float(p_size * oversampling) / fds->face->available_sizes[0].width; - for (int i = 1; i < fds->face->num_fixed_sizes; i++) { - int ndiff = ABS(p_size - ((int64_t)fds->face->available_sizes[i].width)); - if (ndiff < diff) { - best_match = i; - diff = ndiff; - fds->scale_color_font = float(p_size * oversampling) / fds->face->available_sizes[i].width; - } - } - FT_Select_Size(fds->face, best_match); - } else { - FT_Set_Pixel_Sizes(fds->face, 0, p_size * oversampling); - } - - fds->size = p_size; - fds->ascent = (fds->face->size->metrics.ascender / 64.0) / oversampling * fds->scale_color_font; - fds->descent = (-fds->face->size->metrics.descender / 64.0) / oversampling * fds->scale_color_font; - fds->underline_position = (-FT_MulFix(fds->face->underline_position, fds->face->size->metrics.y_scale) / 64.0) / oversampling * fds->scale_color_font; - fds->underline_thickness = (FT_MulFix(fds->face->underline_thickness, fds->face->size->metrics.y_scale) / 64.0) / oversampling * fds->scale_color_font; - - if (p_outline_size != 0) { - size_cache_outline[id] = fds; - } else { - size_cache[id] = fds; - } - } - - return fds; -} - -DynamicFontDataFallback::TexturePosition DynamicFontDataFallback::find_texture_pos_for_glyph(DynamicFontDataFallback::DataAtSize *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) { - TexturePosition ret; - ret.index = -1; - - int mw = p_width; - int mh = p_height; - - for (int i = 0; i < p_data->textures.size(); i++) { - const CharTexture &ct = p_data->textures[i]; - - if (RenderingServer::get_singleton() != nullptr) { - if (ct.texture->get_format() != p_image_format) { - continue; - } - } - - if (mw > ct.texture_size || mh > ct.texture_size) { //too big for this texture - continue; - } - - ret.y = 0x7FFFFFFF; - ret.x = 0; - - for (int j = 0; j < ct.texture_size - mw; j++) { - int max_y = 0; - - for (int k = j; k < j + mw; k++) { - int y = ct.offsets[k]; - if (y > max_y) { - max_y = y; - } - } - - if (max_y < ret.y) { - ret.y = max_y; - ret.x = j; - } - } - - if (ret.y == 0x7FFFFFFF || ret.y + mh > ct.texture_size) { - continue; //fail, could not fit it here - } - - ret.index = i; - break; - } - - if (ret.index == -1) { - //could not find texture to fit, create one - ret.x = 0; - ret.y = 0; - - int texsize = MAX(p_data->size * oversampling * 8, 256); - if (mw > texsize) { - texsize = mw; //special case, adapt to it? - } - if (mh > texsize) { - texsize = mh; //special case, adapt to it? - } - - texsize = next_power_of_2(texsize); - - texsize = MIN(texsize, 4096); - - CharTexture tex; - tex.texture_size = texsize; - tex.imgdata.resize(texsize * texsize * p_color_size); //grayscale alpha - - { - //zero texture - uint8_t *w = tex.imgdata.ptrw(); - ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.imgdata.size(), ret); - // Initialize the texture to all-white pixels to prevent artifacts when the - // font is displayed at a non-default scale with filtering enabled. - if (p_color_size == 2) { - for (int i = 0; i < texsize * texsize * p_color_size; i += 2) { // FORMAT_LA8 - w[i + 0] = 255; - w[i + 1] = 0; - } - } else if (p_color_size == 4) { - for (int i = 0; i < texsize * texsize * p_color_size; i += 4) { // FORMAT_RGBA8 - w[i + 0] = 255; - w[i + 1] = 255; - w[i + 2] = 255; - w[i + 3] = 0; - } - } else { - ERR_FAIL_V(ret); - } - } - tex.offsets.resize(texsize); - for (int i = 0; i < texsize; i++) { //zero offsets - tex.offsets.write[i] = 0; - } - - p_data->textures.push_back(tex); - ret.index = p_data->textures.size() - 1; - } - - return ret; -} - -DynamicFontDataFallback::Character DynamicFontDataFallback::Character::not_found() { - Character ch; - return ch; -} - -DynamicFontDataFallback::Character DynamicFontDataFallback::bitmap_to_character(DynamicFontDataFallback::DataAtSize *p_data, FT_Bitmap bitmap, int yofs, int xofs, const Vector2 &advance) { - int w = bitmap.width; - int h = bitmap.rows; - - int mw = w + rect_margin * 2; - int mh = h + rect_margin * 2; - - ERR_FAIL_COND_V(mw > 4096, Character::not_found()); - ERR_FAIL_COND_V(mh > 4096, Character::not_found()); - - int color_size = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2; - Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8; - - TexturePosition tex_pos = find_texture_pos_for_glyph(p_data, color_size, require_format, mw, mh); - ERR_FAIL_COND_V(tex_pos.index < 0, Character::not_found()); - - //fit character in char texture - - CharTexture &tex = p_data->textures.write[tex_pos.index]; - - { - uint8_t *wr = tex.imgdata.ptrw(); - - for (int i = 0; i < h; i++) { - for (int j = 0; j < w; j++) { - int ofs = ((i + tex_pos.y + rect_margin) * tex.texture_size + j + tex_pos.x + rect_margin) * color_size; - ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), Character::not_found()); - switch (bitmap.pixel_mode) { - case FT_PIXEL_MODE_MONO: { - int byte = i * bitmap.pitch + (j >> 3); - int bit = 1 << (7 - (j % 8)); - wr[ofs + 0] = 255; //grayscale as 1 - wr[ofs + 1] = (bitmap.buffer[byte] & bit) ? 255 : 0; - } break; - case FT_PIXEL_MODE_GRAY: - wr[ofs + 0] = 255; //grayscale as 1 - wr[ofs + 1] = bitmap.buffer[i * bitmap.pitch + j]; - break; - case FT_PIXEL_MODE_BGRA: { - int ofs_color = i * bitmap.pitch + (j << 2); - wr[ofs + 2] = bitmap.buffer[ofs_color + 0]; - wr[ofs + 1] = bitmap.buffer[ofs_color + 1]; - wr[ofs + 0] = bitmap.buffer[ofs_color + 2]; - wr[ofs + 3] = bitmap.buffer[ofs_color + 3]; - } break; - // TODO: FT_PIXEL_MODE_LCD - default: - ERR_FAIL_V_MSG(Character::not_found(), "Font uses unsupported pixel format: " + itos(bitmap.pixel_mode) + "."); - break; - } - } - } - } - - //blit to image and texture - { - if (RenderingServer::get_singleton() != nullptr) { - Ref<Image> img = memnew(Image(tex.texture_size, tex.texture_size, 0, require_format, tex.imgdata)); - - if (tex.texture.is_null()) { - tex.texture.instantiate(); - tex.texture->create_from_image(img); - } else { - tex.texture->update(img); //update - } - } - } - - // update height array - for (int k = tex_pos.x; k < tex_pos.x + mw; k++) { - tex.offsets.write[k] = tex_pos.y + mh; - } - - Character chr; - chr.align = (Vector2(xofs, -yofs) * p_data->scale_color_font / oversampling).round(); - chr.advance = (advance * p_data->scale_color_font / oversampling).round(); - chr.texture_idx = tex_pos.index; - chr.found = true; - - chr.rect_uv = Rect2(tex_pos.x + rect_margin, tex_pos.y + rect_margin, w, h); - chr.rect = chr.rect_uv; - chr.rect.position /= oversampling; - chr.rect.size *= (p_data->scale_color_font / oversampling); - return chr; -} - -void DynamicFontDataFallback::update_char(int p_size, char32_t p_char) { - DataAtSize *fds = get_data_for_size(p_size, false); - ERR_FAIL_COND(fds == nullptr); - - if (fds->char_map.has(p_char)) { - return; - } - - Character character = Character::not_found(); - - FT_GlyphSlot slot = fds->face->glyph; - FT_UInt gl_index = FT_Get_Char_Index(fds->face, p_char); - - if (gl_index == 0) { - fds->char_map[p_char] = character; - return; - } - - int ft_hinting; - switch (hinting) { - case TextServer::HINTING_NONE: - ft_hinting = FT_LOAD_NO_HINTING; - break; - case TextServer::HINTING_LIGHT: - ft_hinting = FT_LOAD_TARGET_LIGHT; - break; - default: - ft_hinting = FT_LOAD_TARGET_NORMAL; - break; - } - - FT_Fixed v, h; - FT_Get_Advance(fds->face, gl_index, FT_HAS_COLOR(fds->face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting, &h); - FT_Get_Advance(fds->face, gl_index, FT_HAS_COLOR(fds->face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting | FT_LOAD_VERTICAL_LAYOUT, &v); - - int error = FT_Load_Glyph(fds->face, gl_index, FT_HAS_COLOR(fds->face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting); - if (error) { - fds->char_map[p_char] = character; - return; - } - - error = FT_Render_Glyph(fds->face->glyph, antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); - if (!error) { - character = bitmap_to_character(fds, slot->bitmap, slot->bitmap_top, slot->bitmap_left, Vector2((h + (1 << 9)) >> 10, (v + (1 << 9)) >> 10) / 64.0); - } - - fds->char_map[p_char] = character; -} - -void DynamicFontDataFallback::update_char_outline(int p_size, int p_outline_size, char32_t p_char) { - DataAtSize *fds = get_data_for_size(p_size, p_outline_size); - ERR_FAIL_COND(fds == nullptr); - - if (fds->char_map.has(p_char)) { - return; - } - - Character character = Character::not_found(); - FT_UInt gl_index = FT_Get_Char_Index(fds->face, p_char); - - if (gl_index == 0) { - fds->char_map[p_char] = character; - return; - } - - int error = FT_Load_Glyph(fds->face, gl_index, FT_LOAD_NO_BITMAP | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); - if (error) { - fds->char_map[p_char] = character; - return; - } - - FT_Stroker stroker; - if (FT_Stroker_New(library, &stroker) != 0) { - fds->char_map[p_char] = character; - return; - } - - FT_Stroker_Set(stroker, (int)(p_outline_size * oversampling * 64.0), FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0); - FT_Glyph glyph; - FT_BitmapGlyph glyph_bitmap; - - if (FT_Get_Glyph(fds->face->glyph, &glyph) != 0) { - goto cleanup_stroker; - } - if (FT_Glyph_Stroke(&glyph, stroker, 1) != 0) { - goto cleanup_glyph; - } - if (FT_Glyph_To_Bitmap(&glyph, antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, nullptr, 1) != 0) { - goto cleanup_glyph; - } - - glyph_bitmap = (FT_BitmapGlyph)glyph; - character = bitmap_to_character(fds, glyph_bitmap->bitmap, glyph_bitmap->top, glyph_bitmap->left, Vector2()); - -cleanup_glyph: - FT_Done_Glyph(glyph); -cleanup_stroker: - FT_Stroker_Done(stroker); - - fds->char_map[p_char] = character; -} - -void DynamicFontDataFallback::clear_cache() { - _THREAD_SAFE_METHOD_ - for (Map<CacheID, DataAtSize *>::Element *E = size_cache.front(); E; E = E->next()) { - memdelete(E->get()); - } - size_cache.clear(); - for (Map<CacheID, DataAtSize *>::Element *E = size_cache_outline.front(); E; E = E->next()) { - memdelete(E->get()); - } - size_cache_outline.clear(); -} - -Error DynamicFontDataFallback::load_from_file(const String &p_filename, int p_base_size) { - _THREAD_SAFE_METHOD_ - if (library == nullptr) { - int error = FT_Init_FreeType(&library); - ERR_FAIL_COND_V_MSG(error != 0, ERR_CANT_CREATE, "Error initializing FreeType."); - } - clear_cache(); - - font_path = p_filename; - base_size = p_base_size; - - valid = true; - DataAtSize *fds = get_data_for_size(base_size); // load base size. - if (fds == nullptr) { - valid = false; - ERR_FAIL_V(ERR_CANT_CREATE); - } - - return OK; -} - -Error DynamicFontDataFallback::load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) { - _THREAD_SAFE_METHOD_ - if (library == nullptr) { - int error = FT_Init_FreeType(&library); - ERR_FAIL_COND_V_MSG(error != 0, ERR_CANT_CREATE, "Error initializing FreeType."); - } - clear_cache(); - - font_mem = p_data; - font_mem_size = p_size; - base_size = p_base_size; - - valid = true; - DataAtSize *fds = get_data_for_size(base_size); // load base size. - if (fds == nullptr) { - valid = false; - ERR_FAIL_V(ERR_CANT_CREATE); - } - - return OK; -} - -float DynamicFontDataFallback::get_height(int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, 0.f); - return fds->ascent + fds->descent; -} - -float DynamicFontDataFallback::get_ascent(int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, 0.f); - return fds->ascent; -} - -float DynamicFontDataFallback::get_descent(int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, 0.f); - return fds->descent; -} - -float DynamicFontDataFallback::get_underline_position(int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, 0.f); - return fds->underline_position; -} - -float DynamicFontDataFallback::get_underline_thickness(int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, 0.f); - return fds->underline_thickness; -} - -void DynamicFontDataFallback::set_antialiased(bool p_antialiased) { - if (antialiased != p_antialiased) { - clear_cache(); - antialiased = p_antialiased; - } -} - -bool DynamicFontDataFallback::get_antialiased() const { - return antialiased; -} - -void DynamicFontDataFallback::set_force_autohinter(bool p_enabled) { - if (force_autohinter != p_enabled) { - clear_cache(); - force_autohinter = p_enabled; - } -} - -bool DynamicFontDataFallback::get_force_autohinter() const { - return force_autohinter; -} - -void DynamicFontDataFallback::set_hinting(TextServer::Hinting p_hinting) { - if (hinting != p_hinting) { - clear_cache(); - hinting = p_hinting; - } -} - -TextServer::Hinting DynamicFontDataFallback::get_hinting() const { - return hinting; -} - -bool DynamicFontDataFallback::has_outline() const { - return true; -} - -float DynamicFontDataFallback::get_base_size() const { - return base_size; -} - -bool DynamicFontDataFallback::has_char(char32_t p_char) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(base_size); - ERR_FAIL_COND_V(fds == nullptr, false); - - const_cast<DynamicFontDataFallback *>(this)->update_char(base_size, p_char); - Character ch = fds->char_map[p_char]; - - return (ch.found); -} - -String DynamicFontDataFallback::get_supported_chars() const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(base_size); - ERR_FAIL_COND_V(fds == nullptr, String()); - - String chars; - - FT_UInt gindex; - FT_ULong charcode = FT_Get_First_Char(fds->face, &gindex); - while (gindex != 0) { - if (charcode != 0) { - chars += char32_t(charcode); - } - charcode = FT_Get_Next_Char(fds->face, charcode, &gindex); - } - - return chars; -} - -Vector2 DynamicFontDataFallback::get_advance(char32_t p_char, int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, Vector2()); - - const_cast<DynamicFontDataFallback *>(this)->update_char(p_size, p_char); - Character ch = fds->char_map[p_char]; - - return ch.advance; -} - -Vector2 DynamicFontDataFallback::get_kerning(char32_t p_char, char32_t p_next, int p_size) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, Vector2()); - - FT_Vector delta; - FT_Get_Kerning(fds->face, FT_Get_Char_Index(fds->face, p_char), FT_Get_Char_Index(fds->face, p_next), FT_KERNING_DEFAULT, &delta); - return Vector2(delta.x, delta.y); -} - -Vector2 DynamicFontDataFallback::draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, Vector2()); - - const_cast<DynamicFontDataFallback *>(this)->update_char(p_size, p_index); - Character ch = fds->char_map[p_index]; - - Vector2 advance; - if (ch.found) { - ERR_FAIL_COND_V(ch.texture_idx < -1 || ch.texture_idx >= fds->textures.size(), Vector2()); - - if (ch.texture_idx != -1) { - Point2i cpos = p_pos; - cpos += ch.align; - - Color modulate = p_color; - if (FT_HAS_COLOR(fds->face)) { - modulate.r = modulate.g = modulate.b = 1.0; - } - if (RenderingServer::get_singleton() != nullptr) { - RID texture = fds->textures[ch.texture_idx].texture->get_rid(); - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, ch.rect.size), texture, ch.rect_uv, modulate, false, false); - } - } - - advance = ch.advance; - } - - return advance; -} - -Vector2 DynamicFontDataFallback::draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size, p_outline_size); - ERR_FAIL_COND_V(fds == nullptr, Vector2()); - - const_cast<DynamicFontDataFallback *>(this)->update_char_outline(p_size, p_outline_size, p_index); - Character ch = fds->char_map[p_index]; - - Vector2 advance; - if (ch.found) { - ERR_FAIL_COND_V(ch.texture_idx < -1 || ch.texture_idx >= fds->textures.size(), Vector2()); - - if (ch.texture_idx != -1) { - Point2i cpos = p_pos; - cpos += ch.align; - - Color modulate = p_color; - if (FT_HAS_COLOR(fds->face)) { - modulate.r = modulate.g = modulate.b = 1.0; - } - if (RenderingServer::get_singleton() != nullptr) { - RID texture = fds->textures[ch.texture_idx].texture->get_rid(); - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, ch.rect.size), texture, ch.rect_uv, modulate, false, false); - } - } - - advance = ch.advance; - } - - return advance; -} - -bool DynamicFontDataFallback::get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { - _THREAD_SAFE_METHOD_ - DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size); - ERR_FAIL_COND_V(fds == nullptr, false); - - int error = FT_Load_Glyph(fds->face, p_index, FT_LOAD_NO_BITMAP | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); - ERR_FAIL_COND_V(error, false); - - r_points.clear(); - r_contours.clear(); - - float h = fds->ascent; - float scale = (1.0 / 64.0) / oversampling * fds->scale_color_font; - for (short i = 0; i < fds->face->glyph->outline.n_points; i++) { - r_points.push_back(Vector3(fds->face->glyph->outline.points[i].x * scale, h - fds->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fds->face->glyph->outline.tags[i]))); - } - for (short i = 0; i < fds->face->glyph->outline.n_contours; i++) { - r_contours.push_back(fds->face->glyph->outline.contours[i]); - } - r_orientation = (FT_Outline_Get_Orientation(&fds->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); - return true; -} - -DynamicFontDataFallback::~DynamicFontDataFallback() { - clear_cache(); - if (library != nullptr) { - FT_Done_FreeType(library); - } -} - -#endif // MODULE_FREETYPE_ENABLED diff --git a/modules/text_server_fb/dynamic_font_fb.h b/modules/text_server_fb/dynamic_font_fb.h deleted file mode 100644 index 82e59fa607..0000000000 --- a/modules/text_server_fb/dynamic_font_fb.h +++ /dev/null @@ -1,173 +0,0 @@ -/*************************************************************************/ -/* dynamic_font_fb.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 DYNAMIC_FONT_FALLBACK_H -#define DYNAMIC_FONT_FALLBACK_H - -#include "font_fb.h" - -#include "modules/modules_enabled.gen.h" -#ifdef MODULE_FREETYPE_ENABLED - -#include <ft2build.h> -#include FT_FREETYPE_H - -struct DynamicFontDataFallback : public FontDataFallback { - _THREAD_SAFE_CLASS_ - -private: - struct CharTexture { - Vector<uint8_t> imgdata; - int texture_size = 0; - Vector<int> offsets; - Ref<ImageTexture> texture; - }; - - struct Character { - bool found = false; - int texture_idx = 0; - Rect2 rect; - Rect2 rect_uv; - Vector2 align; - Vector2 advance = Vector2(-1, -1); - - static Character not_found(); - }; - - struct TexturePosition { - int index = 0; - int x = 0; - int y = 0; - }; - - struct CacheID { - union { - struct { - uint32_t size : 16; - uint32_t outline_size : 16; - }; - uint32_t key = 0; - }; - bool operator<(CacheID right) const { - return key < right.key; - } - }; - - struct DataAtSize { - FT_Face face = nullptr; - FT_StreamRec stream; - - int size = 0; - float scale_color_font = 1.f; - float ascent = 0.0; - float descent = 0.0; - float underline_position = 0.0; - float underline_thickness = 0.0; - - Vector<CharTexture> textures; - HashMap<char32_t, Character> char_map; - - ~DataAtSize() { - if (face != nullptr) { - FT_Done_Face(face); - } - } - }; - - FT_Library library = nullptr; - - // Source data. - const uint8_t *font_mem = nullptr; - int font_mem_size = 0; - String font_path; - Vector<uint8_t> font_mem_cache; - - float rect_margin = 1.f; - int base_size = 16; - float oversampling = 1.f; - bool antialiased = true; - bool force_autohinter = false; - TextServer::Hinting hinting = TextServer::HINTING_LIGHT; - - Map<CacheID, DataAtSize *> size_cache; - Map<CacheID, DataAtSize *> size_cache_outline; - - DataAtSize *get_data_for_size(int p_size, int p_outline_size = 0); - - TexturePosition find_texture_pos_for_glyph(DataAtSize *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height); - Character bitmap_to_character(DataAtSize *p_data, FT_Bitmap bitmap, int yofs, int xofs, const Vector2 &advance); - _FORCE_INLINE_ void update_char(int p_size, char32_t p_char); - _FORCE_INLINE_ void update_char_outline(int p_size, int p_outline_size, char32_t p_char); - -public: - virtual void clear_cache() override; - - virtual Error load_from_file(const String &p_filename, int p_base_size) override; - virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) override; - - virtual float get_height(int p_size) const override; - virtual float get_ascent(int p_size) const override; - virtual float get_descent(int p_size) const override; - - virtual float get_underline_position(int p_size) const override; - virtual float get_underline_thickness(int p_size) const override; - - virtual void set_antialiased(bool p_antialiased) override; - virtual bool get_antialiased() const override; - - virtual void set_hinting(TextServer::Hinting p_hinting) override; - virtual TextServer::Hinting get_hinting() const override; - - virtual void set_force_autohinter(bool p_enabeld) override; - virtual bool get_force_autohinter() const override; - - virtual void set_distance_field_hint(bool p_distance_field) override{}; - virtual bool get_distance_field_hint() const override { return false; }; - - virtual bool has_outline() const override; - virtual float get_base_size() const override; - - virtual bool has_char(char32_t p_char) const override; - virtual String get_supported_chars() const override; - - virtual Vector2 get_advance(char32_t p_char, int p_size) const override; - virtual Vector2 get_kerning(char32_t p_char, char32_t p_next, int p_size) const override; - - virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; - virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override; - - virtual bool get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; - - virtual ~DynamicFontDataFallback() override; -}; - -#endif // MODULE_FREETYPE_ENABLED - -#endif // DYNAMIC_FONT_FALLBACK_H diff --git a/modules/text_server_fb/font_fb.h b/modules/text_server_fb/font_fb.h deleted file mode 100644 index fe9888b7f4..0000000000 --- a/modules/text_server_fb/font_fb.h +++ /dev/null @@ -1,101 +0,0 @@ -/*************************************************************************/ -/* font_fb.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 FONT_FALLBACK_H -#define FONT_FALLBACK_H - -#include "servers/text_server.h" - -struct FontDataFallback { - Map<String, bool> lang_support_overrides; - Map<String, bool> script_support_overrides; - bool valid = false; - int spacing_space = 0; - int spacing_glyph = 0; - - virtual void clear_cache() = 0; - - virtual Error load_from_file(const String &p_filename, int p_base_size) { return ERR_CANT_CREATE; }; - virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) { return ERR_CANT_CREATE; }; - virtual Error bitmap_new(float p_height, float p_ascent, int p_base_size) { return ERR_CANT_CREATE; }; - - virtual void bitmap_add_texture(const Ref<Texture> &p_texture) { ERR_FAIL_MSG("Not supported."); }; - virtual void bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { ERR_FAIL_MSG("Not supported."); }; - virtual void bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) { ERR_FAIL_MSG("Not supported."); }; - - virtual float get_height(int p_size) const = 0; - virtual float get_ascent(int p_size) const = 0; - virtual float get_descent(int p_size) const = 0; - - virtual float get_underline_position(int p_size) const = 0; - virtual float get_underline_thickness(int p_size) const = 0; - - virtual int get_spacing_space() const { return spacing_space; }; - virtual void set_spacing_space(int p_value) { - spacing_space = p_value; - clear_cache(); - }; - - virtual int get_spacing_glyph() const { return spacing_glyph; }; - virtual void set_spacing_glyph(int p_value) { - spacing_glyph = p_value; - clear_cache(); - }; - - virtual void set_antialiased(bool p_antialiased) = 0; - virtual bool get_antialiased() const = 0; - - virtual void set_hinting(TextServer::Hinting p_hinting) = 0; - virtual TextServer::Hinting get_hinting() const = 0; - - virtual void set_distance_field_hint(bool p_distance_field) = 0; - virtual bool get_distance_field_hint() const = 0; - - virtual void set_force_autohinter(bool p_enabeld) = 0; - virtual bool get_force_autohinter() const = 0; - - virtual bool has_outline() const = 0; - virtual float get_base_size() const = 0; - - virtual bool has_char(char32_t p_char) const = 0; - virtual String get_supported_chars() const = 0; - - virtual Vector2 get_advance(char32_t p_char, int p_size) const = 0; - virtual Vector2 get_kerning(char32_t p_char, char32_t p_next, int p_size) const = 0; - - virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const = 0; - virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const = 0; - - virtual bool get_glyph_contours(int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { return false; }; - - virtual ~FontDataFallback(){}; -}; - -#endif // FONT_FALLBACK_H diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 110194c373..e4e6797f92 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -30,8 +30,18 @@ #include "text_server_fb.h" -#include "bitmap_font_fb.h" -#include "dynamic_font_fb.h" +#include "core/string/print_string.h" + +#ifdef MODULE_MSDFGEN_ENABLED +#include "core/ShapeDistanceFinder.h" +#include "core/contour-combiners.h" +#include "core/edge-selectors.h" +#include "msdfgen.h" +#endif + +/*************************************************************************/ +/* Character properties. */ +/*************************************************************************/ _FORCE_INLINE_ bool is_control(char32_t p_char) { return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009F); @@ -100,291 +110,1779 @@ bool TextServerFallback::is_locale_right_to_left(const String &p_locale) { return false; // No RTL support. } +#define OT_TAG(c1, c2, c3, c4) ((int32_t)((((uint32_t)(c1)&0xFF) << 24) | (((uint32_t)(c2)&0xFF) << 16) | (((uint32_t)(c3)&0xFF) << 8) | ((uint32_t)(c4)&0xFF))) + +struct FeatureInfo { + int32_t tag; + String name; +}; + +static FeatureInfo feature_set[] = { + // Registered OpenType variation tags. + { OT_TAG('i', 't', 'a', 'l'), "italic" }, + { OT_TAG('o', 'p', 's', 'z'), "optical_size" }, + { OT_TAG('s', 'l', 'n', 't'), "slant" }, + { OT_TAG('w', 'd', 't', 'h'), "width" }, + { OT_TAG('w', 'g', 'h', 't'), "weight" }, + { 0, String() }, +}; + +_FORCE_INLINE_ int32_t ot_tag_from_string(const char *p_str, int p_len) { + char tag[4]; + uint32_t i; + + if (!p_str || !p_len || !*p_str) + return OT_TAG(0, 0, 0, 0); + + if (p_len < 0 || p_len > 4) { + p_len = 4; + } + for (i = 0; i < (uint32_t)p_len && p_str[i]; i++) { + tag[i] = p_str[i]; + } + + for (; i < 4; i++) { + tag[i] = ' '; + } + + return OT_TAG(tag[0], tag[1], tag[2], tag[3]); +} + +int32_t TextServerFallback::name_to_tag(const String &p_name) const { + for (int i = 0; feature_set[i].tag != 0; i++) { + if (feature_set[i].name == p_name) { + return feature_set[i].tag; + } + } + + // No readable name, use tag string. + return ot_tag_from_string(p_name.replace("custom_", "").ascii().get_data(), -1); +} + +_FORCE_INLINE_ void ot_tag_to_string(int32_t p_tag, char *p_buf) { + p_buf[0] = (char)(uint8_t)(p_tag >> 24); + p_buf[1] = (char)(uint8_t)(p_tag >> 16); + p_buf[2] = (char)(uint8_t)(p_tag >> 8); + p_buf[3] = (char)(uint8_t)(p_tag >> 0); +} + +String TextServerFallback::tag_to_name(int32_t p_tag) const { + for (int i = 0; feature_set[i].tag != 0; i++) { + if (feature_set[i].tag == p_tag) { + return feature_set[i].name; + } + } + + // No readable name, use tag string. + char name[5]; + memset(name, 0, 5); + ot_tag_to_string(p_tag, name); + return String("custom_") + String(name); +} + /*************************************************************************/ -/* Font interface */ +/* Font Glyph Rendering */ /*************************************************************************/ -RID TextServerFallback::create_font_system(const String &p_name, int p_base_size) { - ERR_FAIL_V_MSG(RID(), "System fonts are not supported by this text server."); +_FORCE_INLINE_ TextServerFallback::FontTexturePosition TextServerFallback::find_texture_pos_for_glyph(FontDataForSizeFallback *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) const { + FontTexturePosition ret; + ret.index = -1; + + int mw = p_width; + int mh = p_height; + + for (int i = 0; i < p_data->textures.size(); i++) { + const FontTexture &ct = p_data->textures[i]; + + if (RenderingServer::get_singleton() != nullptr) { + if (ct.texture->get_format() != p_image_format) { + continue; + } + } + + if (mw > ct.texture_w || mh > ct.texture_h) { // Too big for this texture. + continue; + } + + ret.y = 0x7FFFFFFF; + ret.x = 0; + + for (int j = 0; j < ct.texture_w - mw; j++) { + int max_y = 0; + + for (int k = j; k < j + mw; k++) { + int y = ct.offsets[k]; + if (y > max_y) { + max_y = y; + } + } + + if (max_y < ret.y) { + ret.y = max_y; + ret.x = j; + } + } + + if (ret.y == 0x7FFFFFFF || ret.y + mh > ct.texture_h) { + continue; // Fail, could not fit it here. + } + + ret.index = i; + break; + } + + if (ret.index == -1) { + // Could not find texture to fit, create one. + ret.x = 0; + ret.y = 0; + + int texsize = MAX(p_data->size.x * p_data->oversampling * 8, 256); + if (mw > texsize) { + texsize = mw; // Special case, adapt to it? + } + if (mh > texsize) { + texsize = mh; // Special case, adapt to it? + } + + texsize = next_power_of_2(texsize); + + texsize = MIN(texsize, 4096); + + FontTexture tex; + tex.texture_w = texsize; + tex.texture_h = texsize; + tex.format = p_image_format; + tex.imgdata.resize(texsize * texsize * p_color_size); + + { + // Zero texture. + uint8_t *w = tex.imgdata.ptrw(); + ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.imgdata.size(), ret); + // Initialize the texture to all-white pixels to prevent artifacts when the + // font is displayed at a non-default scale with filtering enabled. + if (p_color_size == 2) { + for (int i = 0; i < texsize * texsize * p_color_size; i += 2) { // FORMAT_LA8, BW font. + w[i + 0] = 255; + w[i + 1] = 0; + } + } else if (p_color_size == 4) { + for (int i = 0; i < texsize * texsize * p_color_size; i += 4) { // FORMAT_RGBA8, Color font, Multichannel(+True) SDF. + w[i + 0] = 255; + w[i + 1] = 255; + w[i + 2] = 255; + w[i + 3] = 0; + } + } else { + ERR_FAIL_V(ret); + } + } + tex.offsets.resize(texsize); + for (int i = 0; i < texsize; i++) { // Zero offsets. + tex.offsets.write[i] = 0; + } + + p_data->textures.push_back(tex); + ret.index = p_data->textures.size() - 1; + } + + return ret; } -RID TextServerFallback::create_font_resource(const String &p_filename, int p_base_size) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = nullptr; - if (p_filename.get_extension() == "fnt" || p_filename.get_extension() == "font") { - fd = memnew(BitmapFontDataFallback); +#ifdef MODULE_MSDFGEN_ENABLED + +struct MSContext { + msdfgen::Point2 position; + msdfgen::Shape *shape; + msdfgen::Contour *contour; +}; + +class DistancePixelConversion { + double invRange; + +public: + _FORCE_INLINE_ explicit DistancePixelConversion(double range) : + invRange(1 / range) {} + _FORCE_INLINE_ void operator()(float *pixels, const msdfgen::MultiAndTrueDistance &distance) const { + pixels[0] = float(invRange * distance.r + .5); + pixels[1] = float(invRange * distance.g + .5); + pixels[2] = float(invRange * distance.b + .5); + pixels[3] = float(invRange * distance.a + .5); + } +}; + +struct MSDFThreadData { + msdfgen::Bitmap<float, 4> *output; + msdfgen::Shape *shape; + msdfgen::Projection *projection; + DistancePixelConversion *distancePixelConversion; +}; + +static msdfgen::Point2 ft_point2(const FT_Vector &vector) { + return msdfgen::Point2(vector.x / 60.0f, vector.y / 60.0f); +} + +static int ft_move_to(const FT_Vector *to, void *user) { + MSContext *context = reinterpret_cast<MSContext *>(user); + if (!(context->contour && context->contour->edges.empty())) { + context->contour = &context->shape->addContour(); + } + context->position = ft_point2(*to); + return 0; +} + +static int ft_line_to(const FT_Vector *to, void *user) { + MSContext *context = reinterpret_cast<MSContext *>(user); + msdfgen::Point2 endpoint = ft_point2(*to); + if (endpoint != context->position) { + context->contour->addEdge(new msdfgen::LinearSegment(context->position, endpoint)); + context->position = endpoint; + } + return 0; +} + +static int ft_conic_to(const FT_Vector *control, const FT_Vector *to, void *user) { + MSContext *context = reinterpret_cast<MSContext *>(user); + context->contour->addEdge(new msdfgen::QuadraticSegment(context->position, ft_point2(*control), ft_point2(*to))); + context->position = ft_point2(*to); + return 0; +} + +static int ft_cubic_to(const FT_Vector *control1, const FT_Vector *control2, const FT_Vector *to, void *user) { + MSContext *context = reinterpret_cast<MSContext *>(user); + context->contour->addEdge(new msdfgen::CubicSegment(context->position, ft_point2(*control1), ft_point2(*control2), ft_point2(*to))); + context->position = ft_point2(*to); + return 0; +} + +void TextServerFallback::_generateMTSDF_threaded(uint32_t y, void *p_td) const { + MSDFThreadData *td = (MSDFThreadData *)p_td; + + msdfgen::ShapeDistanceFinder<msdfgen::OverlappingContourCombiner<msdfgen::MultiAndTrueDistanceSelector>> distanceFinder(*td->shape); + int row = td->shape->inverseYAxis ? td->output->height() - y - 1 : y; + for (int col = 0; col < td->output->width(); ++col) { + int x = (y % 2) ? td->output->width() - col - 1 : col; + msdfgen::Point2 p = td->projection->unproject(msdfgen::Point2(x + .5, y + .5)); + msdfgen::MultiAndTrueDistance distance = distanceFinder.distance(p); + td->distancePixelConversion->operator()(td->output->operator()(x, row), distance); + } +} + +_FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_msdf(FontDataFallback *p_font_data, FontDataForSizeFallback *p_data, int p_pixel_range, int p_rect_margin, FT_Outline *outline, const Vector2 &advance) const { + msdfgen::Shape shape; + + shape.contours.clear(); + shape.inverseYAxis = false; + + MSContext context = {}; + context.shape = &shape; + FT_Outline_Funcs ft_functions; + ft_functions.move_to = &ft_move_to; + ft_functions.line_to = &ft_line_to; + ft_functions.conic_to = &ft_conic_to; + ft_functions.cubic_to = &ft_cubic_to; + ft_functions.shift = 0; + ft_functions.delta = 0; + + int error = FT_Outline_Decompose(outline, &ft_functions, &context); + ERR_FAIL_COND_V_MSG(error, FontGlyph(), "FreeType: Outline decomposition error: '" + String(FT_Error_String(error)) + "'."); + if (!shape.contours.empty() && shape.contours.back().edges.empty()) { + shape.contours.pop_back(); + } + + if (FT_Outline_Get_Orientation(outline) == 1) { + for (int i = 0; i < (int)shape.contours.size(); ++i) { + shape.contours[i].reverse(); + } + } + + shape.inverseYAxis = true; + shape.normalize(); + + msdfgen::Shape::Bounds bounds = shape.getBounds(p_pixel_range); + + FontGlyph chr; + chr.found = true; + chr.advance = advance.round(); + + if (shape.validate() && shape.contours.size() > 0) { + int w = (bounds.r - bounds.l); + int h = (bounds.t - bounds.b); + + int mw = w + p_rect_margin * 2; + int mh = h + p_rect_margin * 2; + + ERR_FAIL_COND_V(mw > 4096, FontGlyph()); + ERR_FAIL_COND_V(mh > 4096, FontGlyph()); + + FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, 4, Image::FORMAT_RGBA8, mw, mh); + ERR_FAIL_COND_V(tex_pos.index < 0, FontGlyph()); + FontTexture &tex = p_data->textures.write[tex_pos.index]; + + edgeColoringSimple(shape, 3.0); // Max. angle. + msdfgen::Bitmap<real_t, 4> image(w, h); // Texture size. + //msdfgen::generateMTSDF(image, shape, p_pixel_range, 1.0, msdfgen::Vector2(-bounds.l, -bounds.b)); // Range, scale, translation. + + DistancePixelConversion distancePixelConversion(p_pixel_range); + msdfgen::Projection projection(msdfgen::Vector2(1.0, 1.0), msdfgen::Vector2(-bounds.l, -bounds.b)); + msdfgen::MSDFGeneratorConfig config(true, msdfgen::ErrorCorrectionConfig()); + + MSDFThreadData td; + td.output = ℑ + td.shape = &shape; + td.projection = &projection; + td.distancePixelConversion = &distancePixelConversion; + + if (p_font_data->work_pool.get_thread_count() == 0) { + p_font_data->work_pool.init(); + } + p_font_data->work_pool.do_work(h, this, &TextServerFallback::_generateMTSDF_threaded, &td); + + msdfgen::msdfErrorCorrection(image, shape, projection, p_pixel_range, config); + + { + uint8_t *wr = tex.imgdata.ptrw(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + int ofs = ((i + tex_pos.y + p_rect_margin) * tex.texture_w + j + tex_pos.x + p_rect_margin) * 4; + ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), FontGlyph()); + wr[ofs + 0] = (uint8_t)(CLAMP(image(j, i)[0] * 256.f, 0.f, 255.f)); + wr[ofs + 1] = (uint8_t)(CLAMP(image(j, i)[1] * 256.f, 0.f, 255.f)); + wr[ofs + 2] = (uint8_t)(CLAMP(image(j, i)[2] * 256.f, 0.f, 255.f)); + wr[ofs + 3] = (uint8_t)(CLAMP(image(j, i)[3] * 256.f, 0.f, 255.f)); + } + } + } + + // Blit to image and texture. + { + if (RenderingServer::get_singleton() != nullptr) { + Ref<Image> img = memnew(Image(tex.texture_w, tex.texture_h, 0, Image::FORMAT_RGBA8, tex.imgdata)); + if (tex.texture.is_null()) { + tex.texture.instantiate(); + tex.texture->create_from_image(img); + } else { + tex.texture->update(img); + } + } + } + + // Update height array. + for (int k = tex_pos.x; k < tex_pos.x + mw; k++) { + tex.offsets.write[k] = tex_pos.y + mh; + } + + chr.texture_idx = tex_pos.index; + + chr.uv_rect = Rect2(tex_pos.x + p_rect_margin, tex_pos.y + p_rect_margin, w, h); + chr.rect.position = Vector2(bounds.l, -bounds.t); + chr.rect.size = chr.uv_rect.size; + } + return chr; +} +#endif + #ifdef MODULE_FREETYPE_ENABLED - } else if (p_filename.get_extension() == "ttf" || p_filename.get_extension() == "otf" || p_filename.get_extension() == "woff") { - fd = memnew(DynamicFontDataFallback); +_FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitmap(FontDataForSizeFallback *p_data, int p_rect_margin, FT_Bitmap bitmap, int yofs, int xofs, const Vector2 &advance) const { + int w = bitmap.width; + int h = bitmap.rows; + + int mw = w + p_rect_margin * 2; + int mh = h + p_rect_margin * 2; + + ERR_FAIL_COND_V(mw > 4096, FontGlyph()); + ERR_FAIL_COND_V(mh > 4096, FontGlyph()); + + int color_size = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2; + Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8; + + FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, color_size, require_format, mw, mh); + ERR_FAIL_COND_V(tex_pos.index < 0, FontGlyph()); + + // Fit character in char texture. + + FontTexture &tex = p_data->textures.write[tex_pos.index]; + + { + uint8_t *wr = tex.imgdata.ptrw(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + int ofs = ((i + tex_pos.y + p_rect_margin) * tex.texture_w + j + tex_pos.x + p_rect_margin) * color_size; + ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), FontGlyph()); + switch (bitmap.pixel_mode) { + case FT_PIXEL_MODE_MONO: { + int byte = i * bitmap.pitch + (j >> 3); + int bit = 1 << (7 - (j % 8)); + wr[ofs + 0] = 255; //grayscale as 1 + wr[ofs + 1] = (bitmap.buffer[byte] & bit) ? 255 : 0; + } break; + case FT_PIXEL_MODE_GRAY: + wr[ofs + 0] = 255; //grayscale as 1 + wr[ofs + 1] = bitmap.buffer[i * bitmap.pitch + j]; + break; + case FT_PIXEL_MODE_BGRA: { + int ofs_color = i * bitmap.pitch + (j << 2); + wr[ofs + 2] = bitmap.buffer[ofs_color + 0]; + wr[ofs + 1] = bitmap.buffer[ofs_color + 1]; + wr[ofs + 0] = bitmap.buffer[ofs_color + 2]; + wr[ofs + 3] = bitmap.buffer[ofs_color + 3]; + } break; + default: + ERR_FAIL_V_MSG(FontGlyph(), "Font uses unsupported pixel format: " + itos(bitmap.pixel_mode) + "."); + break; + } + } + } + } + + // Blit to image and texture. + { + if (RenderingServer::get_singleton() != nullptr) { + Ref<Image> img = memnew(Image(tex.texture_w, tex.texture_h, 0, require_format, tex.imgdata)); + + if (tex.texture.is_null()) { + tex.texture.instantiate(); + tex.texture->create_from_image(img); + } else { + tex.texture->update(img); + } + } + } + + // Update height array. + for (int k = tex_pos.x; k < tex_pos.x + mw; k++) { + tex.offsets.write[k] = tex_pos.y + mh; + } + + FontGlyph chr; + chr.advance = (advance * p_data->scale / p_data->oversampling).round(); + chr.texture_idx = tex_pos.index; + chr.found = true; + + chr.uv_rect = Rect2(tex_pos.x + p_rect_margin, tex_pos.y + p_rect_margin, w, h); + chr.rect.position = (Vector2(xofs, -yofs) * p_data->scale / p_data->oversampling).round(); + chr.rect.size = chr.uv_rect.size * p_data->scale / p_data->oversampling; + return chr; +} #endif - } else { - return RID(); + +/*************************************************************************/ +/* Font Cache */ +/*************************************************************************/ + +_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontDataFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false); + + FontDataForSizeFallback *fd = p_font_data->cache[p_size]; + if (fd->glyph_map.has(p_glyph)) { + return fd->glyph_map[p_glyph].found; } - Error err = fd->load_from_file(p_filename, p_base_size); - if (err != OK) { - memdelete(fd); - return RID(); + if (p_glyph == 0) { // Non graphical or invalid glyph, do not render. + fd->glyph_map[p_glyph] = FontGlyph(); + return true; } - return font_owner.make_rid(fd); +#ifdef MODULE_FREETYPE_ENABLED + FontGlyph gl; + if (fd->face) { + FT_Int32 flags = FT_LOAD_DEFAULT; + + bool outline = p_size.y > 0; + switch (p_font_data->hinting) { + case TextServer::HINTING_NONE: + flags |= FT_LOAD_NO_HINTING; + break; + case TextServer::HINTING_LIGHT: + flags |= FT_LOAD_TARGET_LIGHT; + break; + default: + flags |= FT_LOAD_TARGET_NORMAL; + break; + } + if (p_font_data->force_autohinter) { + flags |= FT_LOAD_FORCE_AUTOHINT; + } + if (outline) { + flags |= FT_LOAD_NO_BITMAP; + } else if (FT_HAS_COLOR(fd->face)) { + flags |= FT_LOAD_COLOR; + } + + FT_Fixed v, h; + FT_Get_Advance(fd->face, p_glyph, flags, &h); + FT_Get_Advance(fd->face, p_glyph, flags | FT_LOAD_VERTICAL_LAYOUT, &v); + + int error = FT_Load_Glyph(fd->face, p_glyph, flags); + if (error) { + fd->glyph_map[p_glyph] = FontGlyph(); + ERR_FAIL_V_MSG(false, "FreeType: Failed to load glyph."); + } + + if (!outline) { + if (!p_font_data->msdf) { + error = FT_Render_Glyph(fd->face->glyph, p_font_data->antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); + } + FT_GlyphSlot slot = fd->face->glyph; + if (!error) { + if (p_font_data->msdf) { +#ifdef MODULE_MSDFGEN_ENABLED + gl = rasterize_msdf(p_font_data, fd, p_font_data->msdf_range, rect_range, &slot->outline, Vector2((h + (1 << 9)) >> 10, (v + (1 << 9)) >> 10) / 64.0); +#else + fd->glyph_map[p_glyph] = FontGlyph(); + ERR_FAIL_V_MSG(false, "Compiled without MSDFGEN support!"); +#endif + } else { + gl = rasterize_bitmap(fd, rect_range, slot->bitmap, slot->bitmap_top, slot->bitmap_left, Vector2((h + (1 << 9)) >> 10, (v + (1 << 9)) >> 10) / 64.0); + } + } + } else { + FT_Stroker stroker; + if (FT_Stroker_New(library, &stroker) != 0) { + fd->glyph_map[p_glyph] = FontGlyph(); + ERR_FAIL_V_MSG(false, "FreeType: Failed to load glyph stroker."); + } + + FT_Stroker_Set(stroker, (int)(fd->size.y * fd->oversampling * 16.0), FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0); + FT_Glyph glyph; + FT_BitmapGlyph glyph_bitmap; + + if (FT_Get_Glyph(fd->face->glyph, &glyph) != 0) { + goto cleanup_stroker; + } + if (FT_Glyph_Stroke(&glyph, stroker, 1) != 0) { + goto cleanup_glyph; + } + if (FT_Glyph_To_Bitmap(&glyph, p_font_data->antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, nullptr, 1) != 0) { + goto cleanup_glyph; + } + glyph_bitmap = (FT_BitmapGlyph)glyph; + gl = rasterize_bitmap(fd, rect_range, glyph_bitmap->bitmap, glyph_bitmap->top, glyph_bitmap->left, Vector2()); + + cleanup_glyph: + FT_Done_Glyph(glyph); + cleanup_stroker: + FT_Stroker_Done(stroker); + } + fd->glyph_map[p_glyph] = gl; + return gl.found; + } +#endif + fd->glyph_map[p_glyph] = FontGlyph(); + return false; } -RID TextServerFallback::create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = nullptr; - if (p_type == "fnt" || p_type == "font") { - fd = memnew(BitmapFontDataFallback); +_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontDataFallback *p_font_data, const Vector2i &p_size) const { + if (p_font_data->cache.has(p_size)) { + return true; + } + + FontDataForSizeFallback *fd = memnew(FontDataForSizeFallback); + fd->size = p_size; + if (p_font_data->data_ptr) { + // Init dynamic font. #ifdef MODULE_FREETYPE_ENABLED - } else if (p_type == "ttf" || p_type == "otf" || p_type == "woff") { - fd = memnew(DynamicFontDataFallback); + int error = 0; + if (!library) { + error = FT_Init_FreeType(&library); + ERR_FAIL_COND_V_MSG(error != 0, false, TTR("FreeType: Error initializing library:") + " '" + String(FT_Error_String(error)) + "'."); + } + + memset(&fd->stream, 0, sizeof(FT_StreamRec)); + fd->stream.base = (unsigned char *)p_font_data->data_ptr; + fd->stream.size = p_font_data->data_size; + fd->stream.pos = 0; + + FT_Open_Args fargs; + memset(&fargs, 0, sizeof(FT_Open_Args)); + fargs.memory_base = (unsigned char *)p_font_data->data_ptr; + fargs.memory_size = p_font_data->data_size; + fargs.flags = FT_OPEN_MEMORY; + fargs.stream = &fd->stream; + error = FT_Open_Face(library, &fargs, 0, &fd->face); + if (error) { + FT_Done_Face(fd->face); + fd->face = nullptr; + ERR_FAIL_V_MSG(false, TTR("FreeType: Error loading font:") + " '" + String(FT_Error_String(error)) + "'."); + } + + if (p_font_data->msdf) { + fd->oversampling = 1.0f; + fd->size.x = p_font_data->msdf_source_size; + } else if (p_font_data->oversampling <= 0.0f) { + fd->oversampling = TS->font_get_global_oversampling(); + } else { + fd->oversampling = p_font_data->oversampling; + } + + if (FT_HAS_COLOR(fd->face) && fd->face->num_fixed_sizes > 0) { + int best_match = 0; + int diff = ABS(fd->size.x - ((int64_t)fd->face->available_sizes[0].width)); + fd->scale = real_t(fd->size.x * fd->oversampling) / fd->face->available_sizes[0].width; + for (int i = 1; i < fd->face->num_fixed_sizes; i++) { + int ndiff = ABS(fd->size.x - ((int64_t)fd->face->available_sizes[i].width)); + if (ndiff < diff) { + best_match = i; + diff = ndiff; + fd->scale = real_t(fd->size.x * fd->oversampling) / fd->face->available_sizes[i].width; + } + } + FT_Select_Size(fd->face, best_match); + } else { + FT_Set_Pixel_Sizes(fd->face, 0, fd->size.x * fd->oversampling); + } + + fd->ascent = (fd->face->size->metrics.ascender / 64.0) / fd->oversampling * fd->scale; + fd->descent = (-fd->face->size->metrics.descender / 64.0) / fd->oversampling * fd->scale; + fd->underline_position = (-FT_MulFix(fd->face->underline_position, fd->face->size->metrics.y_scale) / 64.0) / fd->oversampling * fd->scale; + fd->underline_thickness = (FT_MulFix(fd->face->underline_thickness, fd->face->size->metrics.y_scale) / 64.0) / fd->oversampling * fd->scale; + + if (!p_font_data->face_init) { + // Read OpenType variations. + p_font_data->supported_varaitions.clear(); + if (fd->face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { + FT_MM_Var *amaster; + FT_Get_MM_Var(fd->face, &amaster); + for (FT_UInt i = 0; i < amaster->num_axis; i++) { + p_font_data->supported_varaitions[(int32_t)amaster->axis[i].tag] = Vector3i(amaster->axis[i].minimum / 65536, amaster->axis[i].maximum / 65536, amaster->axis[i].def / 65536); + } + FT_Done_MM_Var(library, amaster); + } + p_font_data->face_init = true; + } + + // Write variations. + if (fd->face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { + FT_MM_Var *amaster; + + FT_Get_MM_Var(fd->face, &amaster); + + Vector<FT_Fixed> coords; + coords.resize(amaster->num_axis); + + FT_Get_Var_Design_Coordinates(fd->face, coords.size(), coords.ptrw()); + + for (FT_UInt i = 0; i < amaster->num_axis; i++) { + // Reset to default. + int32_t var_tag = amaster->axis[i].tag; + real_t var_value = (double)amaster->axis[i].def / 65536.f; + coords.write[i] = amaster->axis[i].def; + + if (p_font_data->variation_coordinates.has(var_tag)) { + var_value = p_font_data->variation_coordinates[var_tag]; + coords.write[i] = CLAMP(var_value * 65536.f, amaster->axis[i].minimum, amaster->axis[i].maximum); + } + + if (p_font_data->variation_coordinates.has(tag_to_name(var_tag))) { + var_value = p_font_data->variation_coordinates[tag_to_name(var_tag)]; + coords.write[i] = CLAMP(var_value * 65536.f, amaster->axis[i].minimum, amaster->axis[i].maximum); + } + } + + FT_Set_Var_Design_Coordinates(fd->face, coords.size(), coords.ptrw()); + FT_Done_MM_Var(library, amaster); + } +#else + ERR_FAIL_V_MSG(false, TTR("FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); #endif - } else { - return RID(); } + p_font_data->cache[p_size] = fd; + return true; +} - Error err = fd->load_from_memory(p_data, p_size, p_base_size); - if (err != OK) { - memdelete(fd); - return RID(); +_FORCE_INLINE_ void TextServerFallback::_font_clear_cache(FontDataFallback *p_font_data) { + for (const Map<Vector2i, FontDataForSizeFallback *>::Element *E = p_font_data->cache.front(); E; E = E->next()) { + memdelete(E->get()); } + p_font_data->cache.clear(); + p_font_data->face_init = false; + p_font_data->supported_varaitions.clear(); +} + +RID TextServerFallback::create_font() { + FontDataFallback *fd = memnew(FontDataFallback); + return font_owner.make_rid(fd); } -RID TextServerFallback::create_font_bitmap(float p_height, float p_ascent, int p_base_size) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = memnew(BitmapFontDataFallback); - Error err = fd->bitmap_new(p_height, p_ascent, p_base_size); - if (err != OK) { - memdelete(fd); - return RID(); +void TextServerFallback::font_set_data(RID p_font_rid, const PackedByteArray &p_data) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + _font_clear_cache(fd); + fd->data = p_data; + fd->data_ptr = fd->data.ptr(); + fd->data_size = fd->data.size(); +} + +void TextServerFallback::font_set_data_ptr(RID p_font_rid, const uint8_t *p_data_ptr, size_t p_data_size) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + _font_clear_cache(fd); + fd->data.clear(); + fd->data_ptr = p_data_ptr; + fd->data_size = p_data_size; +} + +void TextServerFallback::font_set_antialiased(RID p_font_rid, bool p_antialiased) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->antialiased != p_antialiased) { + _font_clear_cache(fd); + fd->antialiased = p_antialiased; } +} - return font_owner.make_rid(fd); +bool TextServerFallback::font_is_antialiased(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->antialiased; } -void TextServerFallback::font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +void TextServerFallback::font_set_multichannel_signed_distance_field(RID p_font_rid, bool p_msdf) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->bitmap_add_texture(p_texture); + + MutexLock lock(fd->mutex); + if (fd->msdf != p_msdf) { + _font_clear_cache(fd); + fd->msdf = p_msdf; + } } -void TextServerFallback::font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +bool TextServerFallback::font_is_multichannel_signed_distance_field(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->msdf; +} + +void TextServerFallback::font_set_msdf_pixel_range(RID p_font_rid, int p_msdf_pixel_range) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->bitmap_add_char(p_char, p_texture_idx, p_rect, p_align, p_advance); + + MutexLock lock(fd->mutex); + if (fd->msdf_range != p_msdf_pixel_range) { + _font_clear_cache(fd); + fd->msdf_range = p_msdf_pixel_range; + } } -void TextServerFallback::font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +int TextServerFallback::font_get_msdf_pixel_range(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->msdf_range; +} + +void TextServerFallback::font_set_msdf_size(RID p_font_rid, int p_msdf_size) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->bitmap_add_kerning_pair(p_A, p_B, p_kerning); + + MutexLock lock(fd->mutex); + if (fd->msdf_source_size != p_msdf_size) { + _font_clear_cache(fd); + fd->msdf_source_size = p_msdf_size; + } } -float TextServerFallback::font_get_height(RID p_font, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); +int TextServerFallback::font_get_msdf_size(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->msdf_source_size; +} + +void TextServerFallback::font_set_fixed_size(RID p_font_rid, int p_fixed_size) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->fixed_size != p_fixed_size) { + fd->fixed_size = p_fixed_size; + } +} + +int TextServerFallback::font_get_fixed_size(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->fixed_size; +} + +void TextServerFallback::font_set_force_autohinter(RID p_font_rid, bool p_force_autohinter) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->force_autohinter != p_force_autohinter) { + _font_clear_cache(fd); + fd->force_autohinter = p_force_autohinter; + } +} + +bool TextServerFallback::font_is_force_autohinter(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->force_autohinter; +} + +void TextServerFallback::font_set_hinting(RID p_font_rid, TextServer::Hinting p_hinting) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->hinting != p_hinting) { + _font_clear_cache(fd); + fd->hinting = p_hinting; + } +} + +TextServer::Hinting TextServerFallback::font_get_hinting(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, HINTING_NONE); + + MutexLock lock(fd->mutex); + return fd->hinting; +} + +void TextServerFallback::font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->variation_coordinates != p_variation_coordinates) { + _font_clear_cache(fd); + fd->variation_coordinates = p_variation_coordinates; + } +} + +Dictionary TextServerFallback::font_get_variation_coordinates(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Dictionary()); + + MutexLock lock(fd->mutex); + return fd->variation_coordinates; +} + +void TextServerFallback::font_set_oversampling(RID p_font_rid, real_t p_oversampling) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->oversampling != p_oversampling) { + _font_clear_cache(fd); + fd->oversampling = p_oversampling; + } +} + +real_t TextServerFallback::font_get_oversampling(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_height(p_size); + + MutexLock lock(fd->mutex); + return fd->oversampling; } -float TextServerFallback::font_get_ascent(RID p_font, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); +Array TextServerFallback::font_get_size_cache_list(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Array()); + + MutexLock lock(fd->mutex); + Array ret; + for (const Map<Vector2i, FontDataForSizeFallback *>::Element *E = fd->cache.front(); E; E = E->next()) { + ret.push_back(E->key()); + } + return ret; +} + +void TextServerFallback::font_clear_size_cache(RID p_font_rid) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + for (const Map<Vector2i, FontDataForSizeFallback *>::Element *E = fd->cache.front(); E; E = E->next()) { + memdelete(E->get()); + } + fd->cache.clear(); +} + +void TextServerFallback::font_remove_size_cache(RID p_font_rid, const Vector2i &p_size) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->cache.has(p_size)) { + memdelete(fd->cache[p_size]); + fd->cache.erase(p_size); + } +} + +void TextServerFallback::font_set_ascent(RID p_font_rid, int p_size, real_t p_ascent) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->ascent = p_ascent; +} + +real_t TextServerFallback::font_get_ascent(RID p_font_rid, int p_size) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_ascent(p_size); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.f); + + if (fd->msdf) { + return fd->cache[size]->ascent * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->ascent; + } } -float TextServerFallback::font_get_descent(RID p_font, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); +void TextServerFallback::font_set_descent(RID p_font_rid, int p_size, real_t p_descent) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->descent = p_descent; +} + +real_t TextServerFallback::font_get_descent(RID p_font_rid, int p_size) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_descent(p_size); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.f); + + if (fd->msdf) { + return fd->cache[size]->descent * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->descent; + } } -float TextServerFallback::font_get_underline_position(RID p_font, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); +void TextServerFallback::font_set_underline_position(RID p_font_rid, int p_size, real_t p_underline_position) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->underline_position = p_underline_position; +} + +real_t TextServerFallback::font_get_underline_position(RID p_font_rid, int p_size) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_underline_position(p_size); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.f); + + if (fd->msdf) { + return fd->cache[size]->underline_position * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->underline_position; + } } -float TextServerFallback::font_get_underline_thickness(RID p_font, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); +void TextServerFallback::font_set_underline_thickness(RID p_font_rid, int p_size, real_t p_underline_thickness) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->underline_thickness = p_underline_thickness; +} + +real_t TextServerFallback::font_get_underline_thickness(RID p_font_rid, int p_size) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_underline_thickness(p_size); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.f); + + if (fd->msdf) { + return fd->cache[size]->underline_thickness * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->underline_thickness; + } } -int TextServerFallback::font_get_spacing_space(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, 0); - return fd->get_spacing_space(); +void TextServerFallback::font_set_scale(RID p_font_rid, int p_size, real_t p_scale) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->scale = p_scale; } -void TextServerFallback::font_set_spacing_space(RID p_font, int p_value) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +real_t TextServerFallback::font_get_scale(RID p_font_rid, int p_size) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, 0.f); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.f); + + if (fd->msdf) { + return fd->cache[size]->scale * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->scale / fd->cache[size]->oversampling; + } +} + +void TextServerFallback::font_set_spacing(RID p_font_rid, int p_size, TextServer::SpacingType p_spacing, int p_value) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_spacing_space(p_value); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + switch (p_spacing) { + case TextServer::SPACING_GLYPH: { + fd->cache[size]->spacing_glyph = p_value; + } break; + case TextServer::SPACING_SPACE: { + fd->cache[size]->spacing_space = p_value; + } break; + default: { + ERR_FAIL_MSG("Invalid spacing type: " + itos(p_spacing)); + } break; + } } -int TextServerFallback::font_get_spacing_glyph(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); +int TextServerFallback::font_get_spacing(RID p_font_rid, int p_size, TextServer::SpacingType p_spacing) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, 0); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + + switch (p_spacing) { + case TextServer::SPACING_GLYPH: { + if (fd->msdf) { + return fd->cache[size]->spacing_glyph * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->spacing_glyph; + } + } break; + case TextServer::SPACING_SPACE: { + if (fd->msdf) { + return fd->cache[size]->spacing_space * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->spacing_space; + } + } break; + default: { + ERR_FAIL_V_MSG(0, "Invalid spacing type: " + itos(p_spacing)); + } break; + } + return 0; +} + +int TextServerFallback::font_get_texture_count(RID p_font_rid, const Vector2i &p_size) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, 0); - return fd->get_spacing_glyph(); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + + return fd->cache[size]->textures.size(); } -void TextServerFallback::font_set_spacing_glyph(RID p_font, int p_value) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +void TextServerFallback::font_clear_textures(RID p_font_rid, const Vector2i &p_size) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_spacing_glyph(p_value); + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->textures.clear(); } -void TextServerFallback::font_set_antialiased(RID p_font, bool p_antialiased) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +void TextServerFallback::font_remove_texture(RID p_font_rid, const Vector2i &p_size, int p_texture_index) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_antialiased(p_antialiased); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size()); + + fd->cache[size]->textures.remove(p_texture_index); } -bool TextServerFallback::font_get_antialiased(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, false); - return fd->get_antialiased(); +void TextServerFallback::font_set_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + ERR_FAIL_COND(p_image.is_null()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + ERR_FAIL_COND(p_texture_index < 0); + if (p_texture_index >= fd->cache[size]->textures.size()) { + fd->cache[size]->textures.resize(p_texture_index + 1); + } + + FontTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + + tex.imgdata = p_image->get_data(); + tex.texture_w = p_image->get_width(); + tex.texture_h = p_image->get_height(); + tex.format = p_image->get_format(); + + Ref<Image> img = memnew(Image(tex.texture_w, tex.texture_h, 0, tex.format, tex.imgdata)); + tex.texture = Ref<ImageTexture>(); + tex.texture.instantiate(); + tex.texture->create_from_image(img); } -void TextServerFallback::font_set_distance_field_hint(RID p_font, bool p_distance_field) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +Ref<Image> TextServerFallback::font_get_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Ref<Image>()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>()); + ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>()); + + const FontTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + Ref<Image> img = memnew(Image(tex.texture_w, tex.texture_h, 0, tex.format, tex.imgdata)); + + return img; +} + +void TextServerFallback::font_set_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_distance_field_hint(p_distance_field); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + if (p_texture_index >= fd->cache[size]->textures.size()) { + fd->cache[size]->textures.resize(p_texture_index + 1); + } + + FontTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + tex.offsets = p_offset; } -bool TextServerFallback::font_get_distance_field_hint(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, false); - return fd->get_distance_field_hint(); +PackedInt32Array TextServerFallback::font_get_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, PackedInt32Array()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array()); + ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array()); + + const FontTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + return tex.offsets; } -void TextServerFallback::font_set_hinting(RID p_font, TextServer::Hinting p_hinting) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +Array TextServerFallback::font_get_glyph_list(RID p_font_rid, const Vector2i &p_size) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Array()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Array()); + + Array ret; + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + const int32_t *E = nullptr; + while ((E = gl.next(E))) { + ret.push_back(*E); + } + return ret; +} + +void TextServerFallback::font_clear_glyphs(RID p_font_rid, const Vector2i &p_size) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_hinting(p_hinting); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + fd->cache[size]->glyph_map.clear(); } -TextServer::Hinting TextServerFallback::font_get_hinting(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, TextServer::HINTING_NONE); - return fd->get_hinting(); +void TextServerFallback::font_remove_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + fd->cache[size]->glyph_map.erase(p_glyph); } -void TextServerFallback::font_set_force_autohinter(RID p_font, bool p_enabeld) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +Vector2 TextServerFallback::font_get_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Vector2()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + if (!_ensure_glyph(fd, size, p_glyph)) { + return Vector2(); // Invalid or non graphicl glyph, do not display errors. + } + + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + if (fd->msdf) { + return gl[p_glyph].advance * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return gl[p_glyph].advance; + } +} + +void TextServerFallback::font_set_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph, const Vector2 &p_advance) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->set_force_autohinter(p_enabeld); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + gl[p_glyph].advance = p_advance; + gl[p_glyph].found = true; } -bool TextServerFallback::font_get_force_autohinter(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); +Vector2 TextServerFallback::font_get_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Vector2()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + if (!_ensure_glyph(fd, size, p_glyph)) { + return Vector2(); // Invalid or non graphicl glyph, do not display errors. + } + + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + if (fd->msdf) { + return gl[p_glyph].rect.position * (real_t)p_size.x / (real_t)fd->msdf_source_size; + } else { + return gl[p_glyph].rect.position; + } +} + +void TextServerFallback::font_set_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + gl[p_glyph].rect.position = p_offset; + gl[p_glyph].found = true; +} + +Vector2 TextServerFallback::font_get_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Vector2()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + if (!_ensure_glyph(fd, size, p_glyph)) { + return Vector2(); // Invalid or non graphicl glyph, do not display errors. + } + + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + if (fd->msdf) { + return gl[p_glyph].rect.size * (real_t)p_size.x / (real_t)fd->msdf_source_size; + } else { + return gl[p_glyph].rect.size; + } +} + +void TextServerFallback::font_set_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + gl[p_glyph].rect.size = p_gl_size; + gl[p_glyph].found = true; +} + +Rect2 TextServerFallback::font_get_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Rect2()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2()); + if (!_ensure_glyph(fd, size, p_glyph)) { + return Rect2(); // Invalid or non graphicl glyph, do not display errors. + } + + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + return gl[p_glyph].uv_rect; +} + +void TextServerFallback::font_set_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + gl[p_glyph].uv_rect = p_uv_rect; + gl[p_glyph].found = true; +} + +int TextServerFallback::font_get_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, -1); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1); + if (!_ensure_glyph(fd, size, p_glyph)) { + return -1; // Invalid or non graphicl glyph, do not display errors. + } + + const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + return gl[p_glyph].texture_idx; +} + +void TextServerFallback::font_set_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + + HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + + gl[p_glyph].texture_idx = p_texture_idx; + gl[p_glyph].found = true; +} + +bool TextServerFallback::font_get_glyph_contours(RID p_font_rid, int p_size, int32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, false); - return fd->get_force_autohinter(); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), false); + +#ifdef MODULE_FREETYPE_ENABLED + int error = FT_Load_Glyph(fd->cache[size]->face, p_index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); + ERR_FAIL_COND_V(error, false); + + r_points.clear(); + r_contours.clear(); + + real_t h = fd->cache[size]->ascent; + real_t scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale; + if (fd->msdf) { + scale = scale * (real_t)p_size / (real_t)fd->msdf_source_size; + } + for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) { + r_points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, h - fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i]))); + } + for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) { + r_contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]); + } + r_orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); +#else + return false; +#endif + return true; } -bool TextServerFallback::font_has_char(RID p_font, char32_t p_char) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); +Array TextServerFallback::font_get_kerning_list(RID p_font_rid, int p_size) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Array()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Array()); + + Array ret; + for (const Map<Vector2i, Vector2>::Element *E = fd->cache[size]->kerning_map.front(); E; E = E->next()) { + ret.push_back(E->key()); + } + return ret; +} + +void TextServerFallback::font_clear_kerning_map(RID p_font_rid, int p_size) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->kerning_map.clear(); +} + +void TextServerFallback::font_remove_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->kerning_map.erase(p_glyph_pair); +} + +void TextServerFallback::font_set_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning; +} + +Vector2 TextServerFallback::font_get_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Vector2()); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + + const Map<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map; + + if (kern.has(p_glyph_pair)) { + if (fd->msdf) { + return kern[p_glyph_pair] * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return kern[p_glyph_pair]; + } + } else { +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face) { + FT_Vector delta; + FT_Get_Kerning(fd->cache[size]->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta); + if (fd->msdf) { + return Vector2(delta.x, delta.y) * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return Vector2(delta.x, delta.y); + } + } +#endif + } + return Vector2(); +} + +int32_t TextServerFallback::font_get_glyph_index(RID p_font_rid, int p_size, char32_t p_char, char32_t p_variation_selector) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, 0); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face) { + if (p_variation_selector) { + return FT_Face_GetCharVariantIndex(fd->cache[size]->face, p_char, p_variation_selector); + } else { + return FT_Get_Char_Index(fd->cache[size]->face, p_char); + } + } else { + return 0; + } +#else + return (int32_t)p_char; +#endif +} + +bool TextServerFallback::font_has_char(RID p_font_rid, char32_t p_char) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, false); - return fd->has_char(p_char); + + MutexLock lock(fd->mutex); + if (fd->cache.is_empty()) { + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false); + } + FontDataForSizeFallback *at_size = fd->cache.front()->get(); + +#ifdef MODULE_FREETYPE_ENABLED + if (at_size && at_size->face) { + return FT_Get_Char_Index(at_size->face, p_char) != 0; + } +#endif + return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false; } -String TextServerFallback::font_get_supported_chars(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); +String TextServerFallback::font_get_supported_chars(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, String()); - return fd->get_supported_chars(); + + MutexLock lock(fd->mutex); + if (fd->cache.is_empty()) { + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String()); + } + FontDataForSizeFallback *at_size = fd->cache.front()->get(); + + String chars; +#ifdef MODULE_FREETYPE_ENABLED + if (at_size && at_size->face) { + FT_UInt gindex; + FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex); + while (gindex != 0) { + if (charcode != 0) { + chars += char32_t(charcode); + } + charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex); + } + return chars; + } +#endif + if (at_size) { + const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map; + const int32_t *E = nullptr; + while ((E = gl.next(E))) { + chars += char32_t(*E); + } + } + return chars; } -bool TextServerFallback::font_has_outline(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, false); - return fd->has_outline(); +void TextServerFallback::font_render_range(RID p_font_rid, const Vector2i &p_size, char32_t p_start, char32_t p_end) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + for (char32_t i = p_start; i <= p_end; i++) { +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face) { + _ensure_glyph(fd, size, FT_Get_Char_Index(fd->cache[size]->face, i)); + continue; + } +#endif + _ensure_glyph(fd, size, (int32_t)i); + } } -float TextServerFallback::font_get_base_size(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, 0.f); - return fd->get_base_size(); +void TextServerFallback::font_render_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_index) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + ERR_FAIL_COND(!_ensure_glyph(fd, size, p_index)); } -bool TextServerFallback::font_is_language_supported(RID p_font, const String &p_language) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, false); - if (fd->lang_support_overrides.has(p_language)) { - return fd->lang_support_overrides[p_language]; - } else { - Vector<String> tags = p_language.replace("-", "_").split("_"); - if (tags.size() > 0) { - if (fd->lang_support_overrides.has(tags[0])) { - return fd->lang_support_overrides[tags[0]]; +void TextServerFallback::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, p_size); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + if (!_ensure_glyph(fd, size, p_index)) { + return; // // Invalid or non graphicl glyph, do not display errors, nothing to draw. + } + + const FontGlyph &gl = fd->cache[size]->glyph_map[p_index]; + if (gl.found) { + ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + + if (gl.texture_idx != -1) { + Color modulate = p_color; +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face && FT_HAS_COLOR(fd->cache[size]->face)) { + modulate.r = modulate.g = modulate.b = 1.0; + } +#endif + if (RenderingServer::get_singleton() != nullptr) { + RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + if (fd->msdf) { + Point2 cpos = p_pos; + cpos += gl.rect.position * (real_t)p_size / (real_t)fd->msdf_source_size; + Size2 csize = gl.rect.size * (real_t)p_size / (real_t)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range); + } else { + Point2i cpos = p_pos; + cpos += gl.rect.position; + Size2i csize = gl.rect.size; + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + } } } - return false; } } -void TextServerFallback::font_set_language_support_override(RID p_font, const String &p_language, bool p_supported) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +void TextServerFallback::font_draw_glyph_outline(RID p_font_rid, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->lang_support_overrides[p_language] = p_supported; + + MutexLock lock(fd->mutex); + Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + if (!_ensure_glyph(fd, size, p_index)) { + return; // // Invalid or non graphicl glyph, do not display errors, nothing to draw. + } + + const FontGlyph &gl = fd->cache[size]->glyph_map[p_index]; + if (gl.found) { + ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + + if (gl.texture_idx != -1) { + Color modulate = p_color; +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face && FT_HAS_COLOR(fd->cache[size]->face)) { + modulate.r = modulate.g = modulate.b = 1.0; + } +#endif + if (RenderingServer::get_singleton() != nullptr) { + RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + if (fd->msdf) { + Point2 cpos = p_pos; + cpos += gl.rect.position * (real_t)p_size / (real_t)fd->msdf_source_size; + Size2 csize = gl.rect.size * (real_t)p_size / (real_t)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size * 2, fd->msdf_range); + } else { + Point2i cpos = p_pos; + cpos += gl.rect.position; + Size2i csize = gl.rect.size; + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + } + } + } + } } -bool TextServerFallback::font_get_language_support_override(RID p_font, const String &p_language) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +bool TextServerFallback::font_is_language_supported(RID p_font_rid, const String &p_language) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, false); - return fd->lang_support_overrides[p_language]; + + MutexLock lock(fd->mutex); + if (fd->language_support_overrides.has(p_language)) { + return fd->language_support_overrides[p_language]; + } else { + return true; + } } -void TextServerFallback::font_remove_language_support_override(RID p_font, const String &p_language) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +void TextServerFallback::font_set_language_support_override(RID p_font_rid, const String &p_language, bool p_supported) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); - fd->lang_support_overrides.erase(p_language); + + MutexLock lock(fd->mutex); + fd->language_support_overrides[p_language] = p_supported; } -Vector<String> TextServerFallback::font_get_language_support_overrides(RID p_font) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +bool TextServerFallback::font_get_language_support_override(RID p_font_rid, const String &p_language) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->language_support_overrides[p_language]; +} + +void TextServerFallback::font_remove_language_support_override(RID p_font_rid, const String &p_language) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + fd->language_support_overrides.erase(p_language); +} + +Vector<String> TextServerFallback::font_get_language_support_overrides(RID p_font_rid) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, Vector<String>()); - Vector<String> ret; - for (Map<String, bool>::Element *E = fd->lang_support_overrides.front(); E; E = E->next()) { - ret.push_back(E->key()); + + MutexLock lock(fd->mutex); + Vector<String> out; + for (const Map<String, bool>::Element *E = fd->language_support_overrides.front(); E; E = E->next()) { + out.push_back(E->key()); } - return ret; + return out; } -bool TextServerFallback::font_is_script_supported(RID p_font, const String &p_script) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); +bool TextServerFallback::font_is_script_supported(RID p_font_rid, const String &p_script) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); if (fd->script_support_overrides.has(p_script)) { return fd->script_support_overrides[p_script]; } else { @@ -392,95 +1890,84 @@ bool TextServerFallback::font_is_script_supported(RID p_font, const String &p_sc } } -void TextServerFallback::font_set_script_support_override(RID p_font, const String &p_script, bool p_supported) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +void TextServerFallback::font_set_script_support_override(RID p_font_rid, const String &p_script, bool p_supported) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); fd->script_support_overrides[p_script] = p_supported; } -bool TextServerFallback::font_get_script_support_override(RID p_font, const String &p_script) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +bool TextServerFallback::font_get_script_support_override(RID p_font_rid, const String &p_script) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); return fd->script_support_overrides[p_script]; } -void TextServerFallback::font_remove_script_support_override(RID p_font, const String &p_script) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +void TextServerFallback::font_remove_script_support_override(RID p_font_rid, const String &p_script) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); fd->script_support_overrides.erase(p_script); } -Vector<String> TextServerFallback::font_get_script_support_overrides(RID p_font) { - _THREAD_SAFE_METHOD_ - FontDataFallback *fd = font_owner.getornull(p_font); +Vector<String> TextServerFallback::font_get_script_support_overrides(RID p_font_rid) { + FontDataFallback *fd = font_owner.getornull(p_font_rid); ERR_FAIL_COND_V(!fd, Vector<String>()); - Vector<String> ret; - for (Map<String, bool>::Element *E = fd->script_support_overrides.front(); E; E = E->next()) { - ret.push_back(E->key()); - } - return ret; -} -uint32_t TextServerFallback::font_get_glyph_index(RID p_font, char32_t p_char, char32_t p_variation_selector) const { - return (uint32_t)p_char; -} - -Vector2 TextServerFallback::font_get_glyph_advance(RID p_font, uint32_t p_index, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, Vector2()); - return fd->get_advance(p_index, p_size); + MutexLock lock(fd->mutex); + Vector<String> out; + for (const Map<String, bool>::Element *E = fd->script_support_overrides.front(); E; E = E->next()) { + out.push_back(E->key()); + } + return out; } -Vector2 TextServerFallback::font_get_glyph_kerning(RID p_font, uint32_t p_index_a, uint32_t p_index_b, int p_size) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, Vector2()); - return fd->get_kerning(p_index_a, p_index_b, p_size); +Dictionary TextServerFallback::font_supported_feature_list(RID p_font_rid) const { + return Dictionary(); } -Vector2 TextServerFallback::font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, Vector2()); - return fd->draw_glyph(p_canvas, p_size, p_pos, p_index, p_color); -} +Dictionary TextServerFallback::font_supported_variation_list(RID p_font_rid) const { + FontDataFallback *fd = font_owner.getornull(p_font_rid); + ERR_FAIL_COND_V(!fd, Dictionary()); -Vector2 TextServerFallback::font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, Vector2()); - return fd->draw_glyph_outline(p_canvas, p_size, p_outline_size, p_pos, p_index, p_color); + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + return fd->supported_varaitions; } -bool TextServerFallback::font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const { - _THREAD_SAFE_METHOD_ - const FontDataFallback *fd = font_owner.getornull(p_font); - ERR_FAIL_COND_V(!fd, false); - return fd->get_glyph_contours(p_size, p_index, r_points, r_contours, r_orientation); -} - -float TextServerFallback::font_get_oversampling() const { +real_t TextServerFallback::font_get_global_oversampling() const { return oversampling; } -void TextServerFallback::font_set_oversampling(float p_oversampling) { +void TextServerFallback::font_set_global_oversampling(real_t p_oversampling) { _THREAD_SAFE_METHOD_ if (oversampling != p_oversampling) { oversampling = p_oversampling; List<RID> fonts; font_owner.get_owned_list(&fonts); + bool font_cleared = false; for (const RID &E : fonts) { - font_owner.getornull(E)->clear_cache(); + if (!font_is_multichannel_signed_distance_field(E) && font_get_oversampling(E) <= 0) { + font_clear_size_cache(E); + font_cleared = true; + } } - } -} -Vector<String> TextServerFallback::get_system_fonts() const { - return Vector<String>(); + if (font_cleared) { + List<RID> text_bufs; + shaped_owner.get_owned_list(&text_bufs); + for (const RID &E : text_bufs) { + invalidate(shaped_owner.getornull(E)); + } + } + } } /*************************************************************************/ @@ -533,10 +2020,10 @@ RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, Te } void TextServerFallback::shaped_text_clear(RID p_shaped) { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND(!sd); + MutexLock lock(sd->mutex); sd->parent = RID(); sd->start = 0; sd->end = 0; @@ -557,9 +2044,10 @@ TextServer::Direction TextServerFallback::shaped_text_get_direction(RID p_shaped } void TextServerFallback::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND(!sd); + + MutexLock lock(sd->mutex); if (sd->orientation != p_orientation) { if (sd->parent != RID()) { full_copy(sd); @@ -574,15 +2062,17 @@ void TextServerFallback::shaped_text_set_bidi_override(RID p_shaped, const Vecto } TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL); + + MutexLock lock(sd->mutex); return sd->orientation; } void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_enabled) { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); + + MutexLock lock(sd->mutex); ERR_FAIL_COND(!sd); if (sd->preserve_invalid != p_enabled) { if (sd->parent != RID()) { @@ -594,16 +2084,18 @@ void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e } bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); return sd->preserve_invalid; } void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND(!sd); + + MutexLock lock(sd->mutex); if (sd->preserve_control != p_enabled) { if (sd->parent != RID()) { full_copy(sd); @@ -614,18 +2106,24 @@ void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_e } bool TextServerFallback::shaped_text_get_preserve_control(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); return sd->preserve_control; } bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); ERR_FAIL_COND_V(p_size <= 0, false); + for (int i = 0; i < p_fonts.size(); i++) { + ERR_FAIL_COND_V(!font_owner.getornull(p_fonts[i]), false); + } + if (p_text.is_empty()) { return true; } @@ -637,6 +2135,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te ShapedTextData::Span span; span.start = sd->text.length(); span.end = span.start + p_text.length(); + // Pre-sort fonts, push fonts with the language support first. Vector<RID> fonts_no_match; int font_count = p_fonts.size(); @@ -662,9 +2161,10 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te } bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align, int p_length) { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); ERR_FAIL_COND_V(p_key == Variant(), false); ERR_FAIL_COND_V(sd->objects.has(p_key), false); @@ -692,9 +2192,10 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con } bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align) { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), false); sd->objects[p_key].rect.size = p_size; sd->objects[p_key].inline_align = p_inline_align; @@ -706,8 +2207,6 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, sd->upos = 0; sd->uthk = 0; int sd_size = sd->glyphs.size(); - const FontDataFallback *fd = nullptr; - RID prev_rid = RID(); for (int i = 0; i < sd_size; i++) { Glyph gl = sd->glyphs[i]; @@ -731,17 +2230,13 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, sd->glyphs.write[i].advance = sd->objects[key].rect.size.y; } } else { - if (prev_rid != gl.font_rid) { - fd = font_owner.getornull(gl.font_rid); - prev_rid = gl.font_rid; - } - if (fd != nullptr) { + if (gl.font_rid.is_valid()) { if (sd->orientation == ORIENTATION_HORIZONTAL) { - sd->ascent = MAX(sd->ascent, fd->get_ascent(gl.font_size)); - sd->descent = MAX(sd->descent, fd->get_descent(gl.font_size)); + sd->ascent = MAX(sd->ascent, font_get_ascent(gl.font_rid, gl.font_size)); + sd->descent = MAX(sd->descent, font_get_descent(gl.font_rid, gl.font_size)); } else { - sd->ascent = MAX(sd->ascent, Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5)); - sd->descent = MAX(sd->descent, Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5)); + sd->ascent = MAX(sd->ascent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5)); + sd->descent = MAX(sd->descent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5)); } sd->upos = MAX(sd->upos, font_get_underline_position(gl.font_rid, gl.font_size)); sd->uthk = MAX(sd->uthk, font_get_underline_thickness(gl.font_rid, gl.font_size)); @@ -760,8 +2255,8 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, } // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; + real_t full_ascent = sd->ascent; + real_t full_descent = sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) { if ((E->get().pos >= sd->start) && (E->get().pos < sd->end)) { if (sd->orientation == ORIENTATION_HORIZONTAL) { @@ -830,9 +2325,10 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, } RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_length) const { - _THREAD_SAFE_METHOD_ const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, RID()); + + MutexLock lock(sd->mutex); if (sd->parent != RID()) { return shaped_text_substr(sd->parent, p_start, p_length); } @@ -886,14 +2382,13 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng new_sd->width += new_sd->objects[key].rect.size.y; } } else { - const FontDataFallback *fd = font_owner.getornull(gl.font_rid); - if (fd != nullptr) { + if (gl.font_rid.is_valid()) { if (new_sd->orientation == ORIENTATION_HORIZONTAL) { - new_sd->ascent = MAX(new_sd->ascent, fd->get_ascent(gl.font_size)); - new_sd->descent = MAX(new_sd->descent, fd->get_descent(gl.font_size)); + new_sd->ascent = MAX(new_sd->ascent, font_get_ascent(gl.font_rid, gl.font_size)); + new_sd->descent = MAX(new_sd->descent, font_get_descent(gl.font_rid, gl.font_size)); } else { - new_sd->ascent = MAX(new_sd->ascent, Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5)); - new_sd->descent = MAX(new_sd->descent, Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5)); + new_sd->ascent = MAX(new_sd->ascent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5)); + new_sd->descent = MAX(new_sd->descent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5)); } } else if (new_sd->preserve_invalid || (new_sd->preserve_control && is_control(gl.index))) { // Glyph not found, replace with hex code box. @@ -912,8 +2407,8 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng } // Align embedded objects to baseline. - float full_ascent = new_sd->ascent; - float full_descent = new_sd->descent; + real_t full_ascent = new_sd->ascent; + real_t full_descent = new_sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = new_sd->objects.front(); E; E = E->next()) { if ((E->get().pos >= new_sd->start) && (E->get().pos < new_sd->end)) { if (sd->orientation == ORIENTATION_HORIZONTAL) { @@ -984,16 +2479,18 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng } RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, RID()); + + MutexLock lock(sd->mutex); return sd->parent; } -float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, uint8_t /*JustificationFlag*/ p_jst_flags) { - _THREAD_SAFE_METHOD_ +real_t TextServerFallback::shaped_text_fit_to_width(RID p_shaped, real_t p_width, uint8_t /*JustificationFlag*/ p_jst_flags) { ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } @@ -1053,13 +2550,13 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, } if ((space_count > 0) && ((p_jst_flags & JUSTIFICATION_WORD_BOUND) == JUSTIFICATION_WORD_BOUND)) { - float delta_width_per_space = (p_width - sd->width) / space_count; + real_t delta_width_per_space = (p_width - sd->width) / space_count; for (int i = start_pos; i <= end_pos; i++) { Glyph &gl = sd->glyphs.write[i]; if (gl.count > 0) { if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) { - float old_adv = gl.advance; - gl.advance = Math::round(MAX(gl.advance + delta_width_per_space, 0.05 * gl.font_size)); + real_t old_adv = gl.advance; + gl.advance = MAX(gl.advance + delta_width_per_space, Math::round(0.1 * gl.font_size)); sd->width += (gl.advance - old_adv); } } @@ -1069,10 +2566,11 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, return sd->width; } -float TextServerFallback::shaped_text_tab_align(RID p_shaped, const Vector<float> &p_tab_stops) { - _THREAD_SAFE_METHOD_ +real_t TextServerFallback::shaped_text_tab_align(RID p_shaped, const Vector<real_t> &p_tab_stops) { ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } @@ -1081,7 +2579,7 @@ float TextServerFallback::shaped_text_tab_align(RID p_shaped, const Vector<float } int tab_index = 0; - float off = 0.f; + real_t off = 0.f; int start, end, delta; if (sd->para_direction == DIRECTION_LTR) { @@ -1098,7 +2596,7 @@ float TextServerFallback::shaped_text_tab_align(RID p_shaped, const Vector<float for (int i = start; i != end; i += delta) { if ((gl[i].flags & GRAPHEME_IS_TAB) == GRAPHEME_IS_TAB) { - float tab_off = 0.f; + real_t tab_off = 0.f; while (tab_off <= off) { tab_off += p_tab_stops[tab_index]; tab_index++; @@ -1106,7 +2604,7 @@ float TextServerFallback::shaped_text_tab_align(RID p_shaped, const Vector<float tab_index = 0; } } - float old_adv = gl[i].advance; + real_t old_adv = gl[i].advance; gl[i].advance = tab_off - off; sd->width += gl[i].advance - old_adv; off = 0; @@ -1119,9 +2617,10 @@ float TextServerFallback::shaped_text_tab_align(RID p_shaped, const Vector<float } bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); if (!sd->valid) { shaped_text_shape(p_shaped); } @@ -1159,9 +2658,10 @@ bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) { } bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); if (!sd->valid) { shaped_text_shape(p_shaped); } @@ -1173,10 +2673,11 @@ bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) { return true; } -void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_trim_flags) { - _THREAD_SAFE_METHOD_ +void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, real_t p_width, uint8_t p_trim_flags) { ShapedTextData *sd = shaped_owner.getornull(p_shaped_line); - ERR_FAIL_COND_MSG(!sd, "ShapedTextDataAdvanced invalid."); + ERR_FAIL_COND_MSG(!sd, "ShapedTextDataFallback invalid."); + + MutexLock lock(sd->mutex); if (!sd->valid) { shaped_text_shape(p_shaped_line); } @@ -1203,18 +2704,18 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl int sd_size = sd->glyphs.size(); RID last_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; int last_gl_font_size = sd_glyphs[sd_size - 1].font_size; - uint32_t dot_gl_idx = font_get_glyph_index(last_gl_font_rid, '.'); - Vector2 dot_adv = font_get_glyph_advance(last_gl_font_rid, dot_gl_idx, last_gl_font_size); - uint32_t whitespace_gl_idx = font_get_glyph_index(last_gl_font_rid, ' '); - Vector2 whitespace_adv = font_get_glyph_advance(last_gl_font_rid, whitespace_gl_idx, last_gl_font_size); + int32_t dot_gl_idx = font_get_glyph_index(last_gl_font_rid, '.', 0); + Vector2 dot_adv = font_get_glyph_advance(last_gl_font_rid, last_gl_font_size, dot_gl_idx); + int32_t whitespace_gl_idx = font_get_glyph_index(last_gl_font_rid, ' ', 0); + Vector2 whitespace_adv = font_get_glyph_advance(last_gl_font_rid, last_gl_font_size, whitespace_gl_idx); int ellipsis_width = 0; if (add_ellipsis) { - ellipsis_width = 3 * dot_adv.x + font_get_spacing_glyph(last_gl_font_rid) + (cut_per_word ? whitespace_adv.x : 0); + ellipsis_width = 3 * dot_adv.x + font_get_spacing(last_gl_font_rid, last_gl_font_size, TextServer::SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0); } int ell_min_characters = 6; - float width = sd->width; + real_t width = sd->width; int trim_pos = 0; int ellipsis_pos = (enforce_ellipsis) ? 0 : -1; @@ -1289,16 +2790,18 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl } TextServer::TrimData TextServerFallback::shaped_text_get_trim_data(RID p_shaped) const { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, TrimData(), "ShapedTextDataAdvanced invalid."); + ERR_FAIL_COND_V_MSG(!sd, TrimData(), "ShapedTextDataFallback invalid."); + + MutexLock lock(sd->mutex); return sd->overrun_trim_data; } bool TextServerFallback::shaped_text_shape(RID p_shaped) { - _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); if (sd->valid) { return true; } @@ -1347,14 +2850,12 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { } else { // Text span. for (int j = span.start; j < span.end; j++) { - const FontDataFallback *fd = nullptr; - Glyph gl; gl.start = j; gl.end = j + 1; gl.count = 1; gl.font_size = span.font_size; - gl.index = (uint32_t)sd->text[j]; // Use codepoint. + gl.index = (int32_t)sd->text[j]; // Use codepoint. if (gl.index == 0x0009 || gl.index == 0x000b) { gl.index = 0x0020; } @@ -1363,45 +2864,44 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { } // Select first font which has character (font are already sorted by span language). for (int k = 0; k < span.fonts.size(); k++) { - fd = font_owner.getornull(span.fonts[k]); - if (fd != nullptr && fd->has_char(gl.index)) { + if (font_has_char(span.fonts[k], gl.index)) { gl.font_rid = span.fonts[k]; break; } } - if (gl.font_rid != RID()) { + if (gl.font_rid.is_valid()) { if (sd->text[j] != 0 && !is_linebreak(sd->text[j])) { if (sd->orientation == ORIENTATION_HORIZONTAL) { - gl.advance = fd->get_advance(gl.index, gl.font_size).x; + gl.advance = font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x; gl.x_off = 0; gl.y_off = 0; - sd->ascent = MAX(sd->ascent, fd->get_ascent(gl.font_size)); - sd->descent = MAX(sd->descent, fd->get_descent(gl.font_size)); + sd->ascent = MAX(sd->ascent, font_get_ascent(gl.font_rid, gl.font_size)); + sd->descent = MAX(sd->descent, font_get_descent(gl.font_rid, gl.font_size)); } else { - gl.advance = fd->get_advance(gl.index, gl.font_size).y; - gl.x_off = -Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5); - gl.y_off = fd->get_ascent(gl.font_size); - sd->ascent = MAX(sd->ascent, Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5)); - sd->descent = MAX(sd->descent, Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5)); + gl.advance = font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).y; + gl.x_off = -Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5); + gl.y_off = font_get_ascent(gl.font_rid, gl.font_size); + sd->ascent = MAX(sd->ascent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5)); + sd->descent = MAX(sd->descent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5)); } } - if (fd->get_spacing_space() && is_whitespace(sd->text[j])) { - gl.advance += fd->get_spacing_space(); + if (font_get_spacing(gl.font_rid, gl.font_size, TextServer::SPACING_SPACE) && is_whitespace(sd->text[j])) { + gl.advance += font_get_spacing(gl.font_rid, gl.font_size, TextServer::SPACING_SPACE); } else { - gl.advance += fd->get_spacing_glyph(); + gl.advance += font_get_spacing(gl.font_rid, gl.font_size, TextServer::SPACING_GLYPH); } - sd->upos = MAX(sd->upos, fd->get_underline_position(gl.font_size)); - sd->uthk = MAX(sd->uthk, fd->get_underline_thickness(gl.font_size)); + sd->upos = MAX(sd->upos, font_get_underline_position(gl.font_rid, gl.font_size)); + sd->uthk = MAX(sd->uthk, font_get_underline_thickness(gl.font_rid, gl.font_size)); // Add kerning to previous glyph. if (sd->glyphs.size() > 0) { Glyph &prev_gl = sd->glyphs.write[sd->glyphs.size() - 1]; if (prev_gl.font_rid == gl.font_rid && prev_gl.font_size == gl.font_size) { if (sd->orientation == ORIENTATION_HORIZONTAL) { - prev_gl.advance += fd->get_kerning(prev_gl.index, gl.index, gl.font_size).x; + prev_gl.advance += font_get_kerning(gl.font_rid, gl.font_size, Vector2i(prev_gl.index, gl.index)).x; } else { - prev_gl.advance += fd->get_kerning(prev_gl.index, gl.index, gl.font_size).y; + prev_gl.advance += font_get_kerning(gl.font_rid, gl.font_size, Vector2i(prev_gl.index, gl.index)).y; } } } @@ -1424,8 +2924,8 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { } // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; + real_t full_ascent = sd->ascent; + real_t full_descent = sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) { if (sd->orientation == ORIENTATION_HORIZONTAL) { switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { @@ -1492,16 +2992,18 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { } bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); + + MutexLock lock(sd->mutex); return sd->valid; } Vector<TextServer::Glyph> TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, Vector<TextServer::Glyph>()); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } @@ -1509,16 +3011,18 @@ Vector<TextServer::Glyph> TextServerFallback::shaped_text_get_glyphs(RID p_shape } Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, Vector2i()); + + MutexLock lock(sd->mutex); return Vector2(sd->start, sd->end); } Vector<TextServer::Glyph> TextServerFallback::shaped_text_sort_logical(RID p_shaped) { - _THREAD_SAFE_METHOD_ const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, Vector<TextServer::Glyph>()); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } @@ -1527,10 +3031,11 @@ Vector<TextServer::Glyph> TextServerFallback::shaped_text_sort_logical(RID p_sha } Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const { - _THREAD_SAFE_METHOD_ Array ret; const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, ret); + + MutexLock lock(sd->mutex); for (const Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) { ret.push_back(E->key()); } @@ -1539,9 +3044,10 @@ Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const { } Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_key) const { - _THREAD_SAFE_METHOD_ const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, Rect2()); + + MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2()); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); @@ -1550,9 +3056,10 @@ Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_ke } Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const { - _THREAD_SAFE_METHOD_ const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, Size2()); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } @@ -1563,40 +3070,44 @@ Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const { } } -float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const { - _THREAD_SAFE_METHOD_ +real_t TextServerFallback::shaped_text_get_ascent(RID p_shaped) const { const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } return sd->ascent; } -float TextServerFallback::shaped_text_get_descent(RID p_shaped) const { - _THREAD_SAFE_METHOD_ +real_t TextServerFallback::shaped_text_get_descent(RID p_shaped) const { const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } return sd->descent; } -float TextServerFallback::shaped_text_get_width(RID p_shaped) const { - _THREAD_SAFE_METHOD_ +real_t TextServerFallback::shaped_text_get_width(RID p_shaped) const { const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } return sd->width; } -float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const { - _THREAD_SAFE_METHOD_ +real_t TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const { const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } @@ -1604,10 +3115,11 @@ float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const return sd->upos; } -float TextServerFallback::shaped_text_get_underline_thickness(RID p_shaped) const { - _THREAD_SAFE_METHOD_ +real_t TextServerFallback::shaped_text_get_underline_thickness(RID p_shaped) const { const ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); + + MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } @@ -1623,3 +3135,11 @@ TextServer *TextServerFallback::create_func(Error &r_error, void *p_user_data) { void TextServerFallback::register_server() { TextServerManager::register_create_function(interface_name, interface_features, create_func, nullptr); } + +TextServerFallback::TextServerFallback(){}; + +TextServerFallback::~TextServerFallback() { + if (library != nullptr) { + FT_Done_FreeType(library); + } +}; diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index d4cab2409a..fde75e7135 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -39,22 +39,165 @@ #include "servers/text_server.h" #include "core/templates/rid_owner.h" - +#include "core/templates/thread_work_pool.h" #include "scene/resources/texture.h" -#include "font_fb.h" +#include "modules/modules_enabled.gen.h" + +#ifdef MODULE_FREETYPE_ENABLED +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_TRUETYPE_TABLES_H +#include FT_STROKER_H +#include FT_ADVANCES_H +#include FT_MULTIPLE_MASTERS_H +#include FT_BBOX_H +#endif class TextServerFallback : public TextServer { GDCLASS(TextServerFallback, TextServer); _THREAD_SAFE_CLASS_ - float oversampling = 1.f; - mutable RID_PtrOwner<FontDataFallback> font_owner; - mutable RID_PtrOwner<ShapedTextData> shaped_owner; - static String interface_name; static uint32_t interface_features; + // Font cache data. + +#ifdef MODULE_FREETYPE_ENABLED + mutable FT_Library library = nullptr; +#endif + + const int rect_range = 2; + + struct FontTexture { + Image::Format format; + PackedByteArray imgdata; + int texture_w = 0; + int texture_h = 0; + PackedInt32Array offsets; + Ref<ImageTexture> texture; + }; + + struct FontTexturePosition { + int index = 0; + int x = 0; + int y = 0; + }; + + struct FontGlyph { + bool found = false; + int texture_idx = -1; + Rect2 rect; + Rect2 uv_rect; + Vector2 advance; + }; + + struct FontDataForSizeFallback { + real_t ascent = 0.f; + real_t descent = 0.f; + real_t underline_position = 0.f; + real_t underline_thickness = 0.f; + real_t scale = 1.f; + real_t oversampling = 1.f; + + int spacing_glyph = 0; + int spacing_space = 0; + + Vector2i size; + + Vector<FontTexture> textures; + HashMap<int32_t, FontGlyph> glyph_map; + Map<Vector2i, Vector2> kerning_map; + +#ifdef MODULE_FREETYPE_ENABLED + FT_Face face = nullptr; + FT_StreamRec stream; +#endif + + ~FontDataForSizeFallback() { +#ifdef MODULE_FREETYPE_ENABLED + if (face != nullptr) { + FT_Done_Face(face); + } +#endif + } + }; + + struct FontDataFallback { + Mutex mutex; + + bool antialiased = true; + bool msdf = false; + int msdf_range = 14; + int msdf_source_size = 48; + int fixed_size = 0; + bool force_autohinter = false; + TextServer::Hinting hinting = TextServer::HINTING_LIGHT; + Dictionary variation_coordinates; + real_t oversampling = 0.f; + + Map<Vector2i, FontDataForSizeFallback *> cache; + + bool face_init = false; + Dictionary supported_varaitions; + + // Language/script support override. + Map<String, bool> language_support_overrides; + Map<String, bool> script_support_overrides; + + PackedByteArray data; + const uint8_t *data_ptr; + size_t data_size; + + mutable ThreadWorkPool work_pool; + + ~FontDataFallback() { + work_pool.finish(); + for (const Map<Vector2i, FontDataForSizeFallback *>::Element *E = cache.front(); E; E = E->next()) { + memdelete(E->get()); + } + cache.clear(); + } + }; + + _FORCE_INLINE_ FontTexturePosition find_texture_pos_for_glyph(FontDataForSizeFallback *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) const; +#ifdef MODULE_MSDFGEN_ENABLED + _FORCE_INLINE_ FontGlyph rasterize_msdf(FontDataFallback *p_font_data, FontDataForSizeFallback *p_data, int p_pixel_range, int p_rect_margin, FT_Outline *outline, const Vector2 &advance) const; +#endif +#ifdef MODULE_FREETYPE_ENABLED + _FORCE_INLINE_ FontGlyph rasterize_bitmap(FontDataForSizeFallback *p_data, int p_rect_margin, FT_Bitmap bitmap, int yofs, int xofs, const Vector2 &advance) const; +#endif + _FORCE_INLINE_ bool _ensure_glyph(FontDataFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const; + _FORCE_INLINE_ bool _ensure_cache_for_size(FontDataFallback *p_font_data, const Vector2i &p_size) const; + _FORCE_INLINE_ void _font_clear_cache(FontDataFallback *p_font_data); + void _generateMTSDF_threaded(uint32_t y, void *p_td) const; + + _FORCE_INLINE_ Vector2i _get_size(const FontDataFallback *p_font_data, int p_size) const { + if (p_font_data->msdf) { + return Vector2i(p_font_data->msdf_source_size, 0); + } else if (p_font_data->fixed_size > 0) { + return Vector2i(p_font_data->fixed_size, 0); + } else { + return Vector2i(p_size, 0); + } + } + + _FORCE_INLINE_ Vector2i _get_size_outline(const FontDataFallback *p_font_data, const Vector2i &p_size) const { + if (p_font_data->msdf) { + return Vector2i(p_font_data->msdf_source_size, 0); + } else if (p_font_data->fixed_size > 0) { + return Vector2i(p_font_data->fixed_size, MIN(p_size.y, 1)); + } else { + return p_size; + } + } + + // Common data. + + real_t oversampling = 1.f; + mutable RID_PtrOwner<FontDataFallback> font_owner; + mutable RID_PtrOwner<ShapedTextData> shaped_owner; + protected: static void _bind_methods(){}; @@ -77,72 +220,130 @@ public: virtual bool is_locale_right_to_left(const String &p_locale) override; + virtual int32_t name_to_tag(const String &p_name) const override; + virtual String tag_to_name(int32_t p_tag) const override; + /* Font interface */ - virtual RID create_font_system(const String &p_name, int p_base_size = 16) override; - virtual RID create_font_resource(const String &p_filename, int p_base_size = 16) override; - virtual RID create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size = 16) override; - virtual RID create_font_bitmap(float p_height, float p_ascent, int p_base_size = 16) override; + virtual RID create_font() override; + + virtual void font_set_data(RID p_font_rid, const PackedByteArray &p_data) override; + virtual void font_set_data_ptr(RID p_font_rid, const uint8_t *p_data_ptr, size_t p_data_size) override; + + virtual void font_set_antialiased(RID p_font_rid, bool p_antialiased) override; + virtual bool font_is_antialiased(RID p_font_rid) const override; + + virtual void font_set_multichannel_signed_distance_field(RID p_font_rid, bool p_msdf) override; + virtual bool font_is_multichannel_signed_distance_field(RID p_font_rid) const override; + + virtual void font_set_msdf_pixel_range(RID p_font_rid, int p_msdf_pixel_range) override; + virtual int font_get_msdf_pixel_range(RID p_font_rid) const override; + + virtual void font_set_msdf_size(RID p_font_rid, int p_msdf_size) override; + virtual int font_get_msdf_size(RID p_font_rid) const override; + + virtual void font_set_fixed_size(RID p_font_rid, int p_fixed_size) override; + virtual int font_get_fixed_size(RID p_font_rid) const override; + + virtual void font_set_force_autohinter(RID p_font_rid, bool p_force_autohinter) override; + virtual bool font_is_force_autohinter(RID p_font_rid) const override; + + virtual void font_set_hinting(RID p_font_rid, TextServer::Hinting p_hinting) override; + virtual TextServer::Hinting font_get_hinting(RID p_font_rid) const override; + + virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override; + virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override; + + virtual void font_set_oversampling(RID p_font_rid, real_t p_oversampling) override; + virtual real_t font_get_oversampling(RID p_font_rid) const override; + + virtual Array font_get_size_cache_list(RID p_font_rid) const override; + virtual void font_clear_size_cache(RID p_font_rid) override; + virtual void font_remove_size_cache(RID p_font_rid, const Vector2i &p_size) override; + + virtual void font_set_ascent(RID p_font_rid, int p_size, real_t p_ascent) override; + virtual real_t font_get_ascent(RID p_font_rid, int p_size) const override; + + virtual void font_set_descent(RID p_font_rid, int p_size, real_t p_descent) override; + virtual real_t font_get_descent(RID p_font_rid, int p_size) const override; + + virtual void font_set_underline_position(RID p_font_rid, int p_size, real_t p_underline_position) override; + virtual real_t font_get_underline_position(RID p_font_rid, int p_size) const override; + + virtual void font_set_underline_thickness(RID p_font_rid, int p_size, real_t p_underline_thickness) override; + virtual real_t font_get_underline_thickness(RID p_font_rid, int p_size) const override; + + virtual void font_set_scale(RID p_font_rid, int p_size, real_t p_scale) override; + virtual real_t font_get_scale(RID p_font_rid, int p_size) const override; + + virtual void font_set_spacing(RID p_font_rid, int p_size, SpacingType p_spacing, int p_value) override; + virtual int font_get_spacing(RID p_font_rid, int p_size, SpacingType p_spacing) const override; + + virtual int font_get_texture_count(RID p_font_rid, const Vector2i &p_size) const override; + virtual void font_clear_textures(RID p_font_rid, const Vector2i &p_size) override; + virtual void font_remove_texture(RID p_font_rid, const Vector2i &p_size, int p_texture_index) override; + + virtual void font_set_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) override; + virtual Ref<Image> font_get_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const override; - virtual void font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) override; - virtual void font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) override; - virtual void font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) override; + virtual void font_set_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) override; + virtual PackedInt32Array font_get_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const override; - virtual float font_get_height(RID p_font, int p_size) const override; - virtual float font_get_ascent(RID p_font, int p_size) const override; - virtual float font_get_descent(RID p_font, int p_size) const override; + virtual Array font_get_glyph_list(RID p_font_rid, const Vector2i &p_size) const override; + virtual void font_clear_glyphs(RID p_font_rid, const Vector2i &p_size) override; + virtual void font_remove_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) override; - virtual float font_get_underline_position(RID p_font, int p_size) const override; - virtual float font_get_underline_thickness(RID p_font, int p_size) const override; + virtual Vector2 font_get_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph, const Vector2 &p_advance) override; - virtual int font_get_spacing_space(RID p_font) const override; - virtual void font_set_spacing_space(RID p_font, int p_value) override; + virtual Vector2 font_get_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) override; - virtual int font_get_spacing_glyph(RID p_font) const override; - virtual void font_set_spacing_glyph(RID p_font, int p_value) override; + virtual Vector2 font_get_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) override; - virtual void font_set_antialiased(RID p_font, bool p_antialiased) override; - virtual bool font_get_antialiased(RID p_font) const override; + virtual Rect2 font_get_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) override; - virtual void font_set_hinting(RID p_font, Hinting p_hinting) override; - virtual Hinting font_get_hinting(RID p_font) const override; + virtual int font_get_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const override; + virtual void font_set_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) override; - virtual void font_set_force_autohinter(RID p_font, bool p_enabeld) override; - virtual bool font_get_force_autohinter(RID p_font) const override; + virtual bool font_get_glyph_contours(RID p_font, int p_size, int32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; - virtual bool font_has_char(RID p_font, char32_t p_char) const override; - virtual String font_get_supported_chars(RID p_font) const override; + virtual Array font_get_kerning_list(RID p_font_rid, int p_size) const override; + virtual void font_clear_kerning_map(RID p_font_rid, int p_size) override; + virtual void font_remove_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) override; - virtual void font_set_distance_field_hint(RID p_font, bool p_distance_field) override; - virtual bool font_get_distance_field_hint(RID p_font) const override; + virtual void font_set_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) override; + virtual Vector2 font_get_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) const override; - virtual bool font_has_outline(RID p_font) const override; - virtual float font_get_base_size(RID p_font) const override; + virtual int32_t font_get_glyph_index(RID p_font_rid, int p_size, char32_t p_char, char32_t p_variation_selector = 0) const override; - virtual bool font_is_language_supported(RID p_font, const String &p_language) const override; - virtual void font_set_language_support_override(RID p_font, const String &p_language, bool p_supported) override; - virtual bool font_get_language_support_override(RID p_font, const String &p_language) override; - virtual void font_remove_language_support_override(RID p_font, const String &p_language) override; - Vector<String> font_get_language_support_overrides(RID p_font) override; + virtual bool font_has_char(RID p_font_rid, char32_t p_char) const override; + virtual String font_get_supported_chars(RID p_font_rid) const override; - virtual bool font_is_script_supported(RID p_font, const String &p_script) const override; - virtual void font_set_script_support_override(RID p_font, const String &p_script, bool p_supported) override; - virtual bool font_get_script_support_override(RID p_font, const String &p_script) override; - virtual void font_remove_script_support_override(RID p_font, const String &p_script) override; - Vector<String> font_get_script_support_overrides(RID p_font) override; + virtual void font_render_range(RID p_font, const Vector2i &p_size, char32_t p_start, char32_t p_end) override; + virtual void font_render_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_index) override; - virtual uint32_t font_get_glyph_index(RID p_font, char32_t p_char, char32_t p_variation_selector = 0x0000) const override; - virtual Vector2 font_get_glyph_advance(RID p_font, uint32_t p_index, int p_size) const override; - virtual Vector2 font_get_glyph_kerning(RID p_font, uint32_t p_index_a, uint32_t p_index_b, int p_size) const override; + virtual void font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; + virtual void font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; - virtual Vector2 font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; - virtual Vector2 font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override; + virtual bool font_is_language_supported(RID p_font_rid, const String &p_language) const override; + virtual void font_set_language_support_override(RID p_font_rid, const String &p_language, bool p_supported) override; + virtual bool font_get_language_support_override(RID p_font_rid, const String &p_language) override; + virtual void font_remove_language_support_override(RID p_font_rid, const String &p_language) override; + virtual Vector<String> font_get_language_support_overrides(RID p_font_rid) override; - virtual bool font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const override; + virtual bool font_is_script_supported(RID p_font_rid, const String &p_script) const override; + virtual void font_set_script_support_override(RID p_font_rid, const String &p_script, bool p_supported) override; + virtual bool font_get_script_support_override(RID p_font_rid, const String &p_script) override; + virtual void font_remove_script_support_override(RID p_font_rid, const String &p_script) override; + virtual Vector<String> font_get_script_support_overrides(RID p_font_rid) override; - virtual float font_get_oversampling() const override; - virtual void font_set_oversampling(float p_oversampling) override; + virtual Dictionary font_supported_feature_list(RID p_font_rid) const override; + virtual Dictionary font_supported_variation_list(RID p_font_rid) const override; - virtual Vector<String> get_system_fonts() const override; + virtual real_t font_get_global_oversampling() const override; + virtual void font_set_global_oversampling(real_t p_oversampling) override; /* Shaped text buffer interface */ @@ -171,14 +372,14 @@ public: virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; - virtual float shaped_text_fit_to_width(RID p_shaped, float p_width, uint8_t /*JustificationFlag*/ p_jst_flags = JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA) override; - virtual float shaped_text_tab_align(RID p_shaped, const Vector<float> &p_tab_stops) override; + virtual real_t shaped_text_fit_to_width(RID p_shaped, real_t p_width, uint8_t /*JustificationFlag*/ p_jst_flags = JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA) override; + virtual real_t shaped_text_tab_align(RID p_shaped, const Vector<real_t> &p_tab_stops) override; virtual bool shaped_text_shape(RID p_shaped) override; virtual bool shaped_text_update_breaks(RID p_shaped) override; virtual bool shaped_text_update_justification_ops(RID p_shaped) override; - virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_trim_flags) override; + virtual void shaped_text_overrun_trim_to_width(RID p_shaped, real_t p_width, uint8_t p_trim_flags) override; virtual TrimData shaped_text_get_trim_data(RID p_shaped) const override; virtual bool shaped_text_is_ready(RID p_shaped) const override; @@ -193,17 +394,17 @@ public: virtual Rect2 shaped_text_get_object_rect(RID p_shaped, Variant p_key) const override; virtual Size2 shaped_text_get_size(RID p_shaped) const override; - virtual float shaped_text_get_ascent(RID p_shaped) const override; - virtual float shaped_text_get_descent(RID p_shaped) const override; - virtual float shaped_text_get_width(RID p_shaped) const override; - virtual float shaped_text_get_underline_position(RID p_shaped) const override; - virtual float shaped_text_get_underline_thickness(RID p_shaped) const override; + virtual real_t shaped_text_get_ascent(RID p_shaped) const override; + virtual real_t shaped_text_get_descent(RID p_shaped) const override; + virtual real_t shaped_text_get_width(RID p_shaped) const override; + virtual real_t shaped_text_get_underline_position(RID p_shaped) const override; + virtual real_t shaped_text_get_underline_thickness(RID p_shaped) const override; static TextServer *create_func(Error &r_error, void *p_user_data); static void register_server(); - TextServerFallback(){}; - ~TextServerFallback(){}; + TextServerFallback(); + ~TextServerFallback(); }; #endif // TEXT_SERVER_FALLBACK_H diff --git a/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml b/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml index 195d766c1d..55d0b392fa 100644 --- a/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml +++ b/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml @@ -138,81 +138,75 @@ <constant name="MATH_DB2LINEAR" value="40" enum="BuiltinFunc"> Convert the input from decibel volume to linear volume. </constant> - <constant name="MATH_POLAR2CARTESIAN" value="41" enum="BuiltinFunc"> - Converts a 2D point expressed in the polar coordinate system (a distance from the origin [code]r[/code] and an angle [code]th[/code]) to the cartesian coordinate system (X and Y axis). + <constant name="MATH_WRAP" value="41" enum="BuiltinFunc"> </constant> - <constant name="MATH_CARTESIAN2POLAR" value="42" enum="BuiltinFunc"> - Converts a 2D point expressed in the cartesian coordinate system (X and Y axis) to the polar coordinate system (a distance from the origin and an angle). + <constant name="MATH_WRAPF" value="42" enum="BuiltinFunc"> </constant> - <constant name="MATH_WRAP" value="43" enum="BuiltinFunc"> - </constant> - <constant name="MATH_WRAPF" value="44" enum="BuiltinFunc"> - </constant> - <constant name="LOGIC_MAX" value="45" enum="BuiltinFunc"> + <constant name="LOGIC_MAX" value="43" enum="BuiltinFunc"> Return the greater of the two numbers, also known as their maximum. </constant> - <constant name="LOGIC_MIN" value="46" enum="BuiltinFunc"> + <constant name="LOGIC_MIN" value="44" enum="BuiltinFunc"> Return the lesser of the two numbers, also known as their minimum. </constant> - <constant name="LOGIC_CLAMP" value="47" enum="BuiltinFunc"> + <constant name="LOGIC_CLAMP" value="45" enum="BuiltinFunc"> Return the input clamped inside the given range, ensuring the result is never outside it. Equivalent to [code]min(max(input, range_low), range_high)[/code]. </constant> - <constant name="LOGIC_NEAREST_PO2" value="48" enum="BuiltinFunc"> + <constant name="LOGIC_NEAREST_PO2" value="46" enum="BuiltinFunc"> Return the nearest power of 2 to the input. </constant> - <constant name="OBJ_WEAKREF" value="49" enum="BuiltinFunc"> + <constant name="OBJ_WEAKREF" value="47" enum="BuiltinFunc"> Create a [WeakRef] from the input. </constant> - <constant name="TYPE_CONVERT" value="50" enum="BuiltinFunc"> + <constant name="TYPE_CONVERT" value="48" enum="BuiltinFunc"> Convert between types. </constant> - <constant name="TYPE_OF" value="51" enum="BuiltinFunc"> + <constant name="TYPE_OF" value="49" enum="BuiltinFunc"> Return the type of the input as an integer. Check [enum Variant.Type] for the integers that might be returned. </constant> - <constant name="TYPE_EXISTS" value="52" enum="BuiltinFunc"> + <constant name="TYPE_EXISTS" value="50" enum="BuiltinFunc"> Checks if a type is registered in the [ClassDB]. </constant> - <constant name="TEXT_CHAR" value="53" enum="BuiltinFunc"> + <constant name="TEXT_CHAR" value="51" enum="BuiltinFunc"> Return a character with the given ascii value. </constant> - <constant name="TEXT_STR" value="54" enum="BuiltinFunc"> + <constant name="TEXT_STR" value="52" enum="BuiltinFunc"> Convert the input to a string. </constant> - <constant name="TEXT_PRINT" value="55" enum="BuiltinFunc"> + <constant name="TEXT_PRINT" value="53" enum="BuiltinFunc"> Print the given string to the output window. </constant> - <constant name="TEXT_PRINTERR" value="56" enum="BuiltinFunc"> + <constant name="TEXT_PRINTERR" value="54" enum="BuiltinFunc"> Print the given string to the standard error output. </constant> - <constant name="TEXT_PRINTRAW" value="57" enum="BuiltinFunc"> + <constant name="TEXT_PRINTRAW" value="55" enum="BuiltinFunc"> Print the given string to the standard output, without adding a newline. </constant> - <constant name="VAR_TO_STR" value="58" enum="BuiltinFunc"> + <constant name="VAR_TO_STR" value="56" enum="BuiltinFunc"> Serialize a [Variant] to a string. </constant> - <constant name="STR_TO_VAR" value="59" enum="BuiltinFunc"> + <constant name="STR_TO_VAR" value="57" enum="BuiltinFunc"> Deserialize a [Variant] from a string serialized using [constant VAR_TO_STR]. </constant> - <constant name="VAR_TO_BYTES" value="60" enum="BuiltinFunc"> + <constant name="VAR_TO_BYTES" value="58" enum="BuiltinFunc"> Serialize a [Variant] to a [PackedByteArray]. </constant> - <constant name="BYTES_TO_VAR" value="61" enum="BuiltinFunc"> + <constant name="BYTES_TO_VAR" value="59" enum="BuiltinFunc"> Deserialize a [Variant] from a [PackedByteArray] serialized using [constant VAR_TO_BYTES]. </constant> - <constant name="MATH_SMOOTHSTEP" value="62" enum="BuiltinFunc"> + <constant name="MATH_SMOOTHSTEP" value="60" enum="BuiltinFunc"> Return a number smoothly interpolated between the first two inputs, based on the third input. Similar to [constant MATH_LERP], but interpolates faster at the beginning and slower at the end. Using Hermite interpolation formula: [codeblock] var t = clamp((weight - from) / (to - from), 0.0, 1.0) return t * t * (3.0 - 2.0 * t) [/codeblock] </constant> - <constant name="MATH_POSMOD" value="63" enum="BuiltinFunc"> + <constant name="MATH_POSMOD" value="61" enum="BuiltinFunc"> </constant> - <constant name="MATH_LERP_ANGLE" value="64" enum="BuiltinFunc"> + <constant name="MATH_LERP_ANGLE" value="62" enum="BuiltinFunc"> </constant> - <constant name="TEXT_ORD" value="65" enum="BuiltinFunc"> + <constant name="TEXT_ORD" value="63" enum="BuiltinFunc"> </constant> - <constant name="FUNC_MAX" value="66" enum="BuiltinFunc"> + <constant name="FUNC_MAX" value="64" enum="BuiltinFunc"> Represents the size of the [enum BuiltinFunc] enum. </constant> </constants> diff --git a/modules/visual_script/register_types.cpp b/modules/visual_script/register_types.cpp index fce98eb8a0..7fb9707fce 100644 --- a/modules/visual_script/register_types.cpp +++ b/modules/visual_script/register_types.cpp @@ -43,7 +43,7 @@ VisualScriptLanguage *visual_script_language = nullptr; #ifdef TOOLS_ENABLED -static vs_bind::VisualScriptEditor *vs_editor_singleton = nullptr; +static VisualScriptCustomNodes *vs_custom_nodes_singleton = nullptr; #endif void register_visual_script_types() { @@ -114,10 +114,10 @@ void register_visual_script_types() { #ifdef TOOLS_ENABLED ClassDB::set_current_api(ClassDB::API_EDITOR); - GDREGISTER_CLASS(vs_bind::VisualScriptEditor); + GDREGISTER_CLASS(VisualScriptCustomNodes); ClassDB::set_current_api(ClassDB::API_CORE); - vs_editor_singleton = memnew(vs_bind::VisualScriptEditor); - Engine::get_singleton()->add_singleton(Engine::Singleton("VisualScriptEditor", vs_bind::VisualScriptEditor::get_singleton())); + vs_custom_nodes_singleton = memnew(VisualScriptCustomNodes); + Engine::get_singleton()->add_singleton(Engine::Singleton("VisualScriptEditor", VisualScriptCustomNodes::get_singleton())); VisualScriptEditor::register_editor(); #endif @@ -130,8 +130,8 @@ void unregister_visual_script_types() { #ifdef TOOLS_ENABLED VisualScriptEditor::free_clipboard(); - if (vs_editor_singleton) { - memdelete(vs_editor_singleton); + if (vs_custom_nodes_singleton) { + memdelete(vs_custom_nodes_singleton); } #endif if (visual_script_language) { diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 86793af77f..4d5f3420b8 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -955,7 +955,7 @@ bool VisualScript::are_subnodes_edited() const { } #endif -const Vector<MultiplayerAPI::RPCConfig> VisualScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> VisualScript::get_rpc_methods() const { return rpc_functions; } @@ -1022,11 +1022,11 @@ void VisualScript::_set_data(const Dictionary &p_data) { if (functions[E].func_id >= 0 && nodes.has(functions[E].func_id)) { Ref<VisualScriptFunction> vsf = nodes[functions[E].func_id].node; if (vsf.is_valid()) { - if (vsf->get_rpc_mode() != MultiplayerAPI::RPC_MODE_DISABLED) { - MultiplayerAPI::RPCConfig nd; + if (vsf->get_rpc_mode() != Multiplayer::RPC_MODE_DISABLED) { + Multiplayer::RPCConfig nd; nd.name = E; nd.rpc_mode = vsf->get_rpc_mode(); - nd.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; // TODO + nd.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; // TODO if (rpc_functions.find(nd) == -1) { rpc_functions.push_back(nd); } @@ -1036,7 +1036,7 @@ void VisualScript::_set_data(const Dictionary &p_data) { } // Sort so we are 100% that they are always the same. - rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>(); + rpc_functions.sort_custom<Multiplayer::SortRPCConfig>(); } Dictionary VisualScript::_get_data() const { @@ -1833,7 +1833,7 @@ Ref<Script> VisualScriptInstance::get_script() const { return script; } -const Vector<MultiplayerAPI::RPCConfig> VisualScriptInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> VisualScriptInstance::get_rpc_methods() const { return script->get_rpc_methods(); } @@ -1845,26 +1845,6 @@ void VisualScriptInstance::create(const Ref<VisualScript> &p_script, Object *p_o max_input_args = 0; max_output_args = 0; - if (Object::cast_to<Node>(p_owner)) { - // Turn on these if they exist and base is a node. - Node *node = Object::cast_to<Node>(p_owner); - if (p_script->functions.has("_process")) { - node->set_process(true); - } - if (p_script->functions.has("_physics_process")) { - node->set_physics_process(true); - } - if (p_script->functions.has("_input")) { - node->set_process_input(true); - } - if (p_script->functions.has("_unhandled_input")) { - node->set_process_unhandled_input(true); - } - if (p_script->functions.has("_unhandled_key_input")) { - node->set_process_unhandled_key_input(true); - } - } - // Setup variables. { List<StringName> keys; diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h index 932ebeb27f..39cef8f68b 100644 --- a/modules/visual_script/visual_script.h +++ b/modules/visual_script/visual_script.h @@ -234,7 +234,7 @@ private: HashMap<StringName, Function> functions; HashMap<StringName, Variable> variables; Map<StringName, Vector<Argument>> custom_signals; - Vector<MultiplayerAPI::RPCConfig> rpc_functions; + Vector<Multiplayer::RPCConfig> rpc_functions; Map<Object *, VisualScriptInstance *> instances; @@ -362,7 +362,7 @@ public: virtual int get_member_line(const StringName &p_member) const override; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; #ifdef TOOLS_ENABLED virtual bool are_subnodes_edited() const; @@ -443,7 +443,7 @@ public: virtual ScriptLanguage *get_language(); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const; VisualScriptInstance(); ~VisualScriptInstance(); diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp index c61c3ae272..2bd7220d15 100644 --- a/modules/visual_script/visual_script_builtin_funcs.cpp +++ b/modules/visual_script/visual_script_builtin_funcs.cpp @@ -79,8 +79,6 @@ const char *VisualScriptBuiltinFunc::func_name[VisualScriptBuiltinFunc::FUNC_MAX "rad2deg", "linear2db", "db2linear", - "polar2cartesian", - "cartesian2polar", "wrapi", "wrapf", "max", @@ -194,8 +192,6 @@ int VisualScriptBuiltinFunc::get_func_argument_count(BuiltinFunc p_func) { case MATH_SNAPPED: case MATH_RANDF_RANGE: case MATH_RANDI_RANGE: - case MATH_POLAR2CARTESIAN: - case MATH_CARTESIAN2POLAR: case LOGIC_MAX: case LOGIC_MIN: case TYPE_CONVERT: @@ -381,20 +377,6 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const case MATH_DB2LINEAR: { return PropertyInfo(Variant::FLOAT, "db"); } break; - case MATH_POLAR2CARTESIAN: { - if (p_idx == 0) { - return PropertyInfo(Variant::FLOAT, "r"); - } else { - return PropertyInfo(Variant::FLOAT, "th"); - } - } break; - case MATH_CARTESIAN2POLAR: { - if (p_idx == 0) { - return PropertyInfo(Variant::FLOAT, "x"); - } else { - return PropertyInfo(Variant::FLOAT, "y"); - } - } break; case MATH_WRAP: { if (p_idx == 0) { return PropertyInfo(Variant::INT, "value"); @@ -553,10 +535,6 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons case MATH_DB2LINEAR: { t = Variant::FLOAT; } break; - case MATH_POLAR2CARTESIAN: - case MATH_CARTESIAN2POLAR: { - t = Variant::VECTOR2; - } break; case MATH_WRAP: { t = Variant::INT; } break; @@ -874,20 +852,6 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in VALIDATE_ARG_NUM(0); *r_return = Math::db2linear((double)*p_inputs[0]); } break; - case VisualScriptBuiltinFunc::MATH_POLAR2CARTESIAN: { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - double r = *p_inputs[0]; - double th = *p_inputs[1]; - *r_return = Vector2(r * Math::cos(th), r * Math::sin(th)); - } break; - case VisualScriptBuiltinFunc::MATH_CARTESIAN2POLAR: { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - double x = *p_inputs[0]; - double y = *p_inputs[1]; - *r_return = Vector2(Math::sqrt(x * x + y * y), Math::atan2(y, x)); - } break; case VisualScriptBuiltinFunc::MATH_WRAP: { VALIDATE_ARG_NUM(0); VALIDATE_ARG_NUM(1); @@ -1229,8 +1193,6 @@ void VisualScriptBuiltinFunc::_bind_methods() { BIND_ENUM_CONSTANT(MATH_RAD2DEG); BIND_ENUM_CONSTANT(MATH_LINEAR2DB); BIND_ENUM_CONSTANT(MATH_DB2LINEAR); - BIND_ENUM_CONSTANT(MATH_POLAR2CARTESIAN); - BIND_ENUM_CONSTANT(MATH_CARTESIAN2POLAR); BIND_ENUM_CONSTANT(MATH_WRAP); BIND_ENUM_CONSTANT(MATH_WRAPF); BIND_ENUM_CONSTANT(LOGIC_MAX); @@ -1320,8 +1282,6 @@ void register_visual_script_builtin_func_node() { VisualScriptLanguage::singleton->add_register_func("functions/built_in/rad2deg", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RAD2DEG>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/linear2db", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LINEAR2DB>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/db2linear", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DB2LINEAR>); - VisualScriptLanguage::singleton->add_register_func("functions/built_in/polar2cartesian", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_POLAR2CARTESIAN>); - VisualScriptLanguage::singleton->add_register_func("functions/built_in/cartesian2polar", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_CARTESIAN2POLAR>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/wrapi", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_WRAP>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/wrapf", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_WRAPF>); diff --git a/modules/visual_script/visual_script_builtin_funcs.h b/modules/visual_script/visual_script_builtin_funcs.h index f59a7a0f0c..26abc1e479 100644 --- a/modules/visual_script/visual_script_builtin_funcs.h +++ b/modules/visual_script/visual_script_builtin_funcs.h @@ -79,8 +79,6 @@ public: MATH_RAD2DEG, MATH_LINEAR2DB, MATH_DB2LINEAR, - MATH_POLAR2CARTESIAN, - MATH_CARTESIAN2POLAR, MATH_WRAP, MATH_WRAPF, LOGIC_MAX, diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 9d813e3d77..eee9e8f32b 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -1816,7 +1816,7 @@ void VisualScriptEditor::_generic_search(String p_base_type, Vector2 pos, bool n } } -void VisualScriptEditor::_input(const Ref<InputEvent> &p_event) { +void VisualScriptEditor::input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); // GUI input for VS Editor Plugin @@ -4246,8 +4246,6 @@ void VisualScriptEditor::_bind_methods() { ClassDB::bind_method("_can_drop_data_fw", &VisualScriptEditor::can_drop_data_fw); ClassDB::bind_method("_drop_data_fw", &VisualScriptEditor::drop_data_fw); - ClassDB::bind_method("_input", &VisualScriptEditor::_input); - ClassDB::bind_method("_update_graph_connections", &VisualScriptEditor::_update_graph_connections); ClassDB::bind_method("_update_members", &VisualScriptEditor::_update_members); @@ -4524,46 +4522,44 @@ void VisualScriptEditor::register_editor() { void VisualScriptEditor::validate() { } -namespace vs_bind { +// VisualScriptCustomNodes -Ref<VisualScriptNode> VisualScriptEditor::create_node_custom(const String &p_name) { +Ref<VisualScriptNode> VisualScriptCustomNodes::create_node_custom(const String &p_name) { Ref<VisualScriptCustomNode> node; node.instantiate(); node->set_script(singleton->custom_nodes[p_name]); return node; } -VisualScriptEditor *VisualScriptEditor::singleton = nullptr; -Map<String, REF> VisualScriptEditor::custom_nodes; +VisualScriptCustomNodes *VisualScriptCustomNodes::singleton = nullptr; +Map<String, REF> VisualScriptCustomNodes::custom_nodes; -VisualScriptEditor::VisualScriptEditor() { +VisualScriptCustomNodes::VisualScriptCustomNodes() { singleton = this; } -VisualScriptEditor::~VisualScriptEditor() { +VisualScriptCustomNodes::~VisualScriptCustomNodes() { custom_nodes.clear(); } -void VisualScriptEditor::add_custom_node(const String &p_name, const String &p_category, const Ref<Script> &p_script) { +void VisualScriptCustomNodes::add_custom_node(const String &p_name, const String &p_category, const Ref<Script> &p_script) { String node_name = "custom/" + p_category + "/" + p_name; custom_nodes.insert(node_name, p_script); - VisualScriptLanguage::singleton->add_register_func(node_name, &VisualScriptEditor::create_node_custom); + VisualScriptLanguage::singleton->add_register_func(node_name, &VisualScriptCustomNodes::create_node_custom); emit_signal(SNAME("custom_nodes_updated")); } -void VisualScriptEditor::remove_custom_node(const String &p_name, const String &p_category) { +void VisualScriptCustomNodes::remove_custom_node(const String &p_name, const String &p_category) { String node_name = "custom/" + p_category + "/" + p_name; custom_nodes.erase(node_name); VisualScriptLanguage::singleton->remove_register_func(node_name); emit_signal(SNAME("custom_nodes_updated")); } -void VisualScriptEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_custom_node", "name", "category", "script"), &VisualScriptEditor::add_custom_node); - ClassDB::bind_method(D_METHOD("remove_custom_node", "name", "category"), &VisualScriptEditor::remove_custom_node); +void VisualScriptCustomNodes::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_custom_node", "name", "category", "script"), &VisualScriptCustomNodes::add_custom_node); + ClassDB::bind_method(D_METHOD("remove_custom_node", "name", "category"), &VisualScriptCustomNodes::remove_custom_node); ADD_SIGNAL(MethodInfo("custom_nodes_updated")); } -} // namespace vs_bind - #endif diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 0c4ea880c8..ab32aae7aa 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -46,6 +46,8 @@ class VisualScriptEditorVariableEdit; // TODO: Maybe this class should be refactored. // See https://github.com/godotengine/godot/issues/51913 class VisualScriptEditor : public ScriptEditorBase { + GDCLASS(VisualScriptEditor, ScriptEditorBase); + enum { TYPE_SEQUENCE = 1000, INDEX_BASE_SEQUENCE = 1024 @@ -234,7 +236,7 @@ class VisualScriptEditor : public ScriptEditorBase { void _generic_search(String p_base_type = "", Vector2 pos = Vector2(), bool node_centered = false); - void _input(const Ref<InputEvent> &p_event); + virtual void input(const Ref<InputEvent> &p_event) override; void _graph_gui_input(const Ref<InputEvent> &p_event); void _members_gui_input(const Ref<InputEvent> &p_event); void _fn_name_box_input(const Ref<InputEvent> &p_event); @@ -309,6 +311,8 @@ public: virtual void tag_saved_version() override; virtual void reload(bool p_soft) override; virtual Array get_breakpoints() override; + virtual void set_breakpoint(int p_line, bool p_enable) override{}; + virtual void clear_breakpoints() override{}; virtual void add_callback(const String &p_function, PackedStringArray p_args) override; virtual void update_settings() override; virtual bool show_members_overview() override; @@ -330,33 +334,29 @@ public: ~VisualScriptEditor(); }; -namespace vs_bind { - // Singleton -class VisualScriptEditor : public Object { - GDCLASS(VisualScriptEditor, Object); +class VisualScriptCustomNodes : public Object { + GDCLASS(VisualScriptCustomNodes, Object); friend class VisualScriptLanguage; protected: static void _bind_methods(); - static VisualScriptEditor *singleton; + static VisualScriptCustomNodes *singleton; static Map<String, REF> custom_nodes; static Ref<VisualScriptNode> create_node_custom(const String &p_name); public: - static VisualScriptEditor *get_singleton() { return singleton; } + static VisualScriptCustomNodes *get_singleton() { return singleton; } void add_custom_node(const String &p_name, const String &p_category, const Ref<Script> &p_script); void remove_custom_node(const String &p_name, const String &p_category); - VisualScriptEditor(); - ~VisualScriptEditor(); + VisualScriptCustomNodes(); + ~VisualScriptCustomNodes(); }; -} // namespace vs_bind - #endif #endif // VISUALSCRIPT_EDITOR_H diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index c9e426fa6c..ef77c0cef3 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -90,7 +90,7 @@ bool VisualScriptFunction::_set(const StringName &p_name, const Variant &p_value } if (p_name == "rpc/mode") { - rpc_mode = MultiplayerAPI::RPCMode(int(p_value)); + rpc_mode = Multiplayer::RPCMode(int(p_value)); return true; } @@ -163,7 +163,7 @@ void VisualScriptFunction::_get_property_list(List<PropertyInfo> *p_list) const p_list->push_back(PropertyInfo(Variant::INT, "stack/size", PROPERTY_HINT_RANGE, "1,100000")); } p_list->push_back(PropertyInfo(Variant::BOOL, "stack/stackless")); - p_list->push_back(PropertyInfo(Variant::INT, "rpc/mode", PROPERTY_HINT_ENUM, "Disabled,Remote,Master,Puppet,Remote Sync,Master Sync,Puppet Sync")); + p_list->push_back(PropertyInfo(Variant::INT, "rpc/mode", PROPERTY_HINT_ENUM, "Disabled,Any,Authority")); } int VisualScriptFunction::get_output_sequence_port_count() const { @@ -261,11 +261,11 @@ int VisualScriptFunction::get_argument_count() const { return arguments.size(); } -void VisualScriptFunction::set_rpc_mode(MultiplayerAPI::RPCMode p_mode) { +void VisualScriptFunction::set_rpc_mode(Multiplayer::RPCMode p_mode) { rpc_mode = p_mode; } -MultiplayerAPI::RPCMode VisualScriptFunction::get_rpc_mode() const { +Multiplayer::RPCMode VisualScriptFunction::get_rpc_mode() const { return rpc_mode; } @@ -311,14 +311,14 @@ void VisualScriptFunction::reset_state() { stack_size = 256; stack_less = false; sequenced = true; - rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + rpc_mode = Multiplayer::RPC_MODE_DISABLED; } VisualScriptFunction::VisualScriptFunction() { stack_size = 256; stack_less = false; sequenced = true; - rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + rpc_mode = Multiplayer::RPC_MODE_DISABLED; } void VisualScriptFunction::set_stack_less(bool p_enable) { @@ -2976,7 +2976,7 @@ public: virtual int get_working_memory_size() const { return work_mem_size; } virtual int step(const Variant **p_inputs, Variant **p_outputs, StartMode p_start_mode, Variant *p_working_mem, Callable::CallError &r_error, String &r_error_str) { - if (GDVIRTUAL_IS_OVERRIDEN_PTR(node, _step)) { + if (GDVIRTUAL_IS_OVERRIDDEN_PTR(node, _step)) { Array in_values; Array out_values; Array work_mem; diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h index 35d9b0b4fe..bf2d8e9683 100644 --- a/modules/visual_script/visual_script_nodes.h +++ b/modules/visual_script/visual_script_nodes.h @@ -49,7 +49,7 @@ class VisualScriptFunction : public VisualScriptNode { bool stack_less; int stack_size; - MultiplayerAPI::RPCMode rpc_mode; + Multiplayer::RPCMode rpc_mode; bool sequenced; protected: @@ -96,8 +96,8 @@ public: void set_return_type(Variant::Type p_type); Variant::Type get_return_type() const; - void set_rpc_mode(MultiplayerAPI::RPCMode p_mode); - MultiplayerAPI::RPCMode get_rpc_mode() const; + void set_rpc_mode(Multiplayer::RPCMode p_mode); + Multiplayer::RPCMode get_rpc_mode() const; virtual VisualScriptNodeInstance *instantiate(VisualScriptInstance *p_instance) override; diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp index bacb1947be..d8b88d6cd3 100644 --- a/modules/visual_script/visual_script_property_selector.cpp +++ b/modules/visual_script/visual_script_property_selector.cpp @@ -55,7 +55,7 @@ void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { case KEY_DOWN: case KEY_PAGEUP: case KEY_PAGEDOWN: { - search_options->call("_gui_input", k); + search_options->gui_input(k); search_box->accept_event(); TreeItem *root = search_options->get_root(); diff --git a/modules/vorbis/SCsub b/modules/vorbis/SCsub index bc31fff066..322314487f 100644 --- a/modules/vorbis/SCsub +++ b/modules/vorbis/SCsub @@ -3,9 +3,6 @@ Import("env") Import("env_modules") -# Only kept to build the thirdparty library used by the theora and webm -# modules. We now use stb_vorbis for AudioStreamOGGVorbis. - env_vorbis = env_modules.Clone() # Thirdparty source files diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp new file mode 100644 index 0000000000..e4a80c339f --- /dev/null +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -0,0 +1,435 @@ +/*************************************************************************/ +/* audio_stream_ogg_vorbis.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 "audio_stream_ogg_vorbis.h" + +#include "core/io/file_access.h" +#include "core/variant/typed_array.h" +#include "thirdparty/libogg/ogg/ogg.h" + +int AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) { + ERR_FAIL_COND_V(!ready, 0); + ERR_FAIL_COND_V(!active, 0); + + int todo = p_frames; + + int start_buffer = 0; + + int frames_mixed_this_step = p_frames; + + while (todo && active) { + AudioFrame *buffer = p_buffer; + if (start_buffer > 0) { + buffer = buffer + start_buffer; + } + int mixed = _mix_frames_vorbis(buffer, todo); + if (mixed < 0) { + return 0; + } + todo -= mixed; + frames_mixed += mixed; + start_buffer += mixed; + if (!have_packets_left) { + //end of file! + bool is_not_empty = mixed > 0 || vorbis_stream->get_length() > 0; + if (vorbis_stream->loop && is_not_empty) { + //loop + + seek(vorbis_stream->loop_offset); + loops++; + // we still have buffer to fill, start from this element in the next iteration. + start_buffer = p_frames - todo; + } else { + frames_mixed_this_step = p_frames - todo; + for (int i = p_frames - todo; i < p_frames; i++) { + p_buffer[i] = AudioFrame(0, 0); + } + active = false; + todo = 0; + } + } + } + return frames_mixed_this_step; +} + +int AudioStreamPlaybackOGGVorbis::_mix_frames_vorbis(AudioFrame *p_buffer, int p_frames) { + ERR_FAIL_COND_V(!ready, 0); + if (!have_samples_left) { + ogg_packet *packet = nullptr; + int err; + + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + have_packets_left = false; + WARN_PRINT("ran out of packets in stream"); + return -1; + } + + ERR_FAIL_COND_V_MSG((err = vorbis_synthesis(&block, packet)), 0, "Error during vorbis synthesis " + itos(err)); + ERR_FAIL_COND_V_MSG((err = vorbis_synthesis_blockin(&dsp_state, &block)), 0, "Error during vorbis block processing " + itos(err)); + + have_packets_left = !packet->e_o_s; + } + + float **pcm; // Accessed with pcm[channel_idx][sample_idx]. + + int frames = vorbis_synthesis_pcmout(&dsp_state, &pcm); + if (frames > p_frames) { + frames = p_frames; + have_samples_left = true; + } else { + have_samples_left = false; + } + + if (info.channels > 1) { + for (int frame = 0; frame < frames; frame++) { + p_buffer[frame].l = pcm[0][frame]; + p_buffer[frame].r = pcm[0][frame]; + } + } else { + for (int frame = 0; frame < frames; frame++) { + p_buffer[frame].l = pcm[0][frame]; + p_buffer[frame].r = pcm[0][frame]; + } + } + vorbis_synthesis_read(&dsp_state, frames); + return frames; +} + +float AudioStreamPlaybackOGGVorbis::get_stream_sampling_rate() { + return vorbis_data->get_sampling_rate(); +} + +bool AudioStreamPlaybackOGGVorbis::_alloc_vorbis() { + vorbis_info_init(&info); + info_is_allocated = true; + vorbis_comment_init(&comment); + comment_is_allocated = true; + + ERR_FAIL_COND_V(vorbis_data.is_null(), false); + vorbis_data_playback = vorbis_data->instance_playback(); + + ogg_packet *packet; + int err; + + for (int i = 0; i < 3; i++) { + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + WARN_PRINT("Not enough packets to parse header"); + return false; + } + + err = vorbis_synthesis_headerin(&info, &comment, packet); + ERR_FAIL_COND_V_MSG(err != 0, false, "Error parsing header"); + } + + err = vorbis_synthesis_init(&dsp_state, &info); + ERR_FAIL_COND_V_MSG(err != 0, false, "Error initializing dsp state"); + dsp_state_is_allocated = true; + + err = vorbis_block_init(&dsp_state, &block); + ERR_FAIL_COND_V_MSG(err != 0, false, "Error initializing block"); + block_is_allocated = true; + + ready = true; + + return true; +} + +void AudioStreamPlaybackOGGVorbis::start(float p_from_pos) { + ERR_FAIL_COND(!ready); + active = true; + seek(p_from_pos); + loops = 0; + _begin_resample(); +} + +void AudioStreamPlaybackOGGVorbis::stop() { + active = false; +} + +bool AudioStreamPlaybackOGGVorbis::is_playing() const { + return active; +} + +int AudioStreamPlaybackOGGVorbis::get_loop_count() const { + return loops; +} + +float AudioStreamPlaybackOGGVorbis::get_playback_position() const { + return float(frames_mixed) / vorbis_data->get_sampling_rate(); +} + +void AudioStreamPlaybackOGGVorbis::seek(float p_time) { + ERR_FAIL_COND(!ready); + ERR_FAIL_COND(vorbis_stream.is_null()); + if (!active) { + return; + } + + vorbis_synthesis_restart(&dsp_state); + + if (p_time >= vorbis_stream->get_length()) { + p_time = 0; + } + frames_mixed = uint32_t(vorbis_data->get_sampling_rate() * p_time); + + const int64_t desired_sample = p_time * get_stream_sampling_rate(); + + if (!vorbis_data_playback->seek_page(desired_sample)) { + WARN_PRINT("seek failed"); + return; + } + + ogg_packet *packet; + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + WARN_PRINT_ONCE("seeking beyond limits"); + return; + } + + // The granule position of the page we're seeking through. + int64_t granule_pos = 0; + + int headers_remaining = 0; + int samples_in_page = 0; + int err; + while (true) { + if (vorbis_synthesis_idheader(packet)) { + headers_remaining = 3; + } + if (!headers_remaining) { + ERR_FAIL_COND_MSG((err = vorbis_synthesis(&block, packet)), "Error during vorbis synthesis " + itos(err)); + ERR_FAIL_COND_MSG((err = vorbis_synthesis_blockin(&dsp_state, &block)), "Error during vorbis block processing " + itos(err)); + + int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr); + ERR_FAIL_COND_MSG((err = vorbis_synthesis_read(&dsp_state, samples_out)), "Error during vorbis read updating " + itos(err)); + + samples_in_page += samples_out; + + } else { + headers_remaining--; + } + if (packet->granulepos != -1 && headers_remaining == 0) { + // This indicates the end of the page. + granule_pos = packet->granulepos; + break; + } + if (packet->e_o_s) { + break; + } + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + // We should get an e_o_s flag before this happens. + WARN_PRINT("Vorbis file ended without warning."); + break; + } + } + + int64_t samples_to_burn = samples_in_page - (granule_pos - desired_sample); + + if (samples_to_burn > samples_in_page) { + WARN_PRINT("Burning more samples than we have in this page. Check seek algorithm."); + } else if (samples_to_burn < 0) { + WARN_PRINT("Burning negative samples doesn't make sense. Check seek algorithm."); + } + + // Seek again, this time we'll burn a specific number of samples instead of all of them. + if (!vorbis_data_playback->seek_page(desired_sample)) { + WARN_PRINT("seek failed"); + return; + } + + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + WARN_PRINT_ONCE("seeking beyond limits"); + return; + } + vorbis_synthesis_restart(&dsp_state); + + while (true) { + if (vorbis_synthesis_idheader(packet)) { + headers_remaining = 3; + } + if (!headers_remaining) { + ERR_FAIL_COND_MSG((err = vorbis_synthesis(&block, packet)), "Error during vorbis synthesis " + itos(err)); + ERR_FAIL_COND_MSG((err = vorbis_synthesis_blockin(&dsp_state, &block)), "Error during vorbis block processing " + itos(err)); + + int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr); + int read_samples = samples_to_burn > samples_out ? samples_out : samples_to_burn; + ERR_FAIL_COND_MSG((err = vorbis_synthesis_read(&dsp_state, samples_out)), "Error during vorbis read updating " + itos(err)); + samples_to_burn -= read_samples; + + if (samples_to_burn <= 0) { + break; + } + } else { + headers_remaining--; + } + if (packet->granulepos != -1 && headers_remaining == 0) { + // This indicates the end of the page. + break; + } + if (packet->e_o_s) { + break; + } + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + // We should get an e_o_s flag before this happens. + WARN_PRINT("Vorbis file ended without warning."); + break; + } + } +} + +AudioStreamPlaybackOGGVorbis::~AudioStreamPlaybackOGGVorbis() { + if (block_is_allocated) { + vorbis_block_clear(&block); + } + if (dsp_state_is_allocated) { + vorbis_dsp_clear(&dsp_state); + } + if (comment_is_allocated) { + vorbis_comment_clear(&comment); + } + if (info_is_allocated) { + vorbis_info_clear(&info); + } +} + +Ref<AudioStreamPlayback> AudioStreamOGGVorbis::instance_playback() { + Ref<AudioStreamPlaybackOGGVorbis> ovs; + + ERR_FAIL_COND_V(packet_sequence.is_null(), nullptr); + + ovs.instantiate(); + ovs->vorbis_stream = Ref<AudioStreamOGGVorbis>(this); + ovs->vorbis_data = packet_sequence; + ovs->frames_mixed = 0; + ovs->active = false; + ovs->loops = 0; + if (ovs->_alloc_vorbis()) { + return ovs; + } + // Failed to allocate data structures. + return nullptr; +} + +String AudioStreamOGGVorbis::get_stream_name() const { + return ""; //return stream_name; +} + +void AudioStreamOGGVorbis::maybe_update_info() { + ERR_FAIL_COND(packet_sequence.is_null()); + + vorbis_info info; + vorbis_comment comment; + int err; + + vorbis_info_init(&info); + vorbis_comment_init(&comment); + + int packet_count = 0; + Ref<OGGPacketSequencePlayback> packet_sequence_playback = packet_sequence->instance_playback(); + + for (int i = 0; i < 3; i++) { + ogg_packet *packet; + if (!packet_sequence_playback->next_ogg_packet(&packet)) { + WARN_PRINT("Failed to get header packet"); + break; + } + if (i == 0) { + packet->b_o_s = 1; + } + + if (i == 0) { + ERR_FAIL_COND(!vorbis_synthesis_idheader(packet)); + } + + err = vorbis_synthesis_headerin(&info, &comment, packet); + ERR_FAIL_COND_MSG(err != 0, "Error parsing header packet " + itos(i) + ": " + itos(err)); + + packet_count++; + } + + packet_sequence->set_sampling_rate(info.rate); + + vorbis_comment_clear(&comment); + vorbis_info_clear(&info); +} + +void AudioStreamOGGVorbis::set_packet_sequence(Ref<OGGPacketSequence> p_packet_sequence) { + packet_sequence = p_packet_sequence; + if (packet_sequence.is_valid()) { + maybe_update_info(); + } +} + +Ref<OGGPacketSequence> AudioStreamOGGVorbis::get_packet_sequence() const { + return packet_sequence; +} + +void AudioStreamOGGVorbis::set_loop(bool p_enable) { + loop = p_enable; +} + +bool AudioStreamOGGVorbis::has_loop() const { + return loop; +} + +void AudioStreamOGGVorbis::set_loop_offset(float p_seconds) { + loop_offset = p_seconds; +} + +float AudioStreamOGGVorbis::get_loop_offset() const { + return loop_offset; +} + +float AudioStreamOGGVorbis::get_length() const { + ERR_FAIL_COND_V(packet_sequence.is_null(), 0); + return packet_sequence->get_length(); +} + +bool AudioStreamOGGVorbis::is_monophonic() const { + return false; +} + +void AudioStreamOGGVorbis::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_packet_sequence", "packet_sequence"), &AudioStreamOGGVorbis::set_packet_sequence); + ClassDB::bind_method(D_METHOD("get_packet_sequence"), &AudioStreamOGGVorbis::get_packet_sequence); + + ClassDB::bind_method(D_METHOD("set_loop", "enable"), &AudioStreamOGGVorbis::set_loop); + ClassDB::bind_method(D_METHOD("has_loop"), &AudioStreamOGGVorbis::has_loop); + + ClassDB::bind_method(D_METHOD("set_loop_offset", "seconds"), &AudioStreamOGGVorbis::set_loop_offset); + ClassDB::bind_method(D_METHOD("get_loop_offset"), &AudioStreamOGGVorbis::get_loop_offset); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "packet_sequence", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_packet_sequence", "get_packet_sequence"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "loop_offset"), "set_loop_offset", "get_loop_offset"); +} + +AudioStreamOGGVorbis::AudioStreamOGGVorbis() {} + +AudioStreamOGGVorbis::~AudioStreamOGGVorbis() {} diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h index 2bd70a2722..59a1318a6b 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.h +++ b/modules/vorbis/audio_stream_ogg_vorbis.h @@ -28,31 +28,51 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef AUDIO_STREAM_STB_VORBIS_H -#define AUDIO_STREAM_STB_VORBIS_H +#ifndef AUDIO_STREAM_LIBVORBIS_H +#define AUDIO_STREAM_LIBVORBIS_H -#include "core/io/resource_loader.h" +#include "core/variant/variant.h" +#include "modules/ogg/ogg_packet_sequence.h" #include "servers/audio/audio_stream.h" - -#include "thirdparty/misc/stb_vorbis.h" +#include "thirdparty/libvorbis/vorbis/codec.h" class AudioStreamOGGVorbis; class AudioStreamPlaybackOGGVorbis : public AudioStreamPlaybackResampled { GDCLASS(AudioStreamPlaybackOGGVorbis, AudioStreamPlaybackResampled); - stb_vorbis *ogg_stream = nullptr; - stb_vorbis_alloc ogg_alloc; uint32_t frames_mixed = 0; bool active = false; int loops = 0; + vorbis_info info; + vorbis_comment comment; + vorbis_dsp_state dsp_state; + vorbis_block block; + + bool info_is_allocated = false; + bool comment_is_allocated = false; + bool dsp_state_is_allocated = false; + bool block_is_allocated = false; + + bool ready = false; + + bool have_samples_left = false; + bool have_packets_left = false; + friend class AudioStreamOGGVorbis; + Ref<OGGPacketSequence> vorbis_data; + Ref<OGGPacketSequencePlayback> vorbis_data_playback; Ref<AudioStreamOGGVorbis> vorbis_stream; + int _mix_frames_vorbis(AudioFrame *p_buffer, int p_frames); + + // Allocates vorbis data structures. Returns true upon success, false on failure. + bool _alloc_vorbis(); + protected: - virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) override; + virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override; virtual float get_stream_sampling_rate() override; public: @@ -72,20 +92,20 @@ public: class AudioStreamOGGVorbis : public AudioStream { GDCLASS(AudioStreamOGGVorbis, AudioStream); OBJ_SAVE_TYPE(AudioStream); // Saves derived classes with common type so they can be interchanged. - RES_BASE_EXTENSION("oggstr"); + RES_BASE_EXTENSION("oggvorbisstr"); friend class AudioStreamPlaybackOGGVorbis; - void *data = nullptr; - uint32_t data_len = 0; - - int decode_mem_size = 0; - float sample_rate = 1.0; int channels = 1; float length = 0.0; bool loop = false; float loop_offset = 0.0; - void clear_data(); + + // Performs a seek to the beginning of the stream, should not be called during playback! + // Also causes allocation and deallocation. + void maybe_update_info(); + + Ref<OGGPacketSequence> packet_sequence; protected: static void _bind_methods(); @@ -100,13 +120,15 @@ public: virtual Ref<AudioStreamPlayback> instance_playback() override; virtual String get_stream_name() const override; - void set_data(const Vector<uint8_t> &p_data); - Vector<uint8_t> get_data() const; + void set_packet_sequence(Ref<OGGPacketSequence> p_packet_sequence); + Ref<OGGPacketSequence> get_packet_sequence() const; virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; + AudioStreamOGGVorbis(); virtual ~AudioStreamOGGVorbis(); }; -#endif +#endif // AUDIO_STREAM_LIBVORBIS_H diff --git a/modules/vorbis/config.py b/modules/vorbis/config.py index 8a384e3066..978eccb29f 100644 --- a/modules/vorbis/config.py +++ b/modules/vorbis/config.py @@ -4,3 +4,14 @@ def can_build(env, platform): def configure(env): pass + + +def get_doc_classes(): + return [ + "AudioStreamOGGVorbis", + "AudioStreamPlaybackOGGVorbis", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/stb_vorbis/doc_classes/AudioStreamOGGVorbis.xml b/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml index 94fdff5d43..a680a2f999 100644 --- a/modules/stb_vorbis/doc_classes/AudioStreamOGGVorbis.xml +++ b/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml @@ -1,25 +1,23 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="AudioStreamOGGVorbis" inherits="AudioStream" version="4.0"> <brief_description> - OGG Vorbis audio stream driver. </brief_description> <description> - OGG Vorbis audio stream driver. </description> <tutorials> </tutorials> <methods> </methods> <members> - <member name="data" type="PackedByteArray" setter="set_data" getter="get_data" default="PackedByteArray()"> - Contains the audio data in bytes. - </member> <member name="loop" type="bool" setter="set_loop" getter="has_loop" default="false"> If [code]true[/code], the stream will automatically loop when it reaches the end. </member> <member name="loop_offset" type="float" setter="set_loop_offset" getter="get_loop_offset" default="0.0"> Time in seconds at which the stream starts after being looped. </member> + <member name="packet_sequence" type="OGGPacketSequence" setter="set_packet_sequence" getter="get_packet_sequence"> + Contains the raw OGG data for this stream. + </member> </members> <constants> </constants> diff --git a/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml b/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml new file mode 100644 index 0000000000..3120f2a9e6 --- /dev/null +++ b/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AudioStreamPlaybackOGGVorbis" inherits="AudioStreamPlaybackResampled" version="4.0"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/vorbis/register_types.cpp b/modules/vorbis/register_types.cpp index d3e77ea629..de3f41afdd 100644 --- a/modules/vorbis/register_types.cpp +++ b/modules/vorbis/register_types.cpp @@ -30,8 +30,19 @@ #include "register_types.h" -// Dummy module as libvorbis is needed by other modules (theora ...) +#include "audio_stream_ogg_vorbis.h" +#include "resource_importer_ogg_vorbis.h" -void register_vorbis_types() {} +void register_vorbis_types() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + Ref<ResourceImporterOGGVorbis> ogg_vorbis_importer; + ogg_vorbis_importer.instantiate(); + ResourceFormatImporter::get_singleton()->add_importer(ogg_vorbis_importer); + } +#endif + GDREGISTER_CLASS(AudioStreamOGGVorbis); + GDREGISTER_CLASS(AudioStreamPlaybackOGGVorbis); +} void unregister_vorbis_types() {} diff --git a/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp index 85de698efd..33ee6cf359 100644 --- a/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp +++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp @@ -30,16 +30,19 @@ #include "resource_importer_ogg_vorbis.h" +#include "audio_stream_ogg_vorbis.h" #include "core/io/file_access.h" #include "core/io/resource_saver.h" #include "scene/resources/texture.h" +#include "thirdparty/libogg/ogg/ogg.h" +#include "thirdparty/libvorbis/vorbis/codec.h" String ResourceImporterOGGVorbis::get_importer_name() const { - return "ogg_vorbis"; + return "oggvorbisstr"; } String ResourceImporterOGGVorbis::get_visible_name() const { - return "OGGVorbis"; + return "oggvorbisstr"; } void ResourceImporterOGGVorbis::get_recognized_extensions(List<String> *p_extensions) const { @@ -47,7 +50,7 @@ void ResourceImporterOGGVorbis::get_recognized_extensions(List<String> *p_extens } String ResourceImporterOGGVorbis::get_save_extension() const { - return "oggstr"; + return "oggvorbisstr"; } String ResourceImporterOGGVorbis::get_resource_type() const { @@ -81,23 +84,106 @@ Error ResourceImporterOGGVorbis::import(const String &p_source_file, const Strin uint64_t len = f->get_length(); - Vector<uint8_t> data; - data.resize(len); - uint8_t *w = data.ptrw(); + Vector<uint8_t> file_data; + file_data.resize(len); + uint8_t *w = file_data.ptrw(); f->get_buffer(w, len); memdelete(f); - Ref<AudioStreamOGGVorbis> ogg_stream; - ogg_stream.instantiate(); - - ogg_stream->set_data(data); - ERR_FAIL_COND_V(!ogg_stream->get_data().size(), ERR_FILE_CORRUPT); - ogg_stream->set_loop(loop); - ogg_stream->set_loop_offset(loop_offset); - - return ResourceSaver::save(p_save_path + ".oggstr", ogg_stream); + Ref<AudioStreamOGGVorbis> ogg_vorbis_stream; + ogg_vorbis_stream.instantiate(); + + Ref<OGGPacketSequence> ogg_packet_sequence; + ogg_packet_sequence.instantiate(); + + ogg_stream_state stream_state; + ogg_sync_state sync_state; + ogg_page page; + ogg_packet packet; + bool initialized_stream = false; + + ogg_sync_init(&sync_state); + int err; + size_t cursor = 0; + size_t packet_count = 0; + bool done = false; + while (!done) { + ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err)); + while (ogg_sync_pageout(&sync_state, &page) != 1) { + if (cursor >= len) { + done = true; + break; + } + ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err)); + char *sync_buf = ogg_sync_buffer(&sync_state, OGG_SYNC_BUFFER_SIZE); + ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err)); + ERR_FAIL_COND_V(cursor > len, Error::ERR_INVALID_DATA); + size_t copy_size = len - cursor; + if (copy_size > OGG_SYNC_BUFFER_SIZE) { + copy_size = OGG_SYNC_BUFFER_SIZE; + } + memcpy(sync_buf, &file_data[cursor], copy_size); + ogg_sync_wrote(&sync_state, copy_size); + cursor += copy_size; + ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err)); + } + if (done) { + break; + } + ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err)); + + // Have a page now. + if (!initialized_stream) { + ogg_stream_init(&stream_state, ogg_page_serialno(&page)); + ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err)); + initialized_stream = true; + } + ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err)); + ogg_stream_pagein(&stream_state, &page); + ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err)); + int desync_iters = 0; + + Vector<Vector<uint8_t>> packet_data; + int64_t granule_pos = 0; + + while (true) { + err = ogg_stream_packetout(&stream_state, &packet); + if (err == -1) { + // According to the docs this is usually recoverable, but don't sit here spinning forever. + desync_iters++; + ERR_FAIL_COND_V_MSG(desync_iters > 100, Error::ERR_INVALID_DATA, "Packet sync issue during ogg import"); + continue; + } else if (err == 0) { + // Not enough data to fully reconstruct a packet. Go on to the next page. + break; + } + if (packet_count == 0 && vorbis_synthesis_idheader(&packet) == 0) { + WARN_PRINT("Found a non-vorbis-header packet in a header position"); + // Clearly this logical stream is not a vorbis stream, so destroy it and try again with the next page. + ogg_stream_destroy(&stream_state); + initialized_stream = false; + break; + } + granule_pos = packet.granulepos; + + PackedByteArray data; + data.resize(packet.bytes); + memcpy(data.ptrw(), packet.packet, packet.bytes); + packet_data.push_back(data); + packet_count++; + } + if (initialized_stream) { + ogg_packet_sequence->push_page(granule_pos, packet_data); + } + } + + ogg_vorbis_stream->set_packet_sequence(ogg_packet_sequence); + ogg_vorbis_stream->set_loop(loop); + ogg_vorbis_stream->set_loop_offset(loop_offset); + + return ResourceSaver::save(p_save_path + ".oggvorbisstr", ogg_vorbis_stream); } ResourceImporterOGGVorbis::ResourceImporterOGGVorbis() { diff --git a/modules/stb_vorbis/resource_importer_ogg_vorbis.h b/modules/vorbis/resource_importer_ogg_vorbis.h index 60fe3381fb..acdc1a3d38 100644 --- a/modules/stb_vorbis/resource_importer_ogg_vorbis.h +++ b/modules/vorbis/resource_importer_ogg_vorbis.h @@ -28,25 +28,29 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef RESOURCEIMPORTEROGGVORBIS_H -#define RESOURCEIMPORTEROGGVORBIS_H +#ifndef RESOURCE_IMPORTER_OGG_VORBIS_H +#define RESOURCE_IMPORTER_OGG_VORBIS_H -#include "audio_stream_ogg_vorbis.h" #include "core/io/resource_importer.h" class ResourceImporterOGGVorbis : public ResourceImporter { GDCLASS(ResourceImporterOGGVorbis, ResourceImporter); + enum { + OGG_SYNC_BUFFER_SIZE = 8192, + }; + +private: + // virtual int get_samples_in_packet(Vector<uint8_t> p_packet) = 0; + public: - virtual String get_importer_name() const override; - virtual String get_visible_name() const override; virtual void get_recognized_extensions(List<String> *p_extensions) const override; virtual String get_save_extension() const override; virtual String get_resource_type() const override; - + virtual String get_importer_name() const override; + virtual String get_visible_name() const override; virtual int get_preset_count() const override; virtual String get_preset_name(int p_idx) const override; - 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; @@ -55,4 +59,4 @@ public: ResourceImporterOGGVorbis(); }; -#endif // RESOURCEIMPORTEROGGVORBIS_H +#endif // RESOURCE_IMPORTER_OGG_VORBIS_H diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml index c53af22ae1..43a8e20ef7 100644 --- a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml +++ b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml @@ -4,7 +4,7 @@ A simple interface to create a peer-to-peer mesh network composed of [WebRTCPeerConnection] that is compatible with the [MultiplayerAPI]. </brief_description> <description> - This class constructs a full mesh of [WebRTCPeerConnection] (one connection for each peer) that can be used as a [member MultiplayerAPI.network_peer]. + This class constructs a full mesh of [WebRTCPeerConnection] (one connection for each peer) that can be used as a [member MultiplayerAPI.multiplayer_peer]. You can add each [WebRTCPeerConnection] via [method add_peer] or remove them via [method remove_peer]. Peers must be added in [constant WebRTCPeerConnection.STATE_NEW] state to allow it to create the appropriate channels. This class will not create offers nor set descriptions, it will only poll them, and notify connections and disconnections. [signal MultiplayerPeer.connection_succeeded] and [signal MultiplayerPeer.server_disconnected] will not be emitted unless [code]server_compatibility[/code] is [code]true[/code] in [method initialize]. Beside that data transfer works like in a [MultiplayerPeer]. </description> @@ -56,7 +56,7 @@ Initialize the multiplayer peer with the given [code]peer_id[/code] (must be between 1 and 2147483647). If [code]server_compatibilty[/code] is [code]false[/code] (default), the multiplayer peer will be immediately in state [constant MultiplayerPeer.CONNECTION_CONNECTED] and [signal MultiplayerPeer.connection_succeeded] will not be emitted. If [code]server_compatibilty[/code] is [code]true[/code] the peer will suppress all [signal MultiplayerPeer.peer_connected] signals until a peer with id [constant MultiplayerPeer.TARGET_PEER_SERVER] connects and then emit [signal MultiplayerPeer.connection_succeeded]. After that the signal [signal MultiplayerPeer.peer_connected] will be emitted for every already connected peer, and any new peer that might connect. If the server peer disconnects after that, signal [signal MultiplayerPeer.server_disconnected] will be emitted and state will become [constant MultiplayerPeer.CONNECTION_CONNECTED]. - You can optionally specify a [code]channels_config[/code] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel). + You can optionally specify a [code]channels_config[/code] array of [enum TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel). </description> </method> <method name="remove_peer"> @@ -69,7 +69,7 @@ </methods> <members> <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" override="true" default="false" /> - <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="MultiplayerPeer.TransferMode" default="2" /> + <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="TransferMode" default="2" /> </members> <constants> </constants> diff --git a/modules/webrtc/webrtc_multiplayer_peer.cpp b/modules/webrtc/webrtc_multiplayer_peer.cpp index 95c8c13449..d60d694df1 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.cpp +++ b/modules/webrtc/webrtc_multiplayer_peer.cpp @@ -51,11 +51,11 @@ int WebRTCMultiplayerPeer::get_transfer_channel() const { return transfer_channel; } -void WebRTCMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { +void WebRTCMultiplayerPeer::set_transfer_mode(Multiplayer::TransferMode p_mode) { transfer_mode = p_mode; } -MultiplayerPeer::TransferMode WebRTCMultiplayerPeer::get_transfer_mode() const { +Multiplayer::TransferMode WebRTCMultiplayerPeer::get_transfer_mode() const { return transfer_mode; } @@ -204,7 +204,7 @@ Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Arr ERR_FAIL_COND_V(p_self_id < 1 || p_self_id > ~(1 << 31), ERR_INVALID_PARAMETER); channels_config.clear(); for (int i = 0; i < p_channels_config.size(); i++) { - ERR_FAIL_COND_V_MSG(p_channels_config[i].get_type() != Variant::INT, ERR_INVALID_PARAMETER, "The 'channels_config' array must contain only enum values from 'MultiplayerPeer.TransferMode'"); + ERR_FAIL_COND_V_MSG(p_channels_config[i].get_type() != Variant::INT, ERR_INVALID_PARAMETER, "The 'channels_config' array must contain only enum values from 'MultiplayerPeer.Multiplayer::TransferMode'"); int mode = p_channels_config[i].operator int(); // Initialize data channel configurations. Dictionary cfg; @@ -213,17 +213,17 @@ Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Arr cfg["ordered"] = true; switch (mode) { - case TRANSFER_MODE_UNRELIABLE_ORDERED: + case Multiplayer::TRANSFER_MODE_ORDERED: cfg["maxPacketLifetime"] = 1; break; - case TRANSFER_MODE_UNRELIABLE: + case Multiplayer::TRANSFER_MODE_UNRELIABLE: cfg["maxPacketLifetime"] = 1; cfg["ordered"] = false; break; - case TRANSFER_MODE_RELIABLE: + case Multiplayer::TRANSFER_MODE_RELIABLE: break; default: - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("The 'channels_config' array must contain only enum values from 'MultiplayerPeer.TransferMode'. Got: %d", mode)); + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("The 'channels_config' array must contain only enum values from 'MultiplayerPeer.Multiplayer::TransferMode'. Got: %d", mode)); } channels_config.push_back(cfg); } @@ -355,13 +355,13 @@ Error WebRTCMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_si int ch = transfer_channel; if (ch == 0) { switch (transfer_mode) { - case TRANSFER_MODE_RELIABLE: + case Multiplayer::TRANSFER_MODE_RELIABLE: ch = CH_RELIABLE; break; - case TRANSFER_MODE_UNRELIABLE_ORDERED: + case Multiplayer::TRANSFER_MODE_ORDERED: ch = CH_ORDERED; break; - case TRANSFER_MODE_UNRELIABLE: + case Multiplayer::TRANSFER_MODE_UNRELIABLE: ch = CH_UNRELIABLE; break; } diff --git a/modules/webrtc/webrtc_multiplayer_peer.h b/modules/webrtc/webrtc_multiplayer_peer.h index ef4fe1678c..80a6491492 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.h +++ b/modules/webrtc/webrtc_multiplayer_peer.h @@ -31,7 +31,7 @@ #ifndef WEBRTC_MULTIPLAYER_H #define WEBRTC_MULTIPLAYER_H -#include "core/io/multiplayer_peer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "webrtc_peer_connection.h" class WebRTCMultiplayerPeer : public MultiplayerPeer { @@ -68,7 +68,7 @@ private: bool refuse_connections = false; ConnectionStatus connection_status = CONNECTION_DISCONNECTED; int transfer_channel = 0; - TransferMode transfer_mode = TRANSFER_MODE_RELIABLE; + Multiplayer::TransferMode transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; int next_packet_peer = 0; bool server_compat = false; @@ -99,8 +99,8 @@ public: // MultiplayerPeer void set_transfer_channel(int p_channel) override; int get_transfer_channel() const override; - void set_transfer_mode(TransferMode p_mode) override; - TransferMode get_transfer_mode() const override; + void set_transfer_mode(Multiplayer::TransferMode p_mode) override; + Multiplayer::TransferMode get_transfer_mode() const override; void set_target_peer(int p_peer_id) override; int get_unique_id() const override; diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml index 1549a907b4..b5202469f1 100644 --- a/modules/websocket/doc_classes/WebSocketClient.xml +++ b/modules/websocket/doc_classes/WebSocketClient.xml @@ -5,7 +5,7 @@ </brief_description> <description> This class implements a WebSocket client compatible with any RFC 6455-compliant WebSocket server. - This client can be optionally used as a network peer for the [MultiplayerAPI]. + This client can be optionally used as a multiplayer peer for the [MultiplayerAPI]. After starting the client ([method connect_to_url]), you will need to [method MultiplayerPeer.poll] it at regular intervals (e.g. inside [method Node._process]). You will receive appropriate signals when connecting, disconnecting, or when new data is available. </description> @@ -20,7 +20,7 @@ <argument index="3" name="custom_headers" type="PackedStringArray" default="PackedStringArray()" /> <description> Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. If the list empty (default), no sub-protocol will be requested. - If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a network peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted. + If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a multiplayer peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted. If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.) on the [WebSocketPeer] returned via [code]get_peer(1)[/code] and not on this object directly (e.g. [code]get_peer(1).put_packet(data)[/code]). You can optionally pass a list of [code]custom_headers[/code] to be added to the handshake HTTP request. [b]Note:[/b] To avoid mixed content warnings or errors in HTML5, you may have to use a [code]url[/code] that starts with [code]wss://[/code] (secure) instead of [code]ws://[/code]. When doing so, make sure to use the fully qualified domain name that matches the one defined in the server's SSL certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the SSL certificate. diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml index cd41e9a1fb..c7a0ca100f 100644 --- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml +++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml @@ -4,7 +4,7 @@ Base class for WebSocket server and client. </brief_description> <description> - Base class for WebSocket server and client, allowing them to be used as network peer for the [MultiplayerAPI]. + Base class for WebSocket server and client, allowing them to be used as multiplayer peer for the [MultiplayerAPI]. </description> <tutorials> </tutorials> @@ -32,7 +32,7 @@ </methods> <members> <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" override="true" default="false" /> - <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="MultiplayerPeer.TransferMode" default="2" /> + <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="TransferMode" default="2" /> </members> <signals> <signal name="peer_packet"> diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index 90182de4c2..b66a1054ab 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -55,7 +55,7 @@ <description> Starts listening on the given port. You can specify the desired subprotocols via the "protocols" array. If the list empty (default), no sub-protocol will be requested. - If [code]true[/code] is passed as [code]gd_mp_api[/code], the server will behave like a network peer for the [MultiplayerAPI], connections from non-Godot clients will not work, and [signal data_received] will not be emitted. + If [code]true[/code] is passed as [code]gd_mp_api[/code], the server will behave like a multiplayer peer for the [MultiplayerAPI], connections from non-Godot clients will not work, and [signal data_received] will not be emitted. If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.), on the [WebSocketPeer] returned via [code]get_peer(id)[/code] to communicate with the peer with given [code]id[/code] (e.g. [code]get_peer(id).get_available_packet_count[/code]). </description> </method> diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index 163cc7706b..7464cf2bf5 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -113,13 +113,13 @@ int WebSocketMultiplayerPeer::get_transfer_channel() const { return 0; } -void WebSocketMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { +void WebSocketMultiplayerPeer::set_transfer_mode(Multiplayer::TransferMode p_mode) { // Websocket uses TCP, reliable } -MultiplayerPeer::TransferMode WebSocketMultiplayerPeer::get_transfer_mode() const { +Multiplayer::TransferMode WebSocketMultiplayerPeer::get_transfer_mode() const { // Websocket uses TCP, reliable - return TRANSFER_MODE_RELIABLE; + return Multiplayer::TRANSFER_MODE_RELIABLE; } void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) { diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h index 0fee196f41..d97a599fe9 100644 --- a/modules/websocket/websocket_multiplayer_peer.h +++ b/modules/websocket/websocket_multiplayer_peer.h @@ -32,7 +32,7 @@ #define WEBSOCKET_MULTIPLAYER_PEER_H #include "core/error/error_list.h" -#include "core/io/multiplayer_peer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "core/templates/list.h" #include "websocket_peer.h" @@ -80,8 +80,8 @@ public: /* MultiplayerPeer */ void set_transfer_channel(int p_channel) override; int get_transfer_channel() const override; - void set_transfer_mode(TransferMode p_mode) override; - TransferMode get_transfer_mode() const override; + void set_transfer_mode(Multiplayer::TransferMode p_mode) override; + Multiplayer::TransferMode get_transfer_mode() const override; void set_target_peer(int p_target_peer) override; int get_packet_peer() const override; int get_unique_id() const override; diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index 49997b42d3..26c0176ea4 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -161,22 +161,28 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER); _peer = Ref<WSLPeer>(memnew(WSLPeer)); - IPAddress addr; - if (!p_host.is_valid_ip_address()) { - addr = IP::get_singleton()->resolve_hostname(p_host); + if (p_host.is_valid_ip_address()) { + ip_candidates.clear(); + ip_candidates.push_back(IPAddress(p_host)); } else { - addr = p_host; + ip_candidates = IP::get_singleton()->resolve_hostname_addresses(p_host); } - ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(ip_candidates.is_empty(), ERR_INVALID_PARAMETER); String port = ""; if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { port = ":" + itos(p_port); } - Error err = _tcp->connect_to_host(addr, p_port); + Error err = ERR_BUG; // Should be at least one entry. + while (ip_candidates.size() > 0) { + err = _tcp->connect_to_host(ip_candidates.pop_front(), p_port); + if (err == OK) { + break; + } + } if (err != OK) { _tcp->disconnect_from_host(); _on_error(); @@ -185,6 +191,7 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, _connection = _tcp; _use_ssl = p_ssl; _host = p_host; + _port = p_port; // Strip edges from protocols. _protocols.resize(p_protocols.size()); String *pw = _protocols.ptrw(); @@ -244,6 +251,7 @@ void WSLClient::poll() { _on_error(); break; case StreamPeerTCP::STATUS_CONNECTED: { + ip_candidates.clear(); Ref<StreamPeerSSL> ssl; if (_use_ssl) { if (_connection == _tcp) { @@ -274,6 +282,12 @@ void WSLClient::poll() { _do_handshake(); } break; case StreamPeerTCP::STATUS_ERROR: + while (ip_candidates.size() > 0) { + _tcp->disconnect_from_host(); + if (_tcp->connect_to_host(ip_candidates.pop_front(), _port) == OK) { + return; + } + } disconnect_from_host(); _on_error(); break; @@ -315,6 +329,8 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) { memset(_resp_buf, 0, sizeof(_resp_buf)); _resp_pos = 0; + + ip_candidates.clear(); } IPAddress WSLClient::get_connected_host() const { diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h index 849639ee8b..3972977910 100644 --- a/modules/websocket/wsl_client.h +++ b/modules/websocket/wsl_client.h @@ -63,6 +63,8 @@ private: String _key; String _host; + int _port; + Array ip_candidates; Vector<String> _protocols; bool _use_ssl = false; diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h index 41a690f473..7aac0a6508 100644 --- a/modules/webxr/godot_webxr.h +++ b/modules/webxr/godot_webxr.h @@ -62,7 +62,7 @@ extern void godot_webxr_initialize( extern void godot_webxr_uninitialize(); extern int godot_webxr_get_view_count(); -extern int *godot_webxr_get_render_targetsize(); +extern int *godot_webxr_get_render_target_size(); extern float *godot_webxr_get_transform_for_eye(int p_eye); extern float *godot_webxr_get_projection_for_eye(int p_eye); extern int godot_webxr_get_external_texture_for_eye(int p_eye); diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index 6e19a8ac6e..c4b21defce 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -406,9 +406,9 @@ const GodotWebXR = { return GodotWebXR.pose.views.length; }, - godot_webxr_get_render_targetsize__proxy: 'sync', - godot_webxr_get_render_targetsize__sig: 'i', - godot_webxr_get_render_targetsize: function () { + godot_webxr_get_render_target_size__proxy: 'sync', + godot_webxr_get_render_target_size__sig: 'i', + godot_webxr_get_render_target_size: function () { if (!GodotWebXR.session || !GodotWebXR.pose) { return 0; } diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index 099e769303..2d699961ae 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -199,7 +199,7 @@ StringName WebXRInterfaceJS::get_name() const { return "WebXR"; }; -int WebXRInterfaceJS::get_capabilities() const { +uint32_t WebXRInterfaceJS::get_capabilities() const { return XRInterface::XR_STEREO | XRInterface::XR_MONO; }; @@ -254,9 +254,9 @@ bool WebXRInterfaceJS::initialize() { void WebXRInterfaceJS::uninitialize() { if (initialized) { XRServer *xr_server = XRServer::get_singleton(); - if (xr_server != nullptr) { + if (xr_server != nullptr && xr_server->get_primary_interface() == this) { // no longer our primary interface - xr_server->clear_primary_interface_if(this); + xr_server->set_primary_interface(nullptr); } godot_webxr_uninitialize(); @@ -285,12 +285,12 @@ Transform3D WebXRInterfaceJS::_js_matrix_to_transform(float *p_js_matrix) { return transform; } -Size2 WebXRInterfaceJS::get_render_targetsize() { +Size2 WebXRInterfaceJS::get_render_target_size() { if (render_targetsize.width != 0 && render_targetsize.height != 0) { return render_targetsize; } - int *js_size = godot_webxr_get_render_targetsize(); + int *js_size = godot_webxr_get_render_target_size(); if (!initialized || js_size == nullptr) { // As a temporary default (until WebXR is fully initialized), use half the window size. Size2 temp = DisplayServer::get_singleton()->window_get_size(); @@ -365,20 +365,6 @@ CameraMatrix WebXRInterfaceJS::get_projection_for_view(uint32_t p_view, real_t p return eye; } -unsigned int WebXRInterfaceJS::get_external_texture_for_eye(XRInterface::Eyes p_eye) { - if (!initialized) { - return 0; - } - return godot_webxr_get_external_texture_for_eye(p_eye); -} - -void WebXRInterfaceJS::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) { - if (!initialized) { - return; - } - godot_webxr_commit_for_eye(p_eye); -} - Vector<BlitToScreen> WebXRInterfaceJS::commit_views(RID p_render_target, const Rect2 &p_screen_rect) { Vector<BlitToScreen> blit_to_screen; @@ -474,10 +460,6 @@ void WebXRInterfaceJS::_on_controller_changed() { } } -void WebXRInterfaceJS::notification(int p_what) { - // Nothing to do here. -} - WebXRInterfaceJS::WebXRInterfaceJS() { initialized = false; session_mode = "inline"; diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index f9368582b7..82307190db 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -76,23 +76,20 @@ public: virtual PackedVector3Array get_bounds_geometry() const override; virtual StringName get_name() const override; - virtual int get_capabilities() const override; + virtual uint32_t get_capabilities() const override; virtual bool is_initialized() const override; virtual bool initialize() override; virtual void uninitialize() override; - virtual Size2 get_render_targetsize() override; + virtual Size2 get_render_target_size() override; virtual uint32_t get_view_count() override; virtual Transform3D get_camera_transform() override; virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; virtual CameraMatrix get_projection_for_view(uint32_t p_view, real_t p_aspect, real_t p_z_near, real_t p_z_far) override; - virtual unsigned int get_external_texture_for_eye(XRInterface::Eyes p_eye) override; - virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) override; virtual Vector<BlitToScreen> commit_views(RID p_render_target, const Rect2 &p_screen_rect) override; virtual void process() override; - virtual void notification(int p_what) override; void _on_controller_changed(); diff --git a/platform/android/README.md b/platform/android/README.md new file mode 100644 index 0000000000..343e588553 --- /dev/null +++ b/platform/android/README.md @@ -0,0 +1,14 @@ +# Android platform port + +This folder contains the Java and C++ (JNI) code for the Android platform port, +using [Gradle](https://gradle.org/) as a build system. + +## Artwork license + +[`logo.png`](logo.png) and [`run_icon.png`](run_icon.png) are licensed under +[Creative Commons Attribution 3.0 Unported](https://developer.android.com/distribute/marketing-tools/brand-guidelines#android_robot) +per the Android logo usage guidelines: + +> The Android robot is reproduced or modified from work created and shared by +> Google and used according to terms described in the Creative Commons 3.0 +> Attribution License. diff --git a/platform/android/detect.py b/platform/android/detect.py index 7a993e9ca6..61ccad9ac3 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -93,7 +93,7 @@ def configure(env): install_ndk_if_needed(env) # Workaround for MinGW. See: - # http://www.scons.org/wiki/LongCmdLinesOnWin32 + # https://www.scons.org/wiki/LongCmdLinesOnWin32 if os.name == "nt": import subprocess diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 0bae090702..0eeee8215d 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -161,7 +161,7 @@ bool DirAccessJAndroid::dir_exists(String p_dir) { if (current_dir == "") sd = p_dir; else { - if (p_dir.is_rel_path()) + if (p_dir.is_relative_path()) sd = current_dir.plus_file(p_dir); else sd = fix_path(p_dir); diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index fc86abb6f1..8df61831c2 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -48,6 +48,8 @@ void register_android_exporter() { EDITOR_DEF("export/android/shutdown_adb_on_exit", true); + EDITOR_DEF("export/android/one_click_deploy_clear_previous_install", false); + Ref<EditorExportPlatformAndroid> exporter = Ref<EditorExportPlatformAndroid>(memnew(EditorExportPlatformAndroid)); EditorExport::get_singleton()->add_export_platform(exporter); } diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 8ed2d4e60a..e5422a28af 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -977,6 +977,11 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p Vector<int> feature_versions; if (xr_mode_index == 1 /* XRMode.OVR */) { + // Set degrees of freedom + feature_names.push_back("android.hardware.vr.headtracking"); + feature_required_list.push_back(true); + feature_versions.push_back(1); + // Check for hand tracking int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required if (hand_tracking_index > 0) { @@ -1646,8 +1651,6 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname")); @@ -1794,7 +1797,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int rv; String output; - bool remove_prev = p_preset->get("one_click_deploy/clear_previous_install"); + bool remove_prev = EDITOR_GET("export/android/one_click_deploy_clear_previous_install"); String version_name = p_preset->get("version/name"); String package_name = p_preset->get("package/unique_name"); @@ -2611,7 +2614,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String export_filename = p_path.get_file(); String export_path = p_path.get_base_dir(); - if (export_path.is_rel_path()) { + if (export_path.is_relative_path()) { export_path = OS::get_singleton()->get_resource_dir().plus_file(export_path); } export_path = ProjectSettings::get_singleton()->globalize_path(export_path).simplify_path(); @@ -2939,7 +2942,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP void EditorExportPlatformAndroid::get_platform_features(List<String> *r_features) { r_features->push_back("mobile"); - r_features->push_back("Android"); + r_features->push_back("android"); } void EditorExportPlatformAndroid::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index b9e28a7937..6fbdf73cd0 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -197,6 +197,8 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) { String manifest_xr_features; bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; if (uses_xr) { + manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n"; + int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required if (hand_tracking_index == 1) { manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n"; @@ -228,7 +230,9 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { "tools:replace=\"android:screenOrientation\" " "android:screenOrientation=\"%s\">\n", orientation); - if (!uses_xr) { + if (uses_xr) { + manifest_activity_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"true\" />\n"; + } else { manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n"; } manifest_activity_text += " </activity>\n"; diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 00e01884cf..d7bf6cef30 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -49,6 +49,10 @@ <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> + + <!-- Enable access to OpenXR on Oculus mobile devices, no-op on other Android + platforms. --> + <category android:name="com.oculus.intent.category.VR" /> </intent-filter> </activity> diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 18e07c3762..9640887399 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -34,9 +34,8 @@ allprojects { } dependencies { - implementation libraries.supportCoreUtils implementation libraries.kotlinStdLib - implementation libraries.v4Support + implementation libraries.androidxFragment if (rootProject.findProject(":lib")) { implementation project(":lib") diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index fad64c675f..fcee54e493 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -4,9 +4,8 @@ ext.versions = [ minSdk : 19, targetSdk : 30, buildTools : '30.0.3', - supportCoreUtils : '1.0.0', kotlinVersion : '1.5.10', - v4Support : '1.0.0', + fragmentVersion : '1.3.6', javaVersion : 1.8, ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated. @@ -14,10 +13,9 @@ ext.versions = [ ext.libraries = [ androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin", - supportCoreUtils : "androidx.legacy:legacy-support-core-utils:$versions.supportCoreUtils", kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion", kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlinVersion", - v4Support : "androidx.legacy:legacy-support-v4:$versions.v4Support" + androidxFragment : "androidx.fragment:fragment:$versions.fragmentVersion", ] ext.getExportPackageName = { -> diff --git a/platform/android/java/app/gradle.properties b/platform/android/java/app/gradle.properties index 19587bd81f..0ad8e611ca 100644 --- a/platform/android/java/app/gradle.properties +++ b/platform/android/java/app/gradle.properties @@ -4,7 +4,7 @@ # where otherwise specified. # For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html +# https://www.gradle.org/docs/current/userguide/build_environment.html android.enableJetifier=true android.useAndroidX=true @@ -15,7 +15,7 @@ org.gradle.jvmargs=-Xmx4536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# https://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true org.gradle.warning.mode=all diff --git a/platform/android/java/gradle.properties b/platform/android/java/gradle.properties index b51a19a005..5cd94e85d9 100644 --- a/platform/android/java/gradle.properties +++ b/platform/android/java/gradle.properties @@ -7,7 +7,7 @@ # any settings specified in this file. # For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html +# https://www.gradle.org/docs/current/userguide/build_environment.html android.enableJetifier=true android.useAndroidX=true @@ -18,7 +18,7 @@ org.gradle.jvmargs=-Xmx4536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# https://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true org.gradle.warning.mode=all diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 663ba73d40..fbed4ed078 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -2,9 +2,8 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' dependencies { - implementation libraries.supportCoreUtils implementation libraries.kotlinStdLib - implementation libraries.v4Support + implementation libraries.androidxFragment } def pathToRootDir = "../../../../" diff --git a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java index 2a72c9818d..9aa65fd786 100644 --- a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java @@ -54,7 +54,7 @@ public class Helpers { /* * Parse the Content-Disposition HTTP Header. The format of the header is defined here: - * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for + * https://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for * content that is going to be downloaded to the file system. We only support the attachment * type. */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 896b169953..70bc73b9ad 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -295,7 +295,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { View pluginView = plugin.onMainCreate(activity); if (pluginView != null) { - containerLayout.addView(pluginView); + if (plugin.shouldBeOnTop()) { + containerLayout.addView(pluginView); + } else { + containerLayout.addView(pluginView, 0); + } } } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java index 2dc8359615..4536c21ed3 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java @@ -191,6 +191,9 @@ public abstract class GodotPlugin { * The plugin can return a non-null {@link View} layout in order to add it to the Godot view * hierarchy. * + * Use shouldBeOnTop() to set whether the plugin's {@link View} should be added on top or behind + * the main Godot view. + * * @see Activity#onCreate(Bundle) * @return the plugin's view to be included; null if no views should be included. */ @@ -311,6 +314,17 @@ public abstract class GodotPlugin { } /** + * Returns whether the plugin's {@link View} returned in onMainCreate() should be placed on + * top of the main Godot view. + * + * Returning false causes the plugin's {@link View} to be placed behind, which can be useful + * when used with transparency in order to let the Godot view handle inputs. + */ + public boolean shouldBeOnTop() { + return true; + } + + /** * Runs the specified action on the UI thread. If the current thread is the UI * thread, then the action is executed immediately. If the current thread is * not the UI thread, the action is posted to the event queue of the UI thread. diff --git a/platform/android/logo.png b/platform/android/logo.png Binary files differindex f44d360a25..9c8be93646 100644 --- a/platform/android/logo.png +++ b/platform/android/logo.png diff --git a/platform/iphone/export/export_plugin.h b/platform/iphone/export/export_plugin.h index 8d3af6e057..359f855d86 100644 --- a/platform/iphone/export/export_plugin.h +++ b/platform/iphone/export/export_plugin.h @@ -204,7 +204,7 @@ public: virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("mobile"); - r_features->push_back("iOS"); + r_features->push_back("ios"); } virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { diff --git a/platform/javascript/README.md b/platform/javascript/README.md new file mode 100644 index 0000000000..f181bea9e0 --- /dev/null +++ b/platform/javascript/README.md @@ -0,0 +1,15 @@ +# HTML5 platform port + +This folder contains the C++ and JavaScript code for the HTML5/WebAssembly platform port, +compiled using [Emscripten](https://emscripten.org/). + +It also contains a ESLint linting setup (see [`package.json`](package.json)). + +See also [`misc/dist/html`](/misc/dist/html) folder for files used by this platform +such as the HTML5 shell. + +## Artwork license + +[`logo.png`](logo.png) and [`run_icon.png`](run_icon.png) are licensed under +[Creative Commons Attribution 3.0 Unported](https://www.w3.org/html/logo/faq.html#how-licenced) +per the HTML5 logo usage guidelines. diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp index 54f541f607..c50195639c 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.cpp +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -35,6 +35,7 @@ #include "core/config/project_settings.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" +#include "core/os/time.h" #include "editor/editor_node.h" #include <emscripten/emscripten.h> @@ -58,23 +59,40 @@ JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin(EditorNode *p_editor) { void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { if (!Engine::get_singleton() || !Engine::get_singleton()->is_editor_hint()) { - WARN_PRINT("Project download is only available in Editor mode"); + ERR_PRINT("Downloading the project as a ZIP archive is only available in Editor mode."); return; } String resource_path = ProjectSettings::get_singleton()->get_resource_path(); FileAccess *src_f; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); - zipFile zip = zipOpen2("/tmp/project.zip", APPEND_STATUS_CREATE, nullptr, &io); - String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/"; + + // Name the downloded ZIP file to contain the project name and download date for easier organization. + // Replace characters not allowed (or risky) in Windows file names with safe characters. + // In the project name, all invalid characters become an empty string so that a name + // like "Platformer 2: Godette's Revenge" becomes "platformer_2-_godette-s_revenge". + const String project_name_safe = + GLOBAL_GET("application/config/name").to_lower().replace(" ", "_"); + const String datetime_safe = + Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_"); + const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip")); + const String output_path = String("/tmp").plus_file(output_name); + + zipFile zip = zipOpen2(output_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io); + const String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/"; _zip_recursive(resource_path, base_path, zip); zipClose(zip, nullptr); - FileAccess *f = FileAccess::open("/tmp/project.zip", FileAccess::READ); - ERR_FAIL_COND_MSG(!f, "Unable to create zip file"); + FileAccess *f = FileAccess::open(output_path, FileAccess::READ); + ERR_FAIL_COND_MSG(!f, "Unable to create ZIP file."); Vector<uint8_t> buf; buf.resize(f->get_length()); f->get_buffer(buf.ptrw(), buf.size()); - godot_js_os_download_buffer(buf.ptr(), buf.size(), "project.zip", "application/zip"); + godot_js_os_download_buffer(buf.ptr(), buf.size(), output_name.utf8().get_data(), "application/zip"); + + f->close(); + memdelete(f); + // Remove the temporary file since it was sent to the user's native filesystem as a download. + DirAccess::remove_file_or_error(output_path); } void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) { @@ -108,7 +126,7 @@ void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, z void JavaScriptToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zipFile p_zip) { DirAccess *dir = DirAccess::open(p_path); if (!dir) { - WARN_PRINT("Unable to open dir for zipping: " + p_path); + WARN_PRINT("Unable to open directory for zipping: " + p_path); return; } dir->list_dir_begin(); diff --git a/platform/javascript/export/export_plugin.h b/platform/javascript/export/export_plugin.h index 736edfe3a8..bdd1235259 100644 --- a/platform/javascript/export/export_plugin.h +++ b/platform/javascript/export/export_plugin.h @@ -134,7 +134,7 @@ public: virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("web"); - r_features->push_back(get_os_name()); + r_features->push_back(get_os_name().to_lower()); } virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 76102d941b..95c5909d50 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -137,12 +137,12 @@ int OS_JavaScript::get_processor_count() const { } bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { - if (p_feature == "HTML5" || p_feature == "web") { + if (p_feature == "html5" || p_feature == "web") { return true; } #ifdef JAVASCRIPT_EVAL_ENABLED - if (p_feature == "JavaScript") { + if (p_feature == "javascript") { return true; } #endif diff --git a/platform/linuxbsd/README.md b/platform/linuxbsd/README.md new file mode 100644 index 0000000000..0d3fb37be5 --- /dev/null +++ b/platform/linuxbsd/README.md @@ -0,0 +1,11 @@ +# Linux/*BSD platform port + +This folder contains the C++ code for the Linux/*BSD platform port. + +## Artwork license + +[`logo.png`](logo.png) is derived from the [Linux logo](https://isc.tamu.edu/~lewing/linux/): + +> Permission to use and/or modify this image is granted provided you acknowledge me + <lewing@isc.tamu.edu> and [The GIMP](https://isc.tamu.edu/~lewing/gimp/) + if someone asks. diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index ea0222cb19..0e98af71fa 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -32,6 +32,8 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +#include "core/version.h" +#include "core/version_hash.gen.h" #include "main/main.h" #ifdef DEBUG_ENABLED @@ -61,12 +63,19 @@ static void handle_crash(int sig) { } // Dump the backtrace to stderr with a message to the user + fprintf(stderr, "\n================================================================\n"); fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig); if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); } + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. + if (String(VERSION_HASH).length() != 0) { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + } else { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); if (strings) { @@ -115,6 +124,7 @@ static void handle_crash(int sig) { free(strings); } fprintf(stderr, "-- END OF BACKTRACE --\n"); + fprintf(stderr, "================================================================\n"); // Abort to pass the error to the OS abort(); diff --git a/platform/linuxbsd/export/export.cpp b/platform/linuxbsd/export/export.cpp index 5c6be2d7d4..965a38ef4e 100644 --- a/platform/linuxbsd/export/export.cpp +++ b/platform/linuxbsd/export/export.cpp @@ -53,7 +53,7 @@ void register_linuxbsd_exporter() { platform->set_debug_32("linux_x11_32_debug"); platform->set_release_64("linux_x11_64_release"); platform->set_debug_64("linux_x11_64_debug"); - platform->set_os_name("X11"); + platform->set_os_name("LinuxBSD"); platform->set_chmod_flags(0755); platform->set_fixup_embedded_pck_func(&fixup_embedded_pck); diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 0f128d504f..31228b10b4 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -32,6 +32,8 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +#include "core/version.h" +#include "core/version_hash.gen.h" #include "main/main.h" #include <string.h> @@ -85,11 +87,18 @@ static void handle_crash(int sig) { } // Dump the backtrace to stderr with a message to the user + fprintf(stderr, "\n================================================================\n"); fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig); if (OS::get_singleton()->get_main_loop()) OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. + if (String(VERSION_HASH).length() != 0) { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + } else { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); if (strings) { @@ -148,6 +157,7 @@ static void handle_crash(int sig) { free(strings); } fprintf(stderr, "-- END OF BACKTRACE --\n"); + fprintf(stderr, "================================================================\n"); // Abort to pass the error to the OS abort(); diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 43b7d7c1e0..f037d75fbd 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -190,6 +190,8 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; + // Force window resize event. + [self windowDidResize:notification]; } - (void)windowDidExitFullScreen:(NSNotification *)notification { @@ -217,6 +219,8 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { if (wd.on_top) { [wd.window_object setLevel:NSFloatingWindowLevel]; } + // Force window resize event. + [self windowDidResize:notification]; } - (void)windowDidChangeBackingProperties:(NSNotification *)notification { diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp index 5b959d6da4..54a3104482 100644 --- a/platform/osx/export/export_plugin.cpp +++ b/platform/osx/export/export_plugin.cpp @@ -931,7 +931,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String zipfi.tmz_date.tm_hour = time.hour; zipfi.tmz_date.tm_mday = date.day; zipfi.tmz_date.tm_min = time.minute; - zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/ + zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ zipfi.tmz_date.tm_sec = time.second; zipfi.tmz_date.tm_year = date.year; zipfi.dosDate = 0; @@ -976,7 +976,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String zipfi.tmz_date.tm_hour = time.hour; zipfi.tmz_date.tm_mday = date.day; zipfi.tmz_date.tm_min = time.minute; - zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/ + zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ zipfi.tmz_date.tm_sec = time.second; zipfi.tmz_date.tm_year = date.year; zipfi.dosDate = 0; diff --git a/platform/osx/export/export_plugin.h b/platform/osx/export/export_plugin.h index cd85ce2aad..ca5086622e 100644 --- a/platform/osx/export/export_plugin.h +++ b/platform/osx/export/export_plugin.h @@ -115,7 +115,7 @@ public: virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("pc"); r_features->push_back("s3tc"); - r_features->push_back("macOS"); + r_features->push_back("macos"); } virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp index 5bd00b1549..a54b85a803 100644 --- a/platform/uwp/export/export_plugin.cpp +++ b/platform/uwp/export/export_plugin.cpp @@ -485,7 +485,7 @@ Error EditorExportPlatformUWP::export_project(const Ref<EditorExportPreset> &p_p void EditorExportPlatformUWP::get_platform_features(List<String> *r_features) { r_features->push_back("pc"); - r_features->push_back("UWP"); + r_features->push_back("uwp"); } void EditorExportPlatformUWP::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index e2d507eddd..1b4dae207f 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -32,6 +32,8 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +#include "core/version.h" +#include "core/version_hash.gen.h" #include "main/main.h" #ifdef CRASH_HANDLER_EXCEPTION @@ -127,6 +129,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { return EXCEPTION_CONTINUE_SEARCH; } + fprintf(stderr, "\n================================================================\n"); fprintf(stderr, "%s: Program crashed\n", __FUNCTION__); if (OS::get_singleton()->get_main_loop()) @@ -175,6 +178,12 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { msg = proj_settings->get("debug/settings/crash_handler/message"); } + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. + if (String(VERSION_HASH).length() != 0) { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + } else { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); int n = 0; @@ -200,6 +209,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { } while (frame.AddrReturn.Offset != 0 && n < 256); fprintf(stderr, "-- END OF BACKTRACE --\n"); + fprintf(stderr, "================================================================\n"); SymCleanup(process); diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 9154f7749e..3961480d23 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -321,7 +321,7 @@ def configure_msvc(env, manual_msvc_config): def configure_mingw(env): # Workaround for MinGW. See: - # http://www.scons.org/wiki/LongCmdLinesOnWin32 + # https://www.scons.org/wiki/LongCmdLinesOnWin32 env.use_windows_spawn_fix() ## Build type diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index b6489e7a95..1723026849 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -80,7 +80,7 @@ String DisplayServerWindows::get_name() const { } 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) { + if (windows.has(MAIN_WINDOW_ID) && (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN)) { // Mouse is grabbed (captured or confined). WindowData &wd = windows[MAIN_WINDOW_ID]; @@ -118,8 +118,10 @@ void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) { _THREAD_SAFE_METHOD_ - if (mouse_mode == p_mode) + if (mouse_mode == p_mode) { + // Already in the same mode; do nothing. return; + } mouse_mode = p_mode; @@ -134,7 +136,7 @@ void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) { _THREAD_SAFE_METHOD_ if (!windows.has(last_focused_window)) { - return; //no window focused? + return; // No focused window? } if (mouse_mode == MOUSE_MODE_CAPTURED) { @@ -154,7 +156,6 @@ Point2i DisplayServerWindows::mouse_get_position() const { POINT p; GetCursorPos(&p); return Point2i(p.x, p.y); - //return Point2(old_x, old_y); } MouseButton DisplayServerWindows::mouse_get_button_state() const { @@ -165,12 +166,12 @@ void DisplayServerWindows::clipboard_set(const String &p_text) { _THREAD_SAFE_METHOD_ if (!windows.has(last_focused_window)) { - return; //no window focused? + return; // No focused window? } - // Convert LF line endings to CRLF in clipboard content - // Otherwise, line endings won't be visible when pasted in other software - String text = p_text.replace("\r\n", "\n").replace("\n", "\r\n"); // avoid \r\r\n + // Convert LF line endings to CRLF in clipboard content. + // Otherwise, line endings won't be visible when pasted in other software. + String text = p_text.replace("\r\n", "\n").replace("\n", "\r\n"); // Avoid \r\r\n. if (!OpenClipboard(windows[last_focused_window].hWnd)) { ERR_FAIL_MSG("Unable to open clipboard."); @@ -187,7 +188,7 @@ void DisplayServerWindows::clipboard_set(const String &p_text) { SetClipboardData(CF_UNICODETEXT, mem); - // set the CF_TEXT version (not needed?) + // Set the CF_TEXT version (not needed?). CharString utf8 = text.utf8(); mem = GlobalAlloc(GMEM_MOVEABLE, utf8.length() + 1); ERR_FAIL_COND_MSG(mem == nullptr, "Unable to allocate memory for clipboard contents."); @@ -206,7 +207,7 @@ String DisplayServerWindows::clipboard_get() const { _THREAD_SAFE_METHOD_ if (!windows.has(last_focused_window)) { - return String(); //no window focused? + return String(); // No focused window? } String ret; @@ -498,16 +499,18 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod } void DisplayServerWindows::show_window(WindowID p_id) { + ERR_FAIL_COND(!windows.has(p_id)); + WindowData &wd = windows[p_id]; if (p_id != MAIN_WINDOW_ID) { _update_window_style(p_id); } - ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window + ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window. if (!wd.no_focus) { - SetForegroundWindow(wd.hWnd); // Slightly Higher Priority - SetFocus(wd.hWnd); // Sets Keyboard Focus To + SetForegroundWindow(wd.hWnd); // Slightly higher priority. + SetFocus(wd.hWnd); // Set keyboard focus. } } @@ -606,6 +609,8 @@ void DisplayServerWindows::window_set_mouse_passthrough(const Vector<Vector2> &p } void DisplayServerWindows::_update_window_mouse_passthrough(WindowID p_window) { + ERR_FAIL_COND(!windows.has(p_window)); + if (windows[p_window].mpath.size() == 0) { SetWindowRgn(windows[p_window].hWnd, nullptr, TRUE); } else { @@ -664,16 +669,11 @@ Point2i DisplayServerWindows::window_get_position(WindowID p_window) const { ClientToScreen(wd.hWnd, &point); return Point2i(point.x, point.y); - -#if 0 - //do not use this method, as it includes windows decorations - RECT r; - GetWindowRect(wd.hWnd, &r); - return Point2(r.left, r.top); -#endif } void DisplayServerWindows::_update_real_mouse_position(WindowID p_window) { + ERR_FAIL_COND(!windows.has(p_window)); + POINT mouse_pos; if (GetCursorPos(&mouse_pos) && ScreenToClient(windows[p_window].hWnd, &mouse_pos)) { if (mouse_pos.x > 0 && mouse_pos.y > 0 && mouse_pos.x <= windows[p_window].width && mouse_pos.y <= windows[p_window].height) { @@ -691,14 +691,9 @@ void DisplayServerWindows::window_set_position(const Point2i &p_position, Window ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - if (wd.fullscreen) + if (wd.fullscreen) { return; -#if 0 - //wrong needs to account properly for decorations - RECT r; - GetWindowRect(wd.hWnd, &r); - MoveWindow(wd.hWnd, p_position.x, p_position.y, r.right - r.left, r.bottom - r.top, TRUE); -#else + } RECT rc; rc.left = p_position.x; @@ -711,8 +706,8 @@ void DisplayServerWindows::window_set_position(const Point2i &p_position, Window AdjustWindowRectEx(&rc, style, false, exStyle); MoveWindow(wd.hWnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); -#endif - // Don't let the mouse leave the window when moved + + // Don't let the mouse leave the window when moved. if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { RECT rect; GetClientRect(wd.hWnd, &rect); @@ -729,16 +724,15 @@ void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_pa _THREAD_SAFE_METHOD_ ERR_FAIL_COND(p_window == p_parent); - ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd_window = windows[p_window]; ERR_FAIL_COND(wd_window.transient_parent == p_parent); - ERR_FAIL_COND_MSG(wd_window.always_on_top, "Windows with the 'on top' can't become transient."); if (p_parent == INVALID_WINDOW_ID) { - //remove transient + // Remove transient. ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID); ERR_FAIL_COND(!windows.has(wd_window.transient_parent)); @@ -838,7 +832,7 @@ void DisplayServerWindows::window_set_size(const Size2i p_size, WindowID p_windo MoveWindow(wd.hWnd, rect.left, rect.top, w, h, TRUE); - // Don't let the mouse leave the window when resizing to a smaller resolution + // Don't let the mouse leave the window when resizing to a smaller resolution. if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { RECT crect; GetClientRect(wd.hWnd, &crect); @@ -854,12 +848,13 @@ Size2i DisplayServerWindows::window_get_size(WindowID p_window) const { ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); const WindowData &wd = windows[p_window]; + // GetClientRect() returns a zero rect for a minimized window, so we need to get the size in another way. if (wd.minimized) { return Size2(wd.width, wd.height); } RECT r; - if (GetClientRect(wd.hWnd, &r)) { // Only area inside of window border + if (GetClientRect(wd.hWnd, &r)) { // Retrieves area inside of window border, including decoration. return Size2(r.right - r.left, r.bottom - r.top); } return Size2(); @@ -872,13 +867,17 @@ Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const { const WindowData &wd = windows[p_window]; RECT r; - if (GetWindowRect(wd.hWnd, &r)) { // Includes area of the window border + if (GetWindowRect(wd.hWnd, &r)) { // Retrieves area inside of window border, including decoration. return Size2(r.right - r.left, r.bottom - r.top); } return Size2(); } void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { + // Windows docs for window styles: + // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles + // https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles + r_style = 0; r_style_ex = WS_EX_WINDOWEDGE; if (p_main_window) { @@ -886,10 +885,7 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre } if (p_fullscreen || p_borderless) { - r_style |= WS_POPUP; - //if (p_borderless) { - // r_style_ex |= WS_EX_TOOLWINDOW; - //} + r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past. } else { if (p_resizable) { if (p_maximized) { @@ -1025,7 +1021,7 @@ bool DisplayServerWindows::window_is_maximize_allowed(WindowID p_window) const { // FIXME: Implement this, or confirm that it should always be true. - return true; //no idea + return true; } void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { @@ -1170,8 +1166,9 @@ void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowI void DisplayServerWindows::console_set_visible(bool p_enabled) { _THREAD_SAFE_METHOD_ - if (console_visible == p_enabled) + if (console_visible == p_enabled) { return; + } ShowWindow(GetConsoleWindow(), p_enabled ? SW_SHOW : SW_HIDE); console_visible = p_enabled; } @@ -1185,8 +1182,9 @@ void DisplayServerWindows::cursor_set_shape(CursorShape p_shape) { ERR_FAIL_INDEX(p_shape, CURSOR_MAX); - if (cursor_shape == p_shape) + if (cursor_shape == p_shape) { return; + } if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { cursor_shape = p_shape; @@ -1196,7 +1194,7 @@ void DisplayServerWindows::cursor_set_shape(CursorShape p_shape) { static const LPCTSTR win_cursors[CURSOR_MAX] = { IDC_ARROW, IDC_IBEAM, - IDC_HAND, //finger + IDC_HAND, // Finger. IDC_CROSS, IDC_WAIT, IDC_APPSTARTING, @@ -1227,49 +1225,49 @@ DisplayServer::CursorShape DisplayServerWindows::cursor_get_shape() const { } void DisplayServerWindows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap) { - // Get the system display DC + // Get the system display DC. HDC hDC = GetDC(nullptr); - // Create helper DC + // Create helper DC. HDC hMainDC = CreateCompatibleDC(hDC); HDC hAndMaskDC = CreateCompatibleDC(hDC); HDC hXorMaskDC = CreateCompatibleDC(hDC); - // Get the dimensions of the source bitmap + // Get the dimensions of the source bitmap. BITMAP bm; GetObject(hSourceBitmap, sizeof(BITMAP), &bm); - // Create the mask bitmaps - hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color - hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color + // Create the mask bitmaps. + hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // Color. + hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // Color. - // Release the system display DC + // Release the system display DC. ReleaseDC(nullptr, hDC); - // Select the bitmaps to helper DC + // Select the bitmaps to helper DC. HBITMAP hOldMainBitmap = (HBITMAP)SelectObject(hMainDC, hSourceBitmap); HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap); HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap); // Assign the monochrome AND mask bitmap pixels so that the pixels of the source bitmap - // with 'clrTransparent' will be white pixels of the monochrome bitmap + // with 'clrTransparent' will be white pixels of the monochrome bitmap. SetBkColor(hMainDC, clrTransparent); BitBlt(hAndMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCCOPY); // Assign the color XOR mask bitmap pixels so that the pixels of the source bitmap // with 'clrTransparent' will be black and rest the pixels same as corresponding - // pixels of the source bitmap + // pixels of the source bitmap. SetBkColor(hXorMaskDC, RGB(0, 0, 0)); SetTextColor(hXorMaskDC, RGB(255, 255, 255)); BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hAndMaskDC, 0, 0, SRCCOPY); BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCAND); - // Deselect bitmaps from the helper DC + // Deselect bitmaps from the helper DC. SelectObject(hMainDC, hOldMainBitmap); SelectObject(hAndMaskDC, hOldAndMaskBitmap); SelectObject(hXorMaskDC, hOldXorMaskBitmap); - // Delete the helper DC + // Delete the helper DC. DeleteDC(hXorMaskDC); DeleteDC(hAndMaskDC); DeleteDC(hMainDC); @@ -1326,7 +1324,7 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh UINT image_size = texture_size.width * texture_size.height; - // Create the BITMAP with alpha channel + // Create the BITMAP with alpha channel. COLORREF *buffer = (COLORREF *)memalloc(sizeof(COLORREF) * image_size); for (UINT index = 0; index < image_size; index++) { @@ -1341,11 +1339,11 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh *(buffer + index) = image->get_pixel(column_index, row_index).to_argb32(); } - // Using 4 channels, so 4 * 8 bits + // Using 4 channels, so 4 * 8 bits. HBITMAP bitmap = CreateBitmap(texture_size.width, texture_size.height, 1, 4 * 8, buffer); COLORREF clrTransparent = -1; - // Create the AND and XOR masks for the bitmap + // Create the AND and XOR masks for the bitmap. HBITMAP hAndMask = nullptr; HBITMAP hXorMask = nullptr; @@ -1357,7 +1355,7 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh return; } - // Finally, create the icon + // Finally, create the icon. ICONINFO iconinfo; iconinfo.fIcon = FALSE; iconinfo.xHotspot = p_hotspot.x; @@ -1365,8 +1363,9 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh iconinfo.hbmMask = hAndMask; iconinfo.hbmColor = hXorMask; - if (cursors[p_shape]) + if (cursors[p_shape]) { DestroyIcon(cursors[p_shape]); + } cursors[p_shape] = CreateIconIndirect(&iconinfo); @@ -1392,7 +1391,7 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh memfree(buffer); DeleteObject(bitmap); } else { - // Reset to default system cursor + // Reset to default system cursor. if (cursors[p_shape]) { DestroyIcon(cursors[p_shape]); cursors[p_shape] = nullptr; @@ -1574,9 +1573,9 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) { icon_dir = (ICONDIR *)memrealloc(icon_dir, 3 * sizeof(WORD) + icon_dir->idCount * sizeof(ICONDIRENTRY)); f->get_buffer((uint8_t *)&icon_dir->idEntries[0], icon_dir->idCount * sizeof(ICONDIRENTRY)); - int small_icon_index = -1; // Select 16x16 with largest color count + int small_icon_index = -1; // Select 16x16 with largest color count. int small_icon_cc = 0; - int big_icon_index = -1; // Select largest + int big_icon_index = -1; // Select largest. int big_icon_width = 16; int big_icon_cc = 0; @@ -1606,7 +1605,7 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) { small_icon_cc = big_icon_cc; } - // Read the big icon + // Read the big icon. DWORD bytecount_big = icon_dir->idEntries[big_icon_index].dwBytesInRes; Vector<uint8_t> data_big; data_big.resize(bytecount_big); @@ -1616,7 +1615,7 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) { HICON icon_big = CreateIconFromResource((PBYTE)&data_big.write[0], bytecount_big, TRUE, 0x00030000); ERR_FAIL_COND_MSG(!icon_big, "Could not create " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + "."); - // Read the small icon + // Read the small icon. DWORD bytecount_small = icon_dir->idEntries[small_icon_index].dwBytesInRes; Vector<uint8_t> data_small; data_small.resize(bytecount_small); @@ -1626,7 +1625,7 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) { HICON icon_small = CreateIconFromResource((PBYTE)&data_small.write[0], bytecount_small, TRUE, 0x00030000); ERR_FAIL_COND_MSG(!icon_small, "Could not create 16x16 @" + itos(small_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + "."); - // Online tradition says to be sure last error is cleared and set the small icon first + // Online tradition says to be sure last error is cleared and set the small icon first. int err = 0; SetLastError(err); @@ -1647,12 +1646,13 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { ERR_FAIL_COND(!p_icon.is_valid()); Ref<Image> icon = p_icon->duplicate(); - if (icon->get_format() != Image::FORMAT_RGBA8) + if (icon->get_format() != Image::FORMAT_RGBA8) { icon->convert(Image::FORMAT_RGBA8); + } int w = icon->get_width(); int h = icon->get_height(); - /* Create temporary bitmap buffer */ + // Create temporary bitmap buffer. int icon_len = 40 + h * w * 4; Vector<BYTE> v; v.resize(icon_len); @@ -1686,10 +1686,10 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { HICON hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000); - /* Set the icon for the window */ + // Set the icon for the window. SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon); - /* Set the icon in the task manager (should we do this?) */ + // Set the icon in the task manager (should we do this?). SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_BIG, (LPARAM)hicon); } @@ -1717,13 +1717,13 @@ void DisplayServerWindows::set_context(Context p_context) { // Keeping the name suggested by Microsoft, but this macro really answers: // Is this mouse event emulated from touch or pen input? #define IsPenEvent(dw) (((dw)&SIGNATURE_MASK) == MI_WP_SIGNATURE) -// This one tells whether the event comes from touchscreen (and not from pen) +// This one tells whether the event comes from touchscreen (and not from pen). #define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw)&0x80)) void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx) { - // Defensive - if (touch_state.has(idx) == p_pressed) + if (touch_state.has(idx) == p_pressed) { return; + } if (p_pressed) { touch_state.insert(idx, Vector2(p_x, p_y)); @@ -1743,12 +1743,13 @@ void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, int idx) { Map<int, Vector2>::Element *curr = touch_state.find(idx); - // Defensive - if (!curr) + if (!curr) { return; + } - if (curr->get() == Vector2(p_x, p_y)) + if (curr->get() == Vector2(p_x, p_y)) { return; + } Ref<InputEventScreenDrag> event; event.instantiate(); @@ -1790,7 +1791,7 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) Ref<InputEventFromWindow> event_from_window = p_event; if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) { - //send to a window + // Send to a single window. if (!windows.has(event_from_window->get_window_id())) { in_dispatch_input_event = false; ERR_FAIL_MSG("DisplayServerWindows: Invalid window id in input event."); @@ -1802,7 +1803,7 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) } callable.call((const Variant **)&evp, 1, ret, ce); } else { - //send to all windows + // Send to all windows. for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { Callable callable = E->get().input_event_callback; if (callable.is_null()) { @@ -1815,6 +1816,9 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) in_dispatch_input_event = false; } +// Our default window procedure to handle processing of window-related system messages/events. +// Also known as DefProc or DefWindowProc. +// See: https://docs.microsoft.com/en-us/windows/win32/winmsg/window-procedures LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (drop_events) { if (user_proc) { @@ -1827,6 +1831,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA WindowID window_id = INVALID_WINDOW_ID; bool window_created = false; + // Check whether window exists. for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { if (E->get().hWnd == hWnd) { window_id = E->key(); @@ -1835,19 +1840,19 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } + // Window doesn't exist or creation in progress, don't handle messages yet. if (!window_created) { - // Window creation in progress. window_id = window_id_counter; ERR_FAIL_COND_V(!windows.has(window_id), 0); } - switch (uMsg) // Check For Windows Messages - { + // Process window messages. + switch (uMsg) { case WM_SETFOCUS: { windows[window_id].window_has_focus = true; last_focused_window = window_id; - // Restore mouse mode + // Restore mouse mode. _set_mouse_mode_impl(mouse_mode); if (!app_focused) { @@ -1856,16 +1861,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } app_focused = true; } - break; - } + } break; case WM_KILLFOCUS: { windows[window_id].window_has_focus = false; last_focused_window = window_id; - // Release capture unconditionally because it can be set due to dragging, in addition to captured mode + // Release capture unconditionally because it can be set due to dragging, in addition to captured mode. ReleaseCapture(); - // Release every touch to avoid sticky points + // Release every touch to avoid sticky points. for (Map<int, Vector2>::Element *E = touch_state.front(); E; E = E->next()) { _touch_event(window_id, false, E->get().x, E->get().y, E->key()); } @@ -1883,10 +1887,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } app_focused = false; } - - break; - } - case WM_ACTIVATE: { // Watch For Window Activate Message + } break; + case WM_ACTIVATE: { // Watch for window activate message. if (!windows[window_id].window_focused) { _process_activate_event(window_id, wParam, lParam); } else { @@ -1896,11 +1898,13 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Run a timer to prevent event catching warning if the focused window is closing. windows[window_id].focus_timer_id = SetTimer(windows[window_id].hWnd, 2, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); } - return 0; // Return To The Message Loop - } + return 0; // Return to the message loop. + } break; case WM_GETMINMAXINFO: { if (windows[window_id].resizable && !windows[window_id].fullscreen) { - Size2 decor = window_get_size(window_id) - window_get_real_size(window_id); // Size of window decorations + // Size of window decorations. + Size2 decor = window_get_real_size(window_id) - window_get_size(window_id); + MINMAXINFO *min_max_info = (MINMAXINFO *)lParam; if (windows[window_id].min_size != Size2()) { min_max_info->ptMinTrackSize.x = windows[window_id].min_size.x + decor.x; @@ -1911,37 +1915,31 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA min_max_info->ptMaxTrackSize.y = windows[window_id].max_size.y + decor.y; } return 0; - } else { - break; } - } - case WM_PAINT: - + } break; + case WM_PAINT: { Main::force_redraw(); - break; - - case WM_SYSCOMMAND: // Intercept System Commands + } break; + case WM_SYSCOMMAND: // Intercept system commands. { - switch (wParam) // Check System Calls + switch (wParam) // Check system calls. { - case SC_SCREENSAVE: // Screensaver Trying To Start? - case SC_MONITORPOWER: // Monitor Trying To Enter Powersave? - return 0; // Prevent From Happening + case SC_SCREENSAVE: // Screensaver trying to start? + case SC_MONITORPOWER: // Monitor trying to enter powersave? + return 0; // Prevent from happening. case SC_KEYMENU: if ((lParam >> 16) <= 0) return 0; } - break; // Exit - } - - case WM_CLOSE: // Did We Receive A Close Message? + } break; + case WM_CLOSE: // Did we receive a close message? { if (windows[window_id].focus_timer_id != 0U) { KillTimer(windows[window_id].hWnd, windows[window_id].focus_timer_id); } _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); - return 0; // Jump Back + return 0; // Jump back. } case WM_MOUSELEAVE: { old_invalid = true; @@ -1983,7 +1981,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Point2i c(windows[window_id].width / 2, windows[window_id].height / 2); - // centering just so it works as before + // Centering just so it works as before. POINT pos = { (int)c.x, (int)c.y }; ClientToScreen(windows[window_id].hWnd, &pos); SetCursorPos(pos.x, pos.y); @@ -2006,7 +2004,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA (double(raw->data.mouse.lLastX) - 65536.0 / (nScreenWidth)) * nScreenWidth / 65536.0 + nScreenLeft, (double(raw->data.mouse.lLastY) - 65536.0 / (nScreenHeight)) * nScreenHeight / 65536.0 + nScreenTop); - POINT coords; //client coords + POINT coords; // Client coords. coords.x = abs_pos.x; coords.y = abs_pos.y; @@ -2015,14 +2013,11 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_relative(Vector2(coords.x - old_x, coords.y - old_y)); old_x = coords.x; old_y = coords.y; - - /*Input.mi.dx = (int)((((double)(pos.x)-nScreenLeft) * 65536) / nScreenWidth + 65536 / (nScreenWidth)); - Input.mi.dy = (int)((((double)(pos.y)-nScreenTop) * 65536) / nScreenHeight + 65536 / (nScreenHeight)); - */ } - if (windows[window_id].window_has_focus && mm->get_relative() != Vector2()) + if (windows[window_id].window_has_focus && mm->get_relative() != Vector2()) { Input::get_singleton()->parse_input_event(mm); + } } delete[] lpb; } break; @@ -2167,7 +2162,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (Input::get_singleton()->is_emulating_mouse_from_touch()) { - // Universal translation enabled; ignore OS translation + // Universal translation enabled; ignore OS translation. LPARAM extra = GetMessageExtraInfo(); if (IsTouchEvent(extra)) { break; @@ -2175,7 +2170,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (outside) { - //mouse enter + // Mouse enter. if (mouse_mode != MOUSE_MODE_CAPTURED) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); @@ -2186,7 +2181,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA cursor_set_shape(c); outside = false; - //Once-Off notification, must call again.... + // Once-off notification, must call again. TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_LEAVE; @@ -2219,7 +2214,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_button_mask(last_button_state); - POINT coords; //client coords + POINT coords; // Client coords. coords.x = GET_X_LPARAM(lParam); coords.y = GET_Y_LPARAM(lParam); @@ -2261,7 +2256,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Input::get_singleton()->parse_input_event(mm); } - return 0; //Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event + return 0; // Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event. } break; case WM_MOUSEMOVE: { if (windows[window_id].block_mm) { @@ -2273,7 +2268,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (Input::get_singleton()->is_emulating_mouse_from_touch()) { - // Universal translation enabled; ignore OS translation + // Universal translation enabled; ignore OS translation. LPARAM extra = GetMessageExtraInfo(); if (IsTouchEvent(extra)) { break; @@ -2281,7 +2276,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (outside) { - //mouse enter + // Mouse enter. if (mouse_mode != MOUSE_MODE_CAPTURED) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); @@ -2292,7 +2287,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA cursor_set_shape(c); outside = false; - //Once-Off notification, must call again.... + // Once-off notification, must call again. TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_LEAVE; @@ -2370,7 +2365,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_LBUTTONDOWN: case WM_LBUTTONUP: if (Input::get_singleton()->is_emulating_mouse_from_touch()) { - // Universal translation enabled; ignore OS translations for left button + // Universal translation enabled; ignore OS translations for left button. LPARAM extra = GetMessageExtraInfo(); if (IsTouchEvent(extra)) { break; @@ -2494,7 +2489,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mb->set_ctrl_pressed((wParam & MK_CONTROL) != 0); mb->set_shift_pressed((wParam & MK_SHIFT) != 0); mb->set_alt_pressed(alt_mem); - //mb->is_alt_pressed()=(wParam&MK_MENU)!=0; + // mb->is_alt_pressed()=(wParam&MK_MENU)!=0; if (mb->is_pressed()) { last_button_state |= MouseButton(1 << (mb->get_button_index() - 1)); } else { @@ -2521,7 +2516,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } } else { - // for reasons unknown to mankind, wheel comes in screen coordinates + // For reasons unknown to mankind, wheel comes in screen coordinates. POINT coords; coords.x = mb->get_position().x; coords.y = mb->get_position().y; @@ -2535,7 +2530,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Input::get_singleton()->parse_input_event(mb); if (mb->is_pressed() && mb->get_button_index() > 3 && mb->get_button_index() < 8) { - //send release for mouse wheel + // Send release for mouse wheel. Ref<InputEventMouseButton> mbd = mb->duplicate(); mbd->set_window_id(window_id); last_button_state &= (MouseButton) ~(1 << (mbd->get_button_index() - 1)); @@ -2545,7 +2540,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } break; - case WM_MOVE: { if (!IsIconic(windows[window_id].hWnd)) { int x = int16_t(LOWORD(lParam)); @@ -2561,12 +2555,14 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } } break; - case WM_SIZE: { - // Ignore size when a SIZE_MINIMIZED event is triggered + // Ignore window size change when a SIZE_MINIMIZED event is triggered. if (wParam != SIZE_MINIMIZED) { + // The new width and height of the client area. int window_w = LOWORD(lParam); int window_h = HIWORD(lParam); + + // Set new value to the size if it isn't preserved. if (window_w > 0 && window_h > 0 && !windows[window_id].preserve_window_size) { windows[window_id].width = window_w; windows[window_id].height = window_h; @@ -2577,29 +2573,38 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } #endif - } else { + } else { // If the size is preserved. windows[window_id].preserve_window_size = false; + + // Restore the old size. window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id); } - } else { + } else { // When the window has been minimized, preserve its size. windows[window_id].preserve_window_size = true; } + // Call windows rect change callback. if (!windows[window_id].rect_changed_callback.is_null()) { Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); - Variant *sizep = &size; + Variant *size_ptr = &size; Variant ret; Callable::CallError ce; - windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + windows[window_id].rect_changed_callback.call((const Variant **)&size_ptr, 1, ret, ce); } + // The window has been maximized. if (wParam == SIZE_MAXIMIZED) { windows[window_id].maximized = true; windows[window_id].minimized = false; - } else if (wParam == SIZE_MINIMIZED) { + } + // The window has been minimized. + else if (wParam == SIZE_MINIMIZED) { windows[window_id].maximized = false; windows[window_id].minimized = true; - } else if (wParam == SIZE_RESTORED) { + windows[window_id].preserve_window_size = false; + } + // The window has been resized, but neither the SIZE_MINIMIZED nor SIZE_MAXIMIZED value applies. + else if (wParam == SIZE_RESTORED) { windows[window_id].maximized = false; windows[window_id].minimized = false; } @@ -2626,9 +2631,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA ZeroMemory(dib_data, dib_size.x * dib_size.y * 4); } #endif - //return 0; // Jump Back } break; - case WM_ENTERSIZEMOVE: { Input::get_singleton()->release_pressed_events(); windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); @@ -2648,7 +2651,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA windows[window_id].focus_timer_id = 0U; } } break; - case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_KEYUP: @@ -2700,7 +2702,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_INPUTLANGCHANGEREQUEST: { // FIXME: Do something? } break; - case WM_TOUCH: { BOOL bHandled = FALSE; UINT cInputs = LOWORD(wParam); @@ -2714,7 +2715,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA TOUCH_COORD_TO_PIXEL(ti.y), }; ScreenToClient(hWnd, &touch_pos); - //do something with each touch input entry + // Do something with each touch input entry. if (ti.dwFlags & TOUCHEVENTF_MOVE) { _drag_event(window_id, touch_pos.x, touch_pos.y, ti.dwID); } else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) { @@ -2723,11 +2724,11 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } bHandled = TRUE; } else { - /* handle the error here */ + // TODO: Handle the error here. } memdelete_arr(pInputs); } else { - /* handle the error here, probably out of memory */ + // TODO: Handle the error here, probably out of memory. } if (bHandled) { CloseTouchInputHandle((HTOUCHINPUT)lParam); @@ -2735,14 +2736,13 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA }; } break; - case WM_DEVICECHANGE: { joypad->probe_joypads(); } break; case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) { if (windows[window_id].window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN)) { - //Hide the cursor + // Hide the cursor. if (hCursor == nullptr) { hCursor = SetCursor(nullptr); } else { @@ -2757,7 +2757,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } } - } break; case WM_DROPFILES: { HDROP hDropInfo = (HDROP)wParam; @@ -2781,9 +2780,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Callable::CallError ce; windows[window_id].drop_files_callback.call((const Variant **)&vp, 1, ret, ce); } - } break; - default: { if (user_proc) { return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam); @@ -2809,7 +2806,7 @@ void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM alt_mem = false; control_mem = false; shift_mem = false; - } else { // WM_INACTIVE + } else { // WM_INACTIVE. Input::get_singleton()->release_pressed_events(); _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT); windows[p_window_id].window_focused = false; @@ -2826,7 +2823,7 @@ void DisplayServerWindows::_process_key_events() { KeyEvent &ke = key_event_buffer[i]; switch (ke.uMsg) { case WM_CHAR: { - // extended keys should only be processed as WM_KEYDOWN message. + // Extended keys should only be processed as WM_KEYDOWN message. if (!KeyMappingWindows::is_extended_key(ke.wParam) && ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR))) { static char32_t prev_wc = 0; char32_t unicode = ke.wParam; @@ -2867,9 +2864,9 @@ void DisplayServerWindows::_process_key_events() { k->set_unicode(0); Input::get_singleton()->parse_input_event(k); + } else { + // Do nothing. } - - //do nothing } break; case WM_KEYUP: case WM_KEYDOWN: { @@ -2885,7 +2882,7 @@ void DisplayServerWindows::_process_key_events() { k->set_pressed(ke.uMsg == WM_KEYDOWN); if ((ke.lParam & (1 << 24)) && (ke.wParam == VK_RETURN)) { - // Special case for Numpad Enter key + // Special case for Numpad Enter key. k->set_keycode(KEY_KP_ENTER); } else { k->set_keycode((Key)KeyMappingWindows::get_keysym(ke.wParam)); @@ -3031,8 +3028,8 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, if (p_mode != WINDOW_MODE_FULLSCREEN) { wd.pre_fs_valid = true; } -#ifdef VULKAN_ENABLED +#ifdef VULKAN_ENABLED if (rendering_driver == "vulkan") { if (context_vulkan->window_create(id, p_vsync_mode, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) == -1) { memdelete(context_vulkan); @@ -3087,7 +3084,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, wd.last_pressure_update = 0; wd.last_tilt = Vector2(); - // IME + // IME. wd.im_himc = ImmGetContext(wd.hWnd); ImmReleaseContext(wd.hWnd, wd.im_himc); @@ -3102,7 +3099,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, return id; } -// WinTab API +// WinTab API. bool DisplayServerWindows::wintab_available = false; WTOpenPtr DisplayServerWindows::wintab_WTOpen = nullptr; WTClosePtr DisplayServerWindows::wintab_WTClose = nullptr; @@ -3110,7 +3107,7 @@ WTInfoPtr DisplayServerWindows::wintab_WTInfo = nullptr; WTPacketPtr DisplayServerWindows::wintab_WTPacket = nullptr; WTEnablePtr DisplayServerWindows::wintab_WTEnable = nullptr; -// Windows Ink API +// Windows Ink API. bool DisplayServerWindows::winink_available = false; GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr; GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr; @@ -3173,7 +3170,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win outside = true; - //Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink. + // Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink. HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll"); if (wintab_lib) { wintab_WTOpen = (WTOpenPtr)GetProcAddress(wintab_lib, "WTOpenW"); @@ -3189,7 +3186,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win tablet_drivers.push_back("wintab"); } - //Note: Windows Ink API for pen input, available on Windows 8+ only. + // Note: Windows Ink API for pen input, available on Windows 8+ only. HMODULE user32_lib = LoadLibraryW(L"user32.dll"); if (user32_lib) { win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); @@ -3222,7 +3219,6 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win wc.lpfnWndProc = (WNDPROC)::WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; - //wc.hInstance = hInstance; wc.hInstance = hInstance ? hInstance : GetModuleHandle(nullptr); wc.hIcon = LoadIcon(nullptr, IDI_WINLOGO); wc.hCursor = nullptr; //LoadCursor(nullptr, IDC_ARROW); @@ -3246,7 +3242,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win Rid[0].hwndTarget = 0; if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) { - //registration failed. + // Registration failed. use_raw_input = false; } @@ -3263,6 +3259,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } } #endif + #if defined(OPENGL_ENABLED) if (rendering_driver_index == VIDEO_DRIVER_GLES2) { context_gles2 = memnew(ContextGL_Windows(hWnd, false)); @@ -3285,6 +3282,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } } #endif + Point2i window_position( (screen_get_size(0).width - p_resolution.width) / 2, (screen_get_size(0).height - p_resolution.height) / 2); @@ -3313,7 +3311,6 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win //set_ime_active(false); if (!OS::get_singleton()->is_in_low_processor_usage_mode()) { - //SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); DWORD index = 0; HANDLE handle = AvSetMmThreadCharacteristics("Games", &index); @@ -3321,7 +3318,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win AvSetMmThreadPriority(handle, AVRT_PRIORITY_CRITICAL); // This is needed to make sure that background work does not starve the main thread. - // This is only setting priority of this thread, not the whole process. + // This is only setting the priority of this thread, not the whole process. SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); } diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index e0b994f27d..8401909384 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -31,127 +31,20 @@ #include "audio_stream_player_2d.h" #include "scene/2d/area_2d.h" +#include "scene/2d/listener_2d.h" #include "scene/main/window.h" -void AudioStreamPlayer2D::_mix_audio() { - if (!stream_playback.is_valid() || !active.is_set() || - (stream_paused && !stream_paused_fade_out)) { - return; - } - - if (setseek.get() >= 0.0) { - stream_playback->start(setseek.get()); - setseek.set(-1.0); //reset seek - } - - //get data - AudioFrame *buffer = mix_buffer.ptrw(); - int buffer_size = mix_buffer.size(); - - if (stream_paused_fade_out) { - // Short fadeout ramp - buffer_size = MIN(buffer_size, 128); - } - - stream_playback->mix(buffer, pitch_scale, buffer_size); - - //write all outputs - int oc = output_count.get(); - for (int i = 0; i < oc; i++) { - Output current = outputs[i]; - - //see if current output exists, to keep volume ramp - bool found = false; - for (int j = i; j < prev_output_count; j++) { - if (prev_outputs[j].viewport == current.viewport) { - if (j != i) { - SWAP(prev_outputs[j], prev_outputs[i]); - } - found = true; - break; - } - } - - if (!found) { - //create new if was not used before - if (prev_output_count < MAX_OUTPUTS) { - prev_outputs[prev_output_count] = prev_outputs[i]; //may be owned by another viewport - prev_output_count++; - } - prev_outputs[i] = current; - } - - //mix! - AudioFrame target_volume = stream_paused_fade_out ? AudioFrame(0.f, 0.f) : current.vol; - AudioFrame vol_prev = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : prev_outputs[i].vol; - AudioFrame vol_inc = (target_volume - vol_prev) / float(buffer_size); - AudioFrame vol = vol_prev; - - int cc = AudioServer::get_singleton()->get_channel_count(); - - if (cc == 1) { - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, 0)) { - continue; //may have been removed - } - - AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, 0); - - for (int j = 0; j < buffer_size; j++) { - target[j] += buffer[j] * vol; - vol += vol_inc; - } - - } else { - AudioFrame *targets[4]; - bool valid = true; - - for (int k = 0; k < cc; k++) { - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, k)) { - valid = false; //may have been removed - break; - } - - targets[k] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, k); - } - - if (!valid) { - continue; - } - - for (int j = 0; j < buffer_size; j++) { - AudioFrame frame = buffer[j] * vol; - for (int k = 0; k < cc; k++) { - targets[k][j] += frame; - } - vol += vol_inc; - } - } - - prev_outputs[i] = current; - } - - prev_output_count = oc; - - //stream is no longer active, disable this. - if (!stream_playback->is_playing()) { - active.clear(); - } - - output_ready.clear(); - stream_paused_fade_in = false; - stream_paused_fade_out = false; -} - void AudioStreamPlayer2D::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - AudioServer::get_singleton()->add_callback(_mix_audios, this); + AudioServer::get_singleton()->add_listener_changed_callback(_listener_changed_cb, this); if (autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); } } if (p_what == NOTIFICATION_EXIT_TREE) { - AudioServer::get_singleton()->remove_callback(_mix_audios, this); + stop(); + AudioServer::get_singleton()->remove_listener_changed_callback(_listener_changed_cb, this); } if (p_what == NOTIFICATION_PAUSED) { @@ -167,120 +60,143 @@ void AudioStreamPlayer2D::_notification(int p_what) { if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course + if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { + _update_panning(); + } - if (!output_ready.is_set()) { - Ref<World2D> world_2d = get_world_2d(); - ERR_FAIL_COND(world_2d.is_null()); - - int new_output_count = 0; - - Vector2 global_pos = get_global_position(); - - int bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus); - - //check if any area is diverting sound into a bus + if (setplay.get() >= 0 && stream.is_valid()) { + active.set(); + Ref<AudioStreamPlayback> new_playback = stream->instance_playback(); + ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback."); + AudioServer::get_singleton()->start_playback_stream(new_playback, _get_actual_bus(), volume_vector, setplay.get()); + stream_playbacks.push_back(new_playback); + setplay.set(-1); + } - PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); + if (!stream_playbacks.is_empty() && active.is_set()) { + // Stop playing if no longer active. + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. + active.clear(); + set_physics_process_internal(false); + } + } - PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); + } + } +} - int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); +StringName AudioStreamPlayer2D::_get_actual_bus() { + Vector2 global_pos = get_global_position(); - for (int i = 0; i < areas; i++) { - Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider); - if (!area2d) { - continue; - } + //check if any area is diverting sound into a bus + Ref<World2D> world_2d = get_world_2d(); + ERR_FAIL_COND_V(world_2d.is_null(), SNAME("Master")); - if (!area2d->is_overriding_audio_bus()) { - continue; - } + PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); + PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; - StringName bus_name = area2d->get_audio_bus_name(); - bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name); - break; - } + int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); - const Set<Viewport *> viewports = world_2d->get_viewports(); + for (int i = 0; i < areas; i++) { + Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider); + if (!area2d) { + continue; + } - for (Set<Viewport *>::Element *E = viewports.front(); E; E = E->next()) { - Viewport *vp = E->get(); - if (vp->is_audio_listener_2d()) { - //compute matrix to convert to screen - Transform2D to_screen = vp->get_global_canvas_transform() * vp->get_canvas_transform(); - Vector2 screen_size = vp->get_visible_rect().size; + if (!area2d->is_overriding_audio_bus()) { + continue; + } - //screen in global is used for attenuation - Vector2 screen_in_global = to_screen.affine_inverse().xform(screen_size * 0.5); + return area2d->get_audio_bus_name(); + } + return default_bus; +} - float dist = global_pos.distance_to(screen_in_global); //distance to screen center +void AudioStreamPlayer2D::_update_panning() { + if (!active.is_set() || stream.is_null()) { + return; + } - if (dist > max_distance) { - continue; //can't hear this sound in this viewport - } + Ref<World2D> world_2d = get_world_2d(); + ERR_FAIL_COND(world_2d.is_null()); - float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); - multiplier *= Math::db2linear(volume_db); //also apply player volume! + Vector2 global_pos = get_global_position(); - //point in screen is used for panning - Vector2 point_in_screen = to_screen.xform(global_pos); + Set<Viewport *> viewports = world_2d->get_viewports(); + viewports.insert(get_viewport()); // TODO: This is a mediocre workaround for #50958. Remove when that bug is fixed! - float pan = CLAMP(point_in_screen.x / screen_size.width, 0.0, 1.0); + volume_vector.resize(4); + volume_vector.write[0] = AudioFrame(0, 0); + volume_vector.write[1] = AudioFrame(0, 0); + volume_vector.write[2] = AudioFrame(0, 0); + volume_vector.write[3] = AudioFrame(0, 0); - float l = 1.0 - pan; - float r = pan; + for (Viewport *vp : viewports) { + if (!vp->is_audio_listener_2d()) { + continue; + } + //compute matrix to convert to screen + Vector2 screen_size = vp->get_visible_rect().size; + Vector2 listener_in_global; + Vector2 relative_to_listener; + + //screen in global is used for attenuation + Listener2D *listener = vp->get_listener_2d(); + if (listener) { + listener_in_global = listener->get_global_position(); + relative_to_listener = global_pos - listener_in_global; + } else { + Transform2D to_listener = vp->get_global_canvas_transform() * vp->get_canvas_transform(); + listener_in_global = to_listener.affine_inverse().xform(screen_size * 0.5); + relative_to_listener = to_listener.xform(global_pos) - screen_size * 0.5; + } - outputs[new_output_count].vol = AudioFrame(l, r) * multiplier; - outputs[new_output_count].bus_index = bus_index; - outputs[new_output_count].viewport = vp; //keep pointer only for reference - new_output_count++; - if (new_output_count == MAX_OUTPUTS) { - break; - } - } - } + float dist = global_pos.distance_to(listener_in_global); // Distance to listener, or screen if none. - output_count.set(new_output_count); - output_ready.set(); + if (dist > max_distance) { + continue; //can't hear this sound in this viewport } - //start playing if requested - if (setplay.get() >= 0.0) { - setseek.set(setplay.get()); - active.set(); - setplay.set(-1); - } + float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); + multiplier *= Math::db2linear(volume_db); //also apply player volume! - //stop playing if no longer active - if (!active.is_set()) { - set_physics_process_internal(false); - emit_signal(SNAME("finished")); - } - } -} + float pan = CLAMP((relative_to_listener.x + screen_size.x * 0.5) / screen_size.x, 0.0, 1.0); -void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { - AudioServer::get_singleton()->lock(); + float l = 1.0 - pan; + float r = pan; - mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); + volume_vector.write[0] = AudioFrame(l, r) * multiplier; + } - if (stream_playback.is_valid()) { - stream_playback.unref(); - stream.unref(); - active.clear(); - setseek.set(-1); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_exclusive(playback, _get_actual_bus(), volume_vector); } - if (p_stream.is_valid()) { - stream = p_stream; - stream_playback = p_stream->instance_playback(); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); } - AudioServer::get_singleton()->unlock(); + last_mix_count = AudioServer::get_singleton()->get_mix_count(); +} - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); - } +void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer2D::get_stream() const { @@ -298,6 +214,9 @@ float AudioStreamPlayer2D::get_volume_db() const { void AudioStreamPlayer2D::set_pitch_scale(float p_pitch_scale) { ERR_FAIL_COND(p_pitch_scale <= 0.0); pitch_scale = p_pitch_scale; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, p_pitch_scale); + } } float AudioStreamPlayer2D::get_pitch_scale() const { @@ -305,66 +224,64 @@ float AudioStreamPlayer2D::get_pitch_scale() const { } void AudioStreamPlayer2D::play(float p_from_pos) { - if (!is_playing()) { - // Reset the prev_output_count if the stream is stopped - prev_output_count = 0; + if (stream.is_null()) { + return; } - - if (stream_playback.is_valid()) { - setplay.set(p_from_pos); - output_ready.clear(); - set_physics_process_internal(true); + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); } + + setplay.set(p_from_pos); + active.set(); + set_physics_process_internal(true); } void AudioStreamPlayer2D::seek(float p_seconds) { - if (stream_playback.is_valid()) { - setseek.set(p_seconds); + if (is_playing()) { + stop(); + play(p_seconds); } } void AudioStreamPlayer2D::stop() { - if (stream_playback.is_valid()) { - active.clear(); - set_physics_process_internal(false); - setplay.set(-1); + setplay.set(-1); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_physics_process_internal(false); } bool AudioStreamPlayer2D::is_playing() const { - if (stream_playback.is_valid()) { - return active.is_set() || setplay.get() >= 0; + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer2D::get_playback_position() { - if (stream_playback.is_valid()) { - float ss = setseek.get(); - if (ss >= 0.0) { - return ss; - } - return stream_playback->get_playback_position(); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } void AudioStreamPlayer2D::set_bus(const StringName &p_bus) { - //if audio is active, must lock this - AudioServer::get_singleton()->lock(); - bus = p_bus; - AudioServer::get_singleton()->unlock(); + default_bus = p_bus; // This will be pushed to the audio server during the next physics timestep, which is fast enough. } StringName AudioStreamPlayer2D::get_bus() const { for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (AudioServer::get_singleton()->get_bus_name(i) == bus) { - return bus; + if (AudioServer::get_singleton()->get_bus_name(i) == default_bus) { + return default_bus; } } - return "Master"; + return SNAME("Master"); } void AudioStreamPlayer2D::set_autoplay(bool p_enable) { @@ -432,19 +349,35 @@ uint32_t AudioStreamPlayer2D::get_area_mask() const { } void AudioStreamPlayer2D::set_stream_paused(bool p_pause) { - if (p_pause != stream_paused) { - stream_paused = p_pause; - stream_paused_fade_in = !p_pause; - stream_paused_fade_out = p_pause; + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer2D::get_stream_paused() const { - return stream_paused; + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); + } + return false; } Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; +} + +void AudioStreamPlayer2D::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer2D::get_max_polyphony() const { + return max_polyphony; } void AudioStreamPlayer2D::_bind_methods() { @@ -485,6 +418,9 @@ void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer2D::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer2D::get_stream_paused); + ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer2D::set_max_polyphony); + ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer2D::get_max_polyphony); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -495,6 +431,7 @@ void AudioStreamPlayer2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "1,4096,1,or_greater,exp"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_attenuation", "get_attenuation"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index cf05a49b00..5360fd4934 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -51,38 +51,31 @@ private: Viewport *viewport = nullptr; //pointer only used for reference to previous mix }; - Output outputs[MAX_OUTPUTS]; - SafeNumeric<int> output_count; - SafeFlag output_ready; - - //these are used by audio thread to have a reference of previous volumes (for ramping volume and avoiding clicks) - Output prev_outputs[MAX_OUTPUTS]; - int prev_output_count = 0; - - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - Vector<AudioFrame> mix_buffer; - SafeNumeric<float> setseek{ -1.0 }; - SafeFlag active; + SafeFlag active{ false }; SafeNumeric<float> setplay{ -1.0 }; + Vector<AudioFrame> volume_vector; + + uint64_t last_mix_count = -1; + float volume_db = 0.0; float pitch_scale = 1.0; bool autoplay = false; - bool stream_paused = false; - bool stream_paused_fade_in = false; - bool stream_paused_fade_out = false; - StringName bus; - - void _mix_audio(); - static void _mix_audios(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->_mix_audio(); } + StringName default_bus = SNAME("Master"); + int max_polyphony = 1; void _set_playing(bool p_enable); bool _is_active() const; + StringName _get_actual_bus(); + void _update_panning(); void _bus_layout_changed(); + static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->_update_panning(); } + uint32_t area_mask = 1; float max_distance = 2000.0; @@ -127,6 +120,9 @@ public: void set_stream_paused(bool p_pause); bool get_stream_paused() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + Ref<AudioStreamPlayback> get_stream_playback(); AudioStreamPlayer2D(); diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp index 60f29ca163..5d3a538f60 100644 --- a/scene/2d/collision_object_2d.cpp +++ b/scene/2d/collision_object_2d.cpp @@ -481,10 +481,8 @@ bool CollisionObject2D::is_pickable() const { return pickable; } -void CollisionObject2D::_input_event(Node *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape) { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_input_event, p_viewport, p_input_event, p_shape); - } +void CollisionObject2D::_input_event_call(Viewport *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape) { + GDVIRTUAL_CALL(_input_event, p_viewport, p_input_event, p_shape); emit_signal(SceneStringNames::get_singleton()->input_event, p_viewport, p_input_event, p_shape); } @@ -597,7 +595,7 @@ void CollisionObject2D::_bind_methods() { ClassDB::bind_method(D_METHOD("shape_owner_clear_shapes", "owner_id"), &CollisionObject2D::shape_owner_clear_shapes); ClassDB::bind_method(D_METHOD("shape_find_owner", "shape_index"), &CollisionObject2D::shape_find_owner); - BIND_VMETHOD(MethodInfo("_input_event", PropertyInfo(Variant::OBJECT, "viewport"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::INT, "shape_idx"))); + GDVIRTUAL_BIND(_input_event, "viewport", "event", "shape_idx"); ADD_SIGNAL(MethodInfo("input_event", PropertyInfo(Variant::OBJECT, "viewport", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::INT, "shape_idx"))); ADD_SIGNAL(MethodInfo("mouse_entered")); diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index 11e11d1382..19abacb201 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -32,6 +32,7 @@ #define COLLISION_OBJECT_2D_H #include "scene/2d/node_2d.h" +#include "scene/main/viewport.h" #include "scene/resources/shape_2d.h" #include "servers/physics_server_2d.h" @@ -88,7 +89,7 @@ protected: void _update_pickable(); friend class Viewport; - void _input_event(Node *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape); + void _input_event_call(Viewport *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape); void _mouse_enter(); void _mouse_exit(); @@ -100,6 +101,7 @@ protected: void set_body_mode(PhysicsServer2D::BodyMode p_mode); + GDVIRTUAL3(_input_event, Viewport *, Ref<InputEvent>, int) public: void set_collision_layer(uint32_t p_layer); uint32_t get_collision_layer() const; diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 559bd2fd16..b836497627 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -248,7 +248,7 @@ TypedArray<String> CPUParticles2D::get_configuration_warnings() const { CanvasItemMaterial *mat = Object::cast_to<CanvasItemMaterial>(get_material().ptr()); if (get_material().is_null() || (mat && !mat->get_particles_animation())) { - if (get_param(PARAM_ANIM_SPEED) != 0.0 || get_param(PARAM_ANIM_OFFSET) != 0.0 || + if (get_param_max(PARAM_ANIM_SPEED) != 0.0 || get_param_max(PARAM_ANIM_OFFSET) != 0.0 || get_param_curve(PARAM_ANIM_SPEED).is_valid() || get_param_curve(PARAM_ANIM_OFFSET).is_valid()) { warnings.push_back(TTR("CPUParticles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled.")); } @@ -292,28 +292,34 @@ real_t CPUParticles2D::get_spread() const { return spread; } -void CPUParticles2D::set_param(Parameter p_param, real_t p_value) { +void CPUParticles2D::set_param_min(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - parameters[p_param] = p_value; + parameters_min[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_max(p_param, p_value); + } } -real_t CPUParticles2D::get_param(Parameter p_param) const { +real_t CPUParticles2D::get_param_min(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return parameters[p_param]; + return parameters_min[p_param]; } -void CPUParticles2D::set_param_randomness(Parameter p_param, real_t p_value) { +void CPUParticles2D::set_param_max(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - randomness[p_param] = p_value; + parameters_max[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_min(p_param, p_value); + } } -real_t CPUParticles2D::get_param_randomness(Parameter p_param) const { +real_t CPUParticles2D::get_param_max(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return randomness[p_param]; + return parameters_max[p_param]; } static void _adjust_curve_range(const Ref<Curve> &p_curve, real_t p_min, real_t p_max) { @@ -460,6 +466,31 @@ Vector2 CPUParticles2D::get_gravity() const { return gravity; } +void CPUParticles2D::set_scale_curve_x(Ref<Curve> p_scale_curve) { + scale_curve_x = p_scale_curve; +} + +void CPUParticles2D::set_scale_curve_y(Ref<Curve> p_scale_curve) { + scale_curve_y = p_scale_curve; +} + +void CPUParticles2D::set_split_scale(bool p_split_scale) { + split_scale = p_split_scale; + notify_property_list_changed(); +} + +Ref<Curve> CPUParticles2D::get_scale_curve_x() const { + return scale_curve_x; +} + +Ref<Curve> CPUParticles2D::get_scale_curve_y() const { + return scale_curve_y; +} + +bool CPUParticles2D::get_split_scale() { + return split_scale; +} + void CPUParticles2D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) { property.usage = PROPERTY_USAGE_NONE; @@ -484,6 +515,9 @@ void CPUParticles2D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_colors" && emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { property.usage = PROPERTY_USAGE_NONE; } + if (property.name.begins_with("scale_curve_") && !split_scale) { + property.usage = PROPERTY_USAGE_NONE; + } } static uint32_t idhash(uint32_t x) { @@ -695,14 +729,14 @@ void CPUParticles2D::_particles_process(double p_delta) { real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); Vector2 rot = Vector2(Math::cos(angle1_rad), Math::sin(angle1_rad)); - p.velocity = rot * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp((real_t)1.0, real_t(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); + p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], Math::randf()); - real_t base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp((real_t)1.0, p.angle_rand, randomness[PARAM_ANGLE]); + real_t base_angle = tex_angle * Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); p.rotation = Math::deg2rad(base_angle); p.custom[0] = 0.0; // unused p.custom[1] = 0.0; // phase [0..1] - p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp((real_t)1.0, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]); //animation phase [0..1] + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand); p.custom[3] = 0.0; p.transform = Transform2D(); p.time = 0; @@ -766,51 +800,51 @@ void CPUParticles2D::_particles_process(double p_delta) { p.custom[1] = p.time / lifetime; tv = p.time / p.lifetime; - real_t tex_linear_velocity = 0.0; + real_t tex_linear_velocity = 1.0; if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(tv); } - real_t tex_orbit_velocity = 0.0; + real_t tex_orbit_velocity = 1.0; if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(tv); } - real_t tex_angular_velocity = 0.0; + real_t tex_angular_velocity = 1.0; if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(tv); } - real_t tex_linear_accel = 0.0; + real_t tex_linear_accel = 1.0; if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) { tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(tv); } - real_t tex_tangential_accel = 0.0; + real_t tex_tangential_accel = 1.0; if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(tv); } - real_t tex_radial_accel = 0.0; + real_t tex_radial_accel = 1.0; if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) { tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(tv); } - real_t tex_damping = 0.0; + real_t tex_damping = 1.0; if (curve_parameters[PARAM_DAMPING].is_valid()) { tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(tv); } - real_t tex_angle = 0.0; + real_t tex_angle = 1.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(tv); } - real_t tex_anim_speed = 0.0; + real_t tex_anim_speed = 1.0; if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) { tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(tv); } - real_t tex_anim_offset = 0.0; + real_t tex_anim_offset = 1.0; if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) { tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(tv); } @@ -819,18 +853,18 @@ void CPUParticles2D::_particles_process(double p_delta) { Vector2 pos = p.transform[2]; //apply linear acceleration - force += p.velocity.length() > 0.0 ? p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector2(); + force += p.velocity.length() > 0.0 ? p.velocity.normalized() * tex_linear_accel * Math::lerp(parameters_min[PARAM_LINEAR_ACCEL], parameters_max[PARAM_LINEAR_ACCEL], rand_from_seed(alt_seed)) : Vector2(); //apply radial acceleration Vector2 org = emission_xform[2]; Vector2 diff = pos - org; - force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector2(); + force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(alt_seed)) : Vector2(); //apply tangential acceleration; Vector2 yx = Vector2(diff.y, diff.x); - force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector2(); + force += yx.length() > 0.0 ? yx.normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector2(); //apply attractor forces p.velocity += force * local_delta; //orbit velocity - real_t orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); + real_t orbit_amount = tex_orbit_velocity * Math::lerp(parameters_min[PARAM_ORBIT_VELOCITY], parameters_max[PARAM_ORBIT_VELOCITY], rand_from_seed(alt_seed)); if (orbit_amount != 0.0) { real_t ang = orbit_amount * local_delta * Math_TAU; // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, @@ -843,9 +877,9 @@ void CPUParticles2D::_particles_process(double p_delta) { p.velocity = p.velocity.normalized() * tex_linear_velocity; } - if (parameters[PARAM_DAMPING] + tex_damping > 0.0) { + if (parameters_max[PARAM_DAMPING] + tex_damping > 0.0) { real_t v = p.velocity.length(); - real_t damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]); + real_t damp = tex_damping * Math::lerp(parameters_min[PARAM_DAMPING], parameters_max[PARAM_DAMPING], rand_from_seed(alt_seed)); v -= damp * local_delta; if (v < 0.0) { p.velocity = Vector2(); @@ -853,18 +887,32 @@ void CPUParticles2D::_particles_process(double p_delta) { p.velocity = p.velocity.normalized() * v; } } - real_t base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp((real_t)1.0, p.angle_rand, randomness[PARAM_ANGLE]); - base_angle += p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]); + real_t base_angle = (tex_angle)*Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); + base_angle += p.custom[1] * lifetime * tex_angular_velocity * Math::lerp(parameters_min[PARAM_ANGULAR_VELOCITY], parameters_max[PARAM_ANGULAR_VELOCITY], rand_from_seed(alt_seed)); p.rotation = Math::deg2rad(base_angle); //angle - real_t animation_phase = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp((real_t)1.0, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + p.custom[1] * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]); - p.custom[2] = animation_phase; + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand) + p.custom[1] * tex_anim_speed * Math::lerp(parameters_min[PARAM_ANIM_SPEED], parameters_max[PARAM_ANIM_SPEED], rand_from_seed(alt_seed)); } //apply color //apply hue rotation - real_t tex_scale = 1.0; - if (curve_parameters[PARAM_SCALE].is_valid()) { - tex_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + Vector2 tex_scale = Vector2(1.0, 1.0); + if (split_scale) { + if (scale_curve_x.is_valid()) { + tex_scale.x = scale_curve_x->interpolate(tv); + } else { + tex_scale.x = 1.0; + } + if (scale_curve_y.is_valid()) { + tex_scale.y = scale_curve_y->interpolate(tv); + } else { + tex_scale.y = 1.0; + } + } else { + if (curve_parameters[PARAM_SCALE].is_valid()) { + real_t tmp_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + tex_scale.x = tmp_scale; + tex_scale.y = tmp_scale; + } } real_t tex_hue_variation = 0.0; @@ -872,7 +920,7 @@ void CPUParticles2D::_particles_process(double p_delta) { tex_hue_variation = curve_parameters[PARAM_HUE_VARIATION]->interpolate(tv); } - real_t hue_rot_angle = (parameters[PARAM_HUE_VARIATION] + tex_hue_variation) * Math_TAU * Math::lerp(1, p.hue_rot_rand * 2.0f - 1.0f, randomness[PARAM_HUE_VARIATION]); + real_t hue_rot_angle = (tex_hue_variation)*Math_TAU * Math::lerp(parameters_min[PARAM_HUE_VARIATION], parameters_max[PARAM_HUE_VARIATION], p.hue_rot_rand); real_t hue_rot_c = Math::cos(hue_rot_angle); real_t hue_rot_s = Math::sin(hue_rot_angle); @@ -912,13 +960,15 @@ void CPUParticles2D::_particles_process(double p_delta) { } //scale by scale - real_t base_scale = tex_scale * Math::lerp(parameters[PARAM_SCALE], (real_t)1.0, p.scale_rand * randomness[PARAM_SCALE]); - if (base_scale < 0.000001) { - base_scale = 0.000001; + Vector2 base_scale = tex_scale * Math::lerp(parameters_min[PARAM_SCALE], parameters_max[PARAM_SCALE], p.scale_rand); + if (base_scale.x < 0.00001) { + base_scale.x = 0.00001; } - - p.transform.elements[0] *= base_scale; - p.transform.elements[1] *= base_scale; + if (base_scale.y < 0.00001) { + base_scale.y = 0.00001; + } + p.transform.elements[0] *= base_scale.x; + p.transform.elements[1] *= base_scale.y; p.transform[2] += p.velocity * local_delta; } @@ -1130,18 +1180,24 @@ void CPUParticles2D::convert_from_particles(Node *p_particles) { Vector2 rect_extents = Vector2(material->get_emission_box_extents().x, material->get_emission_box_extents().y); set_emission_rect_extents(rect_extents); + Ref<CurveXYZTexture> scale3D = material->get_param_texture(ParticlesMaterial::PARAM_SCALE); + if (scale3D.is_valid()) { + split_scale = true; + scale_curve_x = scale3D->get_curve_x(); + scale_curve_y = scale3D->get_curve_y(); + } Vector2 gravity = Vector2(material->get_gravity().x, material->get_gravity().y); set_gravity(gravity); set_lifetime_randomness(material->get_lifetime_randomness()); #define CONVERT_PARAM(m_param) \ - set_param(m_param, material->get_param(ParticlesMaterial::m_param)); \ + set_param_min(m_param, material->get_param_min(ParticlesMaterial::m_param)); \ { \ Ref<CurveTexture> ctex = material->get_param_texture(ParticlesMaterial::m_param); \ if (ctex.is_valid()) \ set_param_curve(m_param, ctex->get_curve()); \ } \ - set_param_randomness(m_param, material->get_param_randomness(ParticlesMaterial::m_param)); + set_param_max(m_param, material->get_param_max(ParticlesMaterial::m_param)); CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); @@ -1224,11 +1280,11 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_spread", "degrees"), &CPUParticles2D::set_spread); ClassDB::bind_method(D_METHOD("get_spread"), &CPUParticles2D::get_spread); - ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &CPUParticles2D::set_param); - ClassDB::bind_method(D_METHOD("get_param", "param"), &CPUParticles2D::get_param); + ClassDB::bind_method(D_METHOD("set_param_min", "param", "value"), &CPUParticles2D::set_param_min); + ClassDB::bind_method(D_METHOD("get_param_min", "param"), &CPUParticles2D::get_param_min); - ClassDB::bind_method(D_METHOD("set_param_randomness", "param", "randomness"), &CPUParticles2D::set_param_randomness); - ClassDB::bind_method(D_METHOD("get_param_randomness", "param"), &CPUParticles2D::get_param_randomness); + ClassDB::bind_method(D_METHOD("set_param_max", "param", "value"), &CPUParticles2D::set_param_max); + ClassDB::bind_method(D_METHOD("get_param_max", "param"), &CPUParticles2D::get_param_max); ClassDB::bind_method(D_METHOD("set_param_curve", "param", "curve"), &CPUParticles2D::set_param_curve); ClassDB::bind_method(D_METHOD("get_param_curve", "param"), &CPUParticles2D::get_param_curve); @@ -1263,6 +1319,15 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles2D::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles2D::set_gravity); + ClassDB::bind_method(D_METHOD("get_split_scale"), &CPUParticles2D::get_split_scale); + ClassDB::bind_method(D_METHOD("set_split_scale", "split_scale"), &CPUParticles2D::set_split_scale); + + ClassDB::bind_method(D_METHOD("get_scale_curve_x"), &CPUParticles2D::get_scale_curve_x); + ClassDB::bind_method(D_METHOD("set_scale_curve_x", "scale_curve"), &CPUParticles2D::set_scale_curve_x); + + ClassDB::bind_method(D_METHOD("get_scale_curve_y"), &CPUParticles2D::get_scale_curve_y); + ClassDB::bind_method(D_METHOD("set_scale_curve_y", "scale_curve"), &CPUParticles2D::set_scale_curve_y); + ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles2D::convert_from_particles); ADD_GROUP("Emission Shape", "emission_"); @@ -1280,54 +1345,58 @@ void CPUParticles2D::_bind_methods() { ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_INITIAL_LINEAR_VELOCITY); ADD_GROUP("Angular Velocity", "angular_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_min", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_max", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); ADD_GROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); ADD_GROUP("Linear Accel", "linear_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_LINEAR_ACCEL); ADD_GROUP("Radial Accel", "radial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_RADIAL_ACCEL); ADD_GROUP("Tangential Accel", "tangential_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param", "get_param", PARAM_DAMPING); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_min", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_min", "get_param_min", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_max", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_max", "get_param_max", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param", "get_param", PARAM_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_min", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_min", "get_param_min", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_max", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_max", "get_param_max", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGLE); ADD_GROUP("Scale", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_SCALE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_amount_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_SCALE); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "split_scale"), "set_split_scale", "get_split_scale"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_x", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_x", "get_scale_curve_x"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_y", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_y", "get_scale_curve_y"); + ADD_GROUP("Color", ""); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_color_ramp", "get_color_ramp"); ADD_GROUP("Hue Variation", "hue_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param", "get_param", PARAM_HUE_VARIATION); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_max", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_max", "get_param_max", PARAM_HUE_VARIATION); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_HUE_VARIATION); ADD_GROUP("Animation", "anim_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_param", "get_param", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_min", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_min", "get_param_min", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_max", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_max", "get_param_max", PARAM_ANIM_OFFSET); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_OFFSET); BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY); @@ -1366,22 +1435,31 @@ CPUParticles2D::CPUParticles2D() { set_amount(8); set_use_local_coordinates(true); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); - set_param(PARAM_ANGULAR_VELOCITY, 0); - set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); - set_param(PARAM_RADIAL_ACCEL, 0); - set_param(PARAM_TANGENTIAL_ACCEL, 0); - set_param(PARAM_DAMPING, 0); - set_param(PARAM_ANGLE, 0); - set_param(PARAM_SCALE, 1); - set_param(PARAM_HUE_VARIATION, 0); - set_param(PARAM_ANIM_SPEED, 0); - set_param(PARAM_ANIM_OFFSET, 0); - - for (int i = 0; i < PARAM_MAX; i++) { - set_param_randomness(Parameter(i), 0); - } + set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_min(PARAM_ANGULAR_VELOCITY, 0); + set_param_min(PARAM_ORBIT_VELOCITY, 0); + set_param_min(PARAM_LINEAR_ACCEL, 0); + set_param_min(PARAM_RADIAL_ACCEL, 0); + set_param_min(PARAM_TANGENTIAL_ACCEL, 0); + set_param_min(PARAM_DAMPING, 0); + set_param_min(PARAM_ANGLE, 0); + set_param_min(PARAM_SCALE, 1); + set_param_min(PARAM_HUE_VARIATION, 0); + set_param_min(PARAM_ANIM_SPEED, 0); + set_param_min(PARAM_ANIM_OFFSET, 0); + + set_param_max(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_max(PARAM_ANGULAR_VELOCITY, 0); + set_param_max(PARAM_ORBIT_VELOCITY, 0); + set_param_max(PARAM_LINEAR_ACCEL, 0); + set_param_max(PARAM_RADIAL_ACCEL, 0); + set_param_max(PARAM_TANGENTIAL_ACCEL, 0); + set_param_max(PARAM_DAMPING, 0); + set_param_max(PARAM_ANGLE, 0); + set_param_max(PARAM_SCALE, 1); + set_param_max(PARAM_HUE_VARIATION, 0); + set_param_max(PARAM_ANIM_SPEED, 0); + set_param_max(PARAM_ANIM_OFFSET, 0); for (int i = 0; i < PARTICLE_FLAG_MAX; i++) { particle_flags[i] = false; diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 0f8950375f..4990d443e3 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -150,8 +150,8 @@ private: Vector2 direction = Vector2(1, 0); real_t spread = 45.0; - real_t parameters[PARAM_MAX]; - real_t randomness[PARAM_MAX]; + real_t parameters_min[PARAM_MAX]; + real_t parameters_max[PARAM_MAX]; Ref<Curve> curve_parameters[PARAM_MAX]; Color color; @@ -167,6 +167,10 @@ private: Vector<Color> emission_colors; int emission_point_count = 0; + Ref<Curve> scale_curve_x; + Ref<Curve> scale_curve_y; + bool split_scale = false; + Vector2 gravity = Vector2(0, 980); void _update_internal(); @@ -236,11 +240,11 @@ public: void set_spread(real_t p_spread); real_t get_spread() const; - void set_param(Parameter p_param, real_t p_value); - real_t get_param(Parameter p_param) const; + void set_param_min(Parameter p_param, real_t p_value); + real_t get_param_min(Parameter p_param) const; - void set_param_randomness(Parameter p_param, real_t p_value); - real_t get_param_randomness(Parameter p_param) const; + void set_param_max(Parameter p_param, real_t p_value); + real_t get_param_max(Parameter p_param) const; void set_param_curve(Parameter p_param, const Ref<Curve> &p_curve); Ref<Curve> get_param_curve(Parameter p_param) const; @@ -261,6 +265,9 @@ public: void set_emission_normals(const Vector<Vector2> &p_normals); void set_emission_colors(const Vector<Color> &p_colors); void set_emission_point_count(int p_count); + void set_scale_curve_x(Ref<Curve> p_scale_curve); + void set_scale_curve_y(Ref<Curve> p_scale_curve); + void set_split_scale(bool p_split_scale); EmissionShape get_emission_shape() const; real_t get_emission_sphere_radius() const; @@ -269,6 +276,9 @@ public: Vector<Vector2> get_emission_normals() const; Vector<Color> get_emission_colors() const; int get_emission_point_count() const; + Ref<Curve> get_scale_curve_x() const; + Ref<Curve> get_scale_curve_y() const; + bool get_split_scale(); void set_gravity(const Vector2 &p_gravity); Vector2 get_gravity() const; diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index 47bf1bc77c..5bce705dd5 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -295,7 +295,7 @@ TypedArray<String> GPUParticles2D::get_configuration_warnings() const { if (get_material().is_null() || (mat && !mat->get_particles_animation())) { const ParticlesMaterial *process = Object::cast_to<ParticlesMaterial>(process_material.ptr()); if (process && - (process->get_param(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || + (process->get_param_max(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param_max(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_SPEED).is_valid() || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_OFFSET).is_valid())) { warnings.push_back(TTR("Particles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled.")); } diff --git a/scene/2d/listener_2d.cpp b/scene/2d/listener_2d.cpp new file mode 100644 index 0000000000..444f05f2b1 --- /dev/null +++ b/scene/2d/listener_2d.cpp @@ -0,0 +1,112 @@ +/*************************************************************************/ +/* listener_2d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "listener_2d.h" + +bool Listener2D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "current") { + if (p_value.operator bool()) { + make_current(); + } else { + clear_current(); + } + } else { + return false; + } + return true; +} + +bool Listener2D::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "current") { + if (is_inside_tree() && get_tree()->is_node_being_edited(this)) { + r_ret = current; + } else { + r_ret = is_current(); + } + } else { + return false; + } + return true; +} + +void Listener2D::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "current")); +} + +void Listener2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (!get_tree()->is_node_being_edited(this) && current) { + make_current(); + } + } break; + case NOTIFICATION_EXIT_TREE: { + if (!get_tree()->is_node_being_edited(this)) { + if (is_current()) { + clear_current(); + current = true; // Keep it true. + } else { + current = false; + } + } + } break; + } +} + +void Listener2D::make_current() { + current = true; + if (!is_inside_tree()) { + return; + } + get_viewport()->_listener_2d_set(this); +} + +void Listener2D::clear_current() { + current = false; + if (!is_inside_tree()) { + return; + } + get_viewport()->_listener_2d_remove(this); +} + +bool Listener2D::is_current() const { + if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) { + return get_viewport()->get_listener_2d() == this; + } else { + return current; + } + return false; +} + +void Listener2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("make_current"), &Listener2D::make_current); + ClassDB::bind_method(D_METHOD("clear_current"), &Listener2D::clear_current); + ClassDB::bind_method(D_METHOD("is_current"), &Listener2D::is_current); +} diff --git a/modules/stb_vorbis/register_types.cpp b/scene/2d/listener_2d.h index bdb1cf69cf..0289a8087d 100644 --- a/modules/stb_vorbis/register_types.cpp +++ b/scene/2d/listener_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* register_types.cpp */ +/* listener_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +28,34 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "register_types.h" +#ifndef LISTENER_2D_H +#define LISTENER_2D_H -#include "audio_stream_ogg_vorbis.h" +#include "scene/2d/node_2d.h" +#include "scene/main/window.h" -#ifdef TOOLS_ENABLED -#include "core/config/engine.h" -#include "resource_importer_ogg_vorbis.h" -#endif +class Listener2D : public Node2D { + GDCLASS(Listener2D, Node2D); -void register_stb_vorbis_types() { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - Ref<ResourceImporterOGGVorbis> ogg_import; - ogg_import.instantiate(); - ResourceFormatImporter::get_singleton()->add_importer(ogg_import); - } -#endif - GDREGISTER_CLASS(AudioStreamOGGVorbis); -} +private: + bool current = false; + + friend class Viewport; + +protected: + void _update_listener(); -void unregister_stb_vorbis_types() { -} + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + void _notification(int p_what); + + static void _bind_methods(); + +public: + void make_current(); + void clear_current(); + bool is_current() const; +}; + +#endif diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index a9d4877cbb..30f012c7aa 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -70,12 +70,12 @@ Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_t return Ref<KinematicCollision2D>(); } -bool PhysicsBody2D::move_and_collide(const Vector2 &p_motion, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, const Set<RID> &p_exclude) { +bool PhysicsBody2D::move_and_collide(const Vector2 &p_motion, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, bool p_collide_separation_ray, const Set<RID> &p_exclude) { if (is_only_update_transform_changes_enabled()) { ERR_PRINT("Move functions do not work together with 'sync to physics' option. Please read the documentation."); } Transform2D gt = get_global_transform(); - bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_exclude); + bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_collide_separation_ray, p_exclude); // Restore direction of motion to be along original motion, // in order to avoid sliding due to recovery, @@ -165,21 +165,13 @@ void PhysicsBody2D::remove_collision_exception_with(Node *p_node) { void StaticBody2D::set_constant_linear_velocity(const Vector2 &p_vel) { constant_linear_velocity = p_vel; - if (kinematic_motion) { - _update_kinematic_motion(); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); } void StaticBody2D::set_constant_angular_velocity(real_t p_vel) { constant_angular_velocity = p_vel; - if (kinematic_motion) { - _update_kinematic_motion(); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); } Vector2 StaticBody2D::get_constant_linear_velocity() const { @@ -209,81 +201,83 @@ Ref<PhysicsMaterial> StaticBody2D::get_physics_material_override() const { return physics_material_override; } -void StaticBody2D::set_kinematic_motion_enabled(bool p_enabled) { - if (p_enabled == kinematic_motion) { - return; - } - - kinematic_motion = p_enabled; +void StaticBody2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody2D::set_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody2D::set_constant_angular_velocity); + ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody2D::get_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody2D::get_constant_angular_velocity); - if (kinematic_motion) { - set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC); - } else { - set_body_mode(PhysicsServer2D::BODY_MODE_STATIC); - } + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody2D::set_physics_material_override); + ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody2D::get_physics_material_override); -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - update_configuration_warnings(); - return; - } -#endif + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); +} - _update_kinematic_motion(); +StaticBody2D::StaticBody2D(PhysicsServer2D::BodyMode p_mode) : + PhysicsBody2D(p_mode) { } -bool StaticBody2D::is_kinematic_motion_enabled() const { - return kinematic_motion; +void StaticBody2D::_reload_physics_characteristics() { + if (physics_material_override.is_null()) { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, 0); + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, 1); + } else { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material_override->computed_bounce()); + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, physics_material_override->computed_friction()); + } } -void StaticBody2D::set_sync_to_physics(bool p_enable) { +void AnimatableBody2D::set_sync_to_physics(bool p_enable) { if (sync_to_physics == p_enable) { return; } sync_to_physics = p_enable; + _update_kinematic_motion(); +} + +bool AnimatableBody2D::is_sync_to_physics_enabled() const { + return sync_to_physics; +} + +void AnimatableBody2D::_update_kinematic_motion() { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - update_configuration_warnings(); return; } #endif - if (kinematic_motion) { - _update_kinematic_motion(); + if (sync_to_physics) { + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); + set_only_update_transform_changes(true); + set_notify_local_transform(true); + } else { + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr); + set_only_update_transform_changes(false); + set_notify_local_transform(false); } } -bool StaticBody2D::is_sync_to_physics_enabled() const { - return sync_to_physics; +void AnimatableBody2D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state) { + AnimatableBody2D *body = (AnimatableBody2D *)p_instance; + body->_body_state_changed(p_state); } -void StaticBody2D::_direct_state_changed(Object *p_state) { +void AnimatableBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { if (!sync_to_physics) { return; } - PhysicsDirectBodyState2D *state = Object::cast_to<PhysicsDirectBodyState2D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState2D object as argument"); - - last_valid_transform = state->get_transform(); + last_valid_transform = p_state->get_transform(); set_notify_local_transform(false); set_global_transform(last_valid_transform); set_notify_local_transform(true); } -TypedArray<String> StaticBody2D::get_configuration_warnings() const { - TypedArray<String> warnings = PhysicsBody2D::get_configuration_warnings(); - - if (sync_to_physics && !kinematic_motion) { - warnings.push_back(TTR("Sync to physics works only when kinematic motion is enabled.")); - } - - return warnings; -} - -void StaticBody2D::_notification(int p_what) { +void AnimatableBody2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { last_valid_transform = get_global_transform(); @@ -293,10 +287,6 @@ void StaticBody2D::_notification(int p_what) { // Used by sync to physics, send the new transform to the physics... Transform2D new_transform = get_global_transform(); - real_t delta_time = get_physics_process_delta_time(); - new_transform.translate(constant_linear_velocity * delta_time); - new_transform.set_rotation(new_transform.get_rotation() + constant_angular_velocity * delta_time); - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform); // ... but then revert changes. @@ -304,98 +294,19 @@ void StaticBody2D::_notification(int p_what) { set_global_transform(last_valid_transform); set_notify_local_transform(true); } break; - - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif - - ERR_FAIL_COND(!kinematic_motion); - - Transform2D new_transform = get_global_transform(); - - real_t delta_time = get_physics_process_delta_time(); - new_transform.translate(constant_linear_velocity * delta_time); - new_transform.set_rotation(new_transform.get_rotation() + constant_angular_velocity * delta_time); - - if (sync_to_physics) { - // Propagate transform change to node. - set_global_transform(new_transform); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform); - - // Propagate transform change to node. - set_block_transform_notify(true); - set_global_transform(new_transform); - set_block_transform_notify(false); - } - } break; } } -void StaticBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody2D::set_constant_linear_velocity); - ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody2D::set_constant_angular_velocity); - ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody2D::get_constant_linear_velocity); - ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody2D::get_constant_angular_velocity); +void AnimatableBody2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &AnimatableBody2D::set_sync_to_physics); + ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &AnimatableBody2D::is_sync_to_physics_enabled); - ClassDB::bind_method(D_METHOD("set_kinematic_motion_enabled", "enabled"), &StaticBody2D::set_kinematic_motion_enabled); - ClassDB::bind_method(D_METHOD("is_kinematic_motion_enabled"), &StaticBody2D::is_kinematic_motion_enabled); - - ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody2D::set_physics_material_override); - ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody2D::get_physics_material_override); - - ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &StaticBody2D::set_sync_to_physics); - ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &StaticBody2D::is_sync_to_physics_enabled); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "kinematic_motion"), "set_kinematic_motion_enabled", "is_kinematic_motion_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled"); } -StaticBody2D::StaticBody2D() : - PhysicsBody2D(PhysicsServer2D::BODY_MODE_STATIC) { -} - -void StaticBody2D::_reload_physics_characteristics() { - if (physics_material_override.is_null()) { - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, 0); - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, 1); - } else { - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material_override->computed_bounce()); - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, physics_material_override->computed_friction()); - } -} - -void StaticBody2D::_update_kinematic_motion() { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif - - if (kinematic_motion && sync_to_physics) { - PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &StaticBody2D::_direct_state_changed)); - set_only_update_transform_changes(true); - set_notify_local_transform(true); - } else { - PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), Callable()); - set_only_update_transform_changes(false); - set_notify_local_transform(false); - } - - bool needs_physics_process = false; - if (kinematic_motion) { - if (!Math::is_zero_approx(constant_angular_velocity) || !constant_linear_velocity.is_equal_approx(Vector2())) { - needs_physics_process = true; - } - } - - set_physics_process_internal(needs_physics_process); +AnimatableBody2D::AnimatableBody2D() : + StaticBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) { + _update_kinematic_motion(); } void RigidBody2D::_body_enter_tree(ObjectID p_id) { @@ -511,26 +422,26 @@ struct _RigidBody2DInOut { int local_shape = 0; }; -void RigidBody2D::_direct_state_changed(Object *p_state) { -#ifdef DEBUG_ENABLED - state = Object::cast_to<PhysicsDirectBodyState2D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState2D object as argument"); -#else - state = (PhysicsDirectBodyState2D *)p_state; //trust it -#endif +void RigidBody2D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state) { + RigidBody2D *body = (RigidBody2D *)p_instance; + body->_body_state_changed(p_state); +} +void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { set_block_transform_notify(true); // don't want notify (would feedback loop) if (mode != MODE_KINEMATIC) { - set_global_transform(state->get_transform()); + set_global_transform(p_state->get_transform()); } - linear_velocity = state->get_linear_velocity(); - angular_velocity = state->get_angular_velocity(); - if (sleeping != state->is_sleeping()) { - sleeping = state->is_sleeping(); + + linear_velocity = p_state->get_linear_velocity(); + angular_velocity = p_state->get_angular_velocity(); + + if (sleeping != p_state->is_sleeping()) { + sleeping = p_state->is_sleeping(); emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed); } - GDVIRTUAL_CALL(_integrate_forces, state); + GDVIRTUAL_CALL(_integrate_forces, p_state); set_block_transform_notify(false); // want it back @@ -546,20 +457,18 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { } } - _RigidBody2DInOut *toadd = (_RigidBody2DInOut *)alloca(state->get_contact_count() * sizeof(_RigidBody2DInOut)); + _RigidBody2DInOut *toadd = (_RigidBody2DInOut *)alloca(p_state->get_contact_count() * sizeof(_RigidBody2DInOut)); int toadd_count = 0; //state->get_contact_count(); RigidBody2D_RemoveAction *toremove = (RigidBody2D_RemoveAction *)alloca(rc * sizeof(RigidBody2D_RemoveAction)); int toremove_count = 0; //put the ones to add - for (int i = 0; i < state->get_contact_count(); i++) { - RID rid = state->get_contact_collider(i); - ObjectID obj = state->get_contact_collider_id(i); - int local_shape = state->get_contact_local_shape(i); - int shape = state->get_contact_collider_shape(i); - - //bool found=false; + for (int i = 0; i < p_state->get_contact_count(); i++) { + RID rid = p_state->get_contact_collider(i); + ObjectID obj = p_state->get_contact_collider_id(i); + int local_shape = p_state->get_contact_local_shape(i); + int shape = p_state->get_contact_collider_shape(i); Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.find(obj); if (!E) { @@ -612,8 +521,6 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { contact_monitor->locked = false; } - - state = nullptr; } void RigidBody2D::set_mode(Mode p_mode) { @@ -653,11 +560,53 @@ real_t RigidBody2D::get_mass() const { void RigidBody2D::set_inertia(real_t p_inertia) { ERR_FAIL_COND(p_inertia < 0); - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, p_inertia); + inertia = p_inertia; + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, inertia); } real_t RigidBody2D::get_inertia() const { - return PhysicsServer2D::get_singleton()->body_get_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA); + return inertia; +} + +void RigidBody2D::set_center_of_mass_mode(CenterOfMassMode p_mode) { + if (center_of_mass_mode == p_mode) { + return; + } + + center_of_mass_mode = p_mode; + + switch (center_of_mass_mode) { + case CENTER_OF_MASS_MODE_AUTO: { + center_of_mass = Vector2(); + PhysicsServer2D::get_singleton()->body_reset_mass_properties(get_rid()); + if (inertia != 0.0) { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, inertia); + } + } break; + + case CENTER_OF_MASS_MODE_CUSTOM: { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); + } break; + } +} + +RigidBody2D::CenterOfMassMode RigidBody2D::get_center_of_mass_mode() const { + return center_of_mass_mode; +} + +void RigidBody2D::set_center_of_mass(const Vector2 &p_center_of_mass) { + if (center_of_mass == p_center_of_mass) { + return; + } + + ERR_FAIL_COND(center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM); + center_of_mass = p_center_of_mass; + + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); +} + +const Vector2 &RigidBody2D::get_center_of_mass() const { + return center_of_mass; } void RigidBody2D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) { @@ -709,25 +658,15 @@ real_t RigidBody2D::get_angular_damp() const { } void RigidBody2D::set_axis_velocity(const Vector2 &p_axis) { - Vector2 v = state ? state->get_linear_velocity() : linear_velocity; Vector2 axis = p_axis.normalized(); - v -= axis * axis.dot(v); - v += p_axis; - if (state) { - set_linear_velocity(v); - } else { - PhysicsServer2D::get_singleton()->body_set_axis_velocity(get_rid(), p_axis); - linear_velocity = v; - } + linear_velocity -= axis * axis.dot(linear_velocity); + linear_velocity += p_axis; + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); } void RigidBody2D::set_linear_velocity(const Vector2 &p_velocity) { linear_velocity = p_velocity; - if (state) { - state->set_linear_velocity(linear_velocity); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); } Vector2 RigidBody2D::get_linear_velocity() const { @@ -736,11 +675,7 @@ Vector2 RigidBody2D::get_linear_velocity() const { void RigidBody2D::set_angular_velocity(real_t p_velocity) { angular_velocity = p_velocity; - if (state) { - state->set_angular_velocity(angular_velocity); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity); } real_t RigidBody2D::get_angular_velocity() const { @@ -925,6 +860,12 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_inertia"), &RigidBody2D::get_inertia); ClassDB::bind_method(D_METHOD("set_inertia", "inertia"), &RigidBody2D::set_inertia); + ClassDB::bind_method(D_METHOD("set_center_of_mass_mode", "mode"), &RigidBody2D::set_center_of_mass_mode); + ClassDB::bind_method(D_METHOD("get_center_of_mass_mode"), &RigidBody2D::get_center_of_mass_mode); + + ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &RigidBody2D::set_center_of_mass); + ClassDB::bind_method(D_METHOD("get_center_of_mass"), &RigidBody2D::get_center_of_mass); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &RigidBody2D::set_physics_material_override); ClassDB::bind_method(D_METHOD("get_physics_material_override"), &RigidBody2D::get_physics_material_override); @@ -981,8 +922,11 @@ void RigidBody2D::_bind_methods() { GDVIRTUAL_BIND(_integrate_forces, "state"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Dynamic,Static,DynamicLocked,Kinematic"), "set_mode", "get_mode"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp"), "set_mass", "get_mass"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp", PROPERTY_USAGE_NONE), "set_inertia", "get_inertia"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater,exp"), "set_inertia", "get_inertia"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_center_of_mass_mode", "get_center_of_mass_mode"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "center_of_mass", PROPERTY_HINT_RANGE, "-10,10,0.01,or_lesser,or_greater"), "set_center_of_mass", "get_center_of_mass"); + ADD_LINKED_PROPERTY("center_of_mass_mode", "center_of_mass"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-128,128,0.01"), "set_gravity_scale", "get_gravity_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_integrator"), "set_use_custom_integrator", "is_using_custom_integrator"); @@ -1012,14 +956,25 @@ void RigidBody2D::_bind_methods() { BIND_ENUM_CONSTANT(MODE_DYNAMIC_LOCKED); BIND_ENUM_CONSTANT(MODE_KINEMATIC); + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_AUTO); + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_CUSTOM); + BIND_ENUM_CONSTANT(CCD_MODE_DISABLED); BIND_ENUM_CONSTANT(CCD_MODE_CAST_RAY); BIND_ENUM_CONSTANT(CCD_MODE_CAST_SHAPE); } +void RigidBody2D::_validate_property(PropertyInfo &property) const { + if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM) { + if (property.name == "center_of_mass") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } +} + RigidBody2D::RigidBody2D() : PhysicsBody2D(PhysicsServer2D::BODY_MODE_DYNAMIC) { - PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &RigidBody2D::_direct_state_changed)); + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); } RigidBody2D::~RigidBody2D() { @@ -1045,14 +1000,19 @@ void RigidBody2D::_reload_physics_characteristics() { bool CharacterBody2D::move_and_slide() { // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky. - float delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); Vector2 current_platform_velocity = platform_velocity; if ((on_floor || on_wall) && platform_rid.is_valid()) { - bool excluded = (moving_platform_ignore_layers & platform_layer) != 0; + bool excluded = false; + if (on_floor) { + excluded = (moving_platform_floor_layers & platform_layer) == 0; + } else if (on_wall) { + excluded = (moving_platform_wall_layers & platform_layer) == 0; + } if (!excluded) { - // This approach makes sure there is less delay between the actual body velocity and the one we saved. + //this approach makes sure there is less delay between the actual body velocity and the one we saved PhysicsDirectBodyState2D *bs = PhysicsServer2D::get_singleton()->body_get_direct_state(platform_rid); if (bs) { Transform2D gt = get_global_transform(); @@ -1075,7 +1035,7 @@ bool CharacterBody2D::move_and_slide() { PhysicsServer2D::MotionResult floor_result; Set<RID> exclude; exclude.insert(platform_rid); - if (move_and_collide(current_platform_velocity * delta, floor_result, margin, false, false, exclude)) { + if (move_and_collide(current_platform_velocity * delta, floor_result, margin, false, false, false, exclude)) { motion_results.push_back(floor_result); _set_collision_direction(floor_result); } @@ -1095,7 +1055,7 @@ bool CharacterBody2D::move_and_slide() { return motion_results.size() > 0; } -void CharacterBody2D::_move_and_slide_grounded(real_t p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity) { +void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity) { Vector2 motion = linear_velocity * p_delta; Vector2 motion_slide_up = motion.slide(up_direction); @@ -1239,7 +1199,7 @@ void CharacterBody2D::_move_and_slide_grounded(real_t p_delta, bool p_was_on_flo } } -void CharacterBody2D::_move_and_slide_free(real_t p_delta) { +void CharacterBody2D::_move_and_slide_free(double p_delta) { Vector2 motion = linear_velocity * p_delta; platform_rid = RID(); @@ -1285,12 +1245,11 @@ void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) Transform2D gt = get_global_transform(); PhysicsServer2D::MotionResult result; - if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false)) { + if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false, true)) { bool apply = true; if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { on_floor = true; floor_normal = result.collision_normal; - platform_velocity = result.collider_velocity; _set_platform_data(result); if (floor_stop_on_slope) { @@ -1319,7 +1278,7 @@ bool CharacterBody2D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facin } PhysicsServer2D::MotionResult result; - if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false)) { + if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false, true)) { if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { return true; } @@ -1332,19 +1291,21 @@ void CharacterBody2D::_set_collision_direction(const PhysicsServer2D::MotionResu if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor on_floor = true; floor_normal = p_result.collision_normal; - platform_velocity = p_result.collider_velocity; _set_platform_data(p_result); } else if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(-up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling on_ceiling = true; } else { on_wall = true; - platform_velocity = p_result.collider_velocity; - _set_platform_data(p_result); + // Don't apply wall velocity when the collider is a CharacterBody2D. + if (Object::cast_to<CharacterBody2D>(ObjectDB::get_instance(p_result.collider_id)) == nullptr) { + _set_platform_data(p_result); + } } } void CharacterBody2D::_set_platform_data(const PhysicsServer2D::MotionResult &p_result) { platform_rid = p_result.collider; + platform_velocity = p_result.collider_velocity; platform_layer = 0; CollisionObject2D *collision_object = Object::cast_to<CollisionObject2D>(ObjectDB::get_instance(p_result.collider_id)); if (collision_object) { @@ -1468,12 +1429,20 @@ void CharacterBody2D::set_slide_on_ceiling_enabled(bool p_enabled) { slide_on_ceiling = p_enabled; } -uint32_t CharacterBody2D::get_moving_platform_ignore_layers() const { - return moving_platform_ignore_layers; +uint32_t CharacterBody2D::get_moving_platform_floor_layers() const { + return moving_platform_floor_layers; +} + +void CharacterBody2D::set_moving_platform_floor_layers(uint32_t p_exclude_layers) { + moving_platform_floor_layers = p_exclude_layers; +} + +uint32_t CharacterBody2D::get_moving_platform_wall_layers() const { + return moving_platform_wall_layers; } -void CharacterBody2D::set_moving_platform_ignore_layers(uint32_t p_exclude_layers) { - moving_platform_ignore_layers = p_exclude_layers; +void CharacterBody2D::set_moving_platform_wall_layers(uint32_t p_exclude_layers) { + moving_platform_wall_layers = p_exclude_layers; } void CharacterBody2D::set_motion_mode(MotionMode p_mode) { @@ -1558,8 +1527,10 @@ void CharacterBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_slide_on_ceiling_enabled", "enabled"), &CharacterBody2D::set_slide_on_ceiling_enabled); ClassDB::bind_method(D_METHOD("is_slide_on_ceiling_enabled"), &CharacterBody2D::is_slide_on_ceiling_enabled); - ClassDB::bind_method(D_METHOD("set_moving_platform_ignore_layers", "exclude_layer"), &CharacterBody2D::set_moving_platform_ignore_layers); - ClassDB::bind_method(D_METHOD("get_moving_platform_ignore_layers"), &CharacterBody2D::get_moving_platform_ignore_layers); + ClassDB::bind_method(D_METHOD("set_moving_platform_floor_layers", "exclude_layer"), &CharacterBody2D::set_moving_platform_floor_layers); + ClassDB::bind_method(D_METHOD("get_moving_platform_floor_layers"), &CharacterBody2D::get_moving_platform_floor_layers); + ClassDB::bind_method(D_METHOD("set_moving_platform_wall_layers", "exclude_layer"), &CharacterBody2D::set_moving_platform_wall_layers); + ClassDB::bind_method(D_METHOD("get_moving_platform_wall_layers"), &CharacterBody2D::get_moving_platform_wall_layers); ClassDB::bind_method(D_METHOD("get_max_slides"), &CharacterBody2D::get_max_slides); ClassDB::bind_method(D_METHOD("set_max_slides", "max_slides"), &CharacterBody2D::set_max_slides); @@ -1601,7 +1572,8 @@ void CharacterBody2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,1000,0.1"), "set_floor_snap_length", "get_floor_snap_length"); ADD_GROUP("Moving platform", "moving_platform"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_ignore_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_ignore_layers", "get_moving_platform_ignore_layers"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_floor_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_floor_layers", "get_moving_platform_floor_layers"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_wall_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_wall_layers", "get_moving_platform_wall_layers"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED); diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index a999317953..1d6437a3ad 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -50,7 +50,7 @@ protected: Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_test_only = false, real_t p_margin = 0.08); public: - bool move_and_collide(const Vector2 &p_motion, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, const Set<RID> &p_exclude = Set<RID>()); + bool move_and_collide(const Vector2 &p_motion, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()); bool test_move(const Transform2D &p_from, const Vector2 &p_motion, const Ref<KinematicCollision2D> &r_collision = Ref<KinematicCollision2D>(), real_t p_margin = 0.08); TypedArray<PhysicsBody2D> get_collision_exceptions(); @@ -63,20 +63,13 @@ public: class StaticBody2D : public PhysicsBody2D { GDCLASS(StaticBody2D, PhysicsBody2D); +private: Vector2 constant_linear_velocity; real_t constant_angular_velocity = 0.0; Ref<PhysicsMaterial> physics_material_override; - bool kinematic_motion = false; - bool sync_to_physics = false; - - Transform2D last_valid_transform; - - void _direct_state_changed(Object *p_state); - protected: - void _notification(int p_what); static void _bind_methods(); public: @@ -89,17 +82,32 @@ public: Vector2 get_constant_linear_velocity() const; real_t get_constant_angular_velocity() const; - virtual TypedArray<String> get_configuration_warnings() const override; - - StaticBody2D(); + StaticBody2D(PhysicsServer2D::BodyMode p_mode = PhysicsServer2D::BODY_MODE_STATIC); private: void _reload_physics_characteristics(); +}; - void _update_kinematic_motion(); +class AnimatableBody2D : public StaticBody2D { + GDCLASS(AnimatableBody2D, StaticBody2D); + +private: + bool sync_to_physics = false; + + Transform2D last_valid_transform; + + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state); + void _body_state_changed(PhysicsDirectBodyState2D *p_state); - void set_kinematic_motion_enabled(bool p_enabled); - bool is_kinematic_motion_enabled() const; +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + AnimatableBody2D(); + +private: + void _update_kinematic_motion(); void set_sync_to_physics(bool p_enable); bool is_sync_to_physics_enabled() const; @@ -116,6 +124,11 @@ public: MODE_KINEMATIC, }; + enum CenterOfMassMode { + CENTER_OF_MASS_MODE_AUTO, + CENTER_OF_MASS_MODE_CUSTOM, + }; + enum CCDMode { CCD_MODE_DISABLED, CCD_MODE_CAST_RAY, @@ -124,10 +137,13 @@ public: private: bool can_sleep = true; - PhysicsDirectBodyState2D *state = nullptr; Mode mode = MODE_DYNAMIC; real_t mass = 1.0; + real_t inertia = 0.0; + CenterOfMassMode center_of_mass_mode = CENTER_OF_MASS_MODE_AUTO; + Vector2 center_of_mass; + Ref<PhysicsMaterial> physics_material_override; real_t gravity_scale = 1.0; real_t linear_damp = -1.0; @@ -183,12 +199,16 @@ private: void _body_exit_tree(ObjectID p_id); void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape); - void _direct_state_changed(Object *p_state); + + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state); + void _body_state_changed(PhysicsDirectBodyState2D *p_state); protected: void _notification(int p_what); static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; + GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState2D *) public: @@ -201,6 +221,12 @@ public: void set_inertia(real_t p_inertia); real_t get_inertia() const; + void set_center_of_mass_mode(CenterOfMassMode p_mode); + CenterOfMassMode get_center_of_mass_mode() const; + + void set_center_of_mass(const Vector2 &p_center_of_mass); + const Vector2 &get_center_of_mass() const; + void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); Ref<PhysicsMaterial> get_physics_material_override() const; @@ -265,6 +291,7 @@ private: }; VARIANT_ENUM_CAST(RigidBody2D::Mode); +VARIANT_ENUM_CAST(RigidBody2D::CenterOfMassMode); VARIANT_ENUM_CAST(RigidBody2D::CCDMode); class CharacterBody2D : public PhysicsBody2D { @@ -310,7 +337,8 @@ private: float floor_snap_length = 0; real_t free_mode_min_slide_angle = Math::deg2rad((real_t)15.0); Vector2 up_direction = Vector2(0.0, -1.0); - uint32_t moving_platform_ignore_layers = 0; + uint32_t moving_platform_floor_layers = UINT32_MAX; + uint32_t moving_platform_wall_layers = 0; Vector2 linear_velocity; Vector2 floor_normal; @@ -350,14 +378,17 @@ private: real_t get_free_mode_min_slide_angle() const; void set_free_mode_min_slide_angle(real_t p_radians); - uint32_t get_moving_platform_ignore_layers() const; - void set_moving_platform_ignore_layers(const uint32_t p_exclude_layer); + uint32_t get_moving_platform_floor_layers() const; + void set_moving_platform_floor_layers(const uint32_t p_exclude_layer); + + uint32_t get_moving_platform_wall_layers() const; + void set_moving_platform_wall_layers(const uint32_t p_exclude_layer); void set_motion_mode(MotionMode p_mode); MotionMode get_motion_mode() const; - void _move_and_slide_free(real_t p_delta); - void _move_and_slide_grounded(real_t p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity); + void _move_and_slide_free(double p_delta); + void _move_and_slide_grounded(double p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity); Ref<KinematicCollision2D> _get_slide_collision(int p_bounce); Ref<KinematicCollision2D> _get_last_slide_collision(); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 12aa1afc45..0eb424b32c 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -236,6 +236,8 @@ Vector2i TileMap::transform_coords_layout(Vector2i p_coords, TileSet::TileOffset } int TileMap::get_effective_quadrant_size(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), 1); + // When using YSort, the quadrant size is reduced to 1 to have one CanvasItem per quadrant if (is_y_sort_enabled() && layers[p_layer].y_sort_enabled) { return 1; @@ -314,16 +316,38 @@ int TileMap::get_quadrant_size() const { return quadrant_size; } -void TileMap::set_layers_count(int p_layers_count) { - ERR_FAIL_COND(p_layers_count < 0); - _clear_internals(); +int TileMap::get_layers_count() const { + return layers.size(); +} - layers.resize(p_layers_count); +void TileMap::add_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = layers.size(); + } + + ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); + + layers.insert(p_to_pos, TileMapLayer()); _recreate_internals(); notify_property_list_changed(); - if (selected_layer >= p_layers_count) { - selected_layer = -1; + emit_signal(SNAME("changed")); + + update_configuration_warnings(); +} + +void TileMap::move_layer(int p_layer, int p_to_pos) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); + + TileMapLayer tl = layers[p_layer]; + layers.insert(p_to_pos, tl); + layers.remove(p_to_pos < p_layer ? p_layer + 1 : p_layer); + _recreate_internals(); + notify_property_list_changed(); + + if (selected_layer == p_layer) { + selected_layer = p_to_pos < p_layer ? p_to_pos - 1 : p_to_pos; } emit_signal(SNAME("changed")); @@ -331,8 +355,20 @@ void TileMap::set_layers_count(int p_layers_count) { update_configuration_warnings(); } -int TileMap::get_layers_count() const { - return layers.size(); +void TileMap::remove_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + + layers.remove(p_layer); + _recreate_internals(); + notify_property_list_changed(); + + if (selected_layer >= p_layer) { + selected_layer -= 1; + } + + emit_signal(SNAME("changed")); + + update_configuration_warnings(); } void TileMap::set_layer_name(int p_layer, String p_name) { @@ -781,7 +817,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List if (atlas_source) { // Get the tile data. TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); - Ref<ShaderMaterial> mat = tile_data->tile_get_material(); + Ref<ShaderMaterial> mat = tile_data->get_material(); int z_index = tile_data->get_z_index(); // Quandrant pos. @@ -1051,9 +1087,13 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r Vector2 quadrant_pos = map_to_world(q.coords * get_effective_quadrant_size(q.layer)); + LocalVector<int> body_shape_count; + body_shape_count.resize(q.bodies.size()); + // Clear shapes. for (int body_index = 0; body_index < q.bodies.size(); body_index++) { ps->body_clear_shapes(q.bodies[body_index]); + body_shape_count[body_index] = 0; // Position the bodies. Transform2D xform; @@ -1078,6 +1118,8 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); for (int body_index = 0; body_index < q.bodies.size(); body_index++) { + int &body_shape_index = body_shape_count[body_index]; + // Add the shapes again. for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { bool one_way_collision = tile_data->is_collision_polygon_one_way(body_index, polygon_index); @@ -1091,8 +1133,10 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r // Add decomposed convex shapes. Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(body_index, polygon_index, shape_index); ps->body_add_shape(q.bodies[body_index], shape->get_rid(), xform); - ps->body_set_shape_metadata(q.bodies[body_index], shape_index, E_cell->get()); - ps->body_set_shape_as_one_way_collision(q.bodies[body_index], shape_index, one_way_collision, one_way_collision_margin); + ps->body_set_shape_metadata(q.bodies[body_index], body_shape_index, E_cell->get()); + ps->body_set_shape_as_one_way_collision(q.bodies[body_index], body_shape_index, one_way_collision, one_way_collision_margin); + + ++body_shape_index; } } } @@ -1139,7 +1183,7 @@ void TileMap::_physics_create_quadrant(TileMapQuadrant *p_quadrant) { PhysicsServer2D::get_singleton()->body_set_space(body, space); Transform2D xform; - xform.set_origin(map_to_world(p_quadrant->coords * get_effective_quadrant_size(layer))); + xform.set_origin(map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer))); xform = global_transform * xform; PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); } @@ -2888,8 +2932,10 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_quadrant_size", "size"), &TileMap::set_quadrant_size); ClassDB::bind_method(D_METHOD("get_quadrant_size"), &TileMap::get_quadrant_size); - ClassDB::bind_method(D_METHOD("set_layers_count", "layers_count"), &TileMap::set_layers_count); ClassDB::bind_method(D_METHOD("get_layers_count"), &TileMap::get_layers_count); + ClassDB::bind_method(D_METHOD("add_layer", "to_position"), &TileMap::add_layer); + ClassDB::bind_method(D_METHOD("move_layer", "layer", "to_position"), &TileMap::move_layer); + ClassDB::bind_method(D_METHOD("remove_layer", "layer"), &TileMap::remove_layer); ClassDB::bind_method(D_METHOD("set_layer_name", "layer", "name"), &TileMap::set_layer_name); ClassDB::bind_method(D_METHOD("get_layer_name", "layer"), &TileMap::get_layer_name); ClassDB::bind_method(D_METHOD("set_layer_enabled", "layer", "enabled"), &TileMap::set_layer_enabled); @@ -2899,7 +2945,7 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_layer_y_sort_origin", "layer", "y_sort_origin"), &TileMap::set_layer_y_sort_origin); ClassDB::bind_method(D_METHOD("get_layer_y_sort_origin", "layer"), &TileMap::get_layer_y_sort_origin); ClassDB::bind_method(D_METHOD("set_layer_z_index", "layer", "z_index"), &TileMap::set_layer_z_index); - ClassDB::bind_method(D_METHOD("get_layer_z_indexd", "layer"), &TileMap::get_layer_z_index); + ClassDB::bind_method(D_METHOD("get_layer_z_index", "layer"), &TileMap::get_layer_z_index); ClassDB::bind_method(D_METHOD("set_collision_visibility_mode", "collision_visibility_mode"), &TileMap::set_collision_visibility_mode); ClassDB::bind_method(D_METHOD("get_collision_visibility_mode"), &TileMap::get_collision_visibility_mode); @@ -2934,9 +2980,7 @@ void TileMap::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_navigation_visibility_mode", "get_navigation_visibility_mode"); - ADD_GROUP("Layers", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "layers_count"), "set_layers_count", "get_layers_count"); - ADD_PROPERTY_DEFAULT("layers_count", 1); + ADD_ARRAY("layers", "layer_"); ADD_PROPERTY_DEFAULT("format", FORMAT_1); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 4e2d76a7b7..3ac50fc7cc 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -308,8 +308,10 @@ public: static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0)); // Layers management. - void set_layers_count(int p_layers_count); int get_layers_count() const; + void add_layer(int p_to_pos); + void move_layer(int p_layer, int p_to_pos); + void remove_layer(int p_layer); void set_layer_name(int p_layer, String p_name); String get_layer_name(int p_layer) const; void set_layer_enabled(int p_layer, bool p_visible); diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp index 00e4e1dc62..8bd7b696f2 100644 --- a/scene/2d/touch_screen_button.cpp +++ b/scene/2d/touch_screen_button.cpp @@ -185,7 +185,7 @@ String TouchScreenButton::get_action() const { return action; } -void TouchScreenButton::_input(const Ref<InputEvent> &p_event) { +void TouchScreenButton::input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!get_tree()) { @@ -288,7 +288,7 @@ void TouchScreenButton::_press(int p_finger_pressed) { iea.instantiate(); iea->set_action(action); iea->set_pressed(true); - get_viewport()->input(iea, true); + get_viewport()->push_input(iea, true); } emit_signal(SNAME("pressed")); @@ -305,7 +305,7 @@ void TouchScreenButton::_release(bool p_exiting_tree) { iea.instantiate(); iea->set_action(action); iea->set_pressed(false); - get_viewport()->input(iea, true); + get_viewport()->push_input(iea, true); } } @@ -384,8 +384,6 @@ void TouchScreenButton::_bind_methods() { ClassDB::bind_method(D_METHOD("is_pressed"), &TouchScreenButton::is_pressed); - ClassDB::bind_method(D_METHOD("_input"), &TouchScreenButton::_input); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "pressed", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_pressed", "get_texture_pressed"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "bitmask", PROPERTY_HINT_RESOURCE_TYPE, "BitMap"), "set_bitmask", "get_bitmask"); diff --git a/scene/2d/touch_screen_button.h b/scene/2d/touch_screen_button.h index 10820ad059..1c515149d4 100644 --- a/scene/2d/touch_screen_button.h +++ b/scene/2d/touch_screen_button.h @@ -61,7 +61,7 @@ private: VisibilityMode visibility = VISIBILITY_ALWAYS; - void _input(const Ref<InputEvent> &p_event); + virtual void input(const Ref<InputEvent> &p_event) override; bool _is_point_inside(const Point2 &p_point); diff --git a/scene/3d/area_3d.cpp b/scene/3d/area_3d.cpp index 2e917c4a42..943586f43c 100644 --- a/scene/3d/area_3d.cpp +++ b/scene/3d/area_3d.cpp @@ -105,6 +105,61 @@ real_t Area3D::get_priority() const { return priority; } +void Area3D::set_wind_force_magnitude(real_t p_wind_force_magnitude) { + wind_force_magnitude = p_wind_force_magnitude; + if (is_inside_tree()) { + _initialize_wind(); + } +} + +real_t Area3D::get_wind_force_magnitude() const { + return wind_force_magnitude; +} + +void Area3D::set_wind_attenuation_factor(real_t p_wind_force_attenuation_factor) { + wind_attenuation_factor = p_wind_force_attenuation_factor; + if (is_inside_tree()) { + _initialize_wind(); + } +} + +real_t Area3D::get_wind_attenuation_factor() const { + return wind_attenuation_factor; +} + +void Area3D::set_wind_source_path(const NodePath &p_wind_source_path) { + wind_source_path = p_wind_source_path; + if (is_inside_tree()) { + _initialize_wind(); + } +} + +const NodePath &Area3D::get_wind_source_path() const { + return wind_source_path; +} + +void Area3D::_initialize_wind() { + real_t temp_magnitude = 0.0; + Vector3 wind_direction(0., 0., 0.); + Vector3 wind_source(0., 0., 0.); + + // Overwrite with area-specified info if available + if (!wind_source_path.is_empty()) { + Node3D *p_wind_source = Object::cast_to<Node3D>(get_node(wind_source_path)); + ERR_FAIL_NULL(p_wind_source); + Transform3D global_transform = p_wind_source->get_transform(); + wind_direction = -global_transform.basis.get_axis(Vector3::AXIS_Z).normalized(); + wind_source = global_transform.origin; + temp_magnitude = wind_force_magnitude; + } + + // Set force, source and direction in the physics server. + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_ATTENUATION_FACTOR, wind_attenuation_factor); + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_SOURCE, wind_source); + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_DIRECTION, wind_direction); + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_FORCE_MAGNITUDE, temp_magnitude); +} + void Area3D::_body_enter_tree(ObjectID p_id) { Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); @@ -264,6 +319,8 @@ void Area3D::_clear_monitoring() { void Area3D::_notification(int p_what) { if (p_what == NOTIFICATION_EXIT_TREE) { _clear_monitoring(); + } else if (p_what == NOTIFICATION_ENTER_TREE) { + _initialize_wind(); } } @@ -550,6 +607,15 @@ void Area3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_priority", "priority"), &Area3D::set_priority); ClassDB::bind_method(D_METHOD("get_priority"), &Area3D::get_priority); + ClassDB::bind_method(D_METHOD("set_wind_force_magnitude", "wind_force_magnitude"), &Area3D::set_wind_force_magnitude); + ClassDB::bind_method(D_METHOD("get_wind_force_magnitude"), &Area3D::get_wind_force_magnitude); + + ClassDB::bind_method(D_METHOD("set_wind_attenuation_factor", "wind_attenuation_factor"), &Area3D::set_wind_attenuation_factor); + ClassDB::bind_method(D_METHOD("get_wind_attenuation_factor"), &Area3D::get_wind_attenuation_factor); + + ClassDB::bind_method(D_METHOD("set_wind_source_path", "wind_source_path"), &Area3D::set_wind_source_path); + ClassDB::bind_method(D_METHOD("get_wind_source_path"), &Area3D::get_wind_source_path); + ClassDB::bind_method(D_METHOD("set_monitorable", "enable"), &Area3D::set_monitorable); ClassDB::bind_method(D_METHOD("is_monitorable"), &Area3D::is_monitorable); @@ -605,6 +671,9 @@ void Area3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity", PROPERTY_HINT_RANGE, "-32,32,0.001,or_lesser,or_greater"), "set_gravity", "get_gravity"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wind_force_magnitude", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater"), "set_wind_force_magnitude", "get_wind_force_magnitude"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wind_attenuation_factor", PROPERTY_HINT_RANGE, "0.0,3.0,0.001,or_greater"), "set_wind_attenuation_factor", "get_wind_attenuation_factor"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "wind_source_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_wind_source_path", "get_wind_source_path"); ADD_GROUP("Audio Bus", "audio_bus_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_bus_override"), "set_audio_bus_override", "is_overriding_audio_bus"); diff --git a/scene/3d/area_3d.h b/scene/3d/area_3d.h index 5b8d612717..847d1c5966 100644 --- a/scene/3d/area_3d.h +++ b/scene/3d/area_3d.h @@ -55,6 +55,9 @@ private: real_t angular_damp = 0.1; real_t linear_damp = 0.1; int priority = 0; + real_t wind_force_magnitude = 0.0; + real_t wind_attenuation_factor = 0.0; + NodePath wind_source_path; bool monitoring = false; bool monitorable = false; bool locked = false; @@ -134,6 +137,8 @@ private: void _validate_property(PropertyInfo &property) const override; + void _initialize_wind(); + protected: void _notification(int p_what); static void _bind_methods(); @@ -163,6 +168,15 @@ public: void set_priority(real_t p_priority); real_t get_priority() const; + void set_wind_force_magnitude(real_t p_wind_force_magnitude); + real_t get_wind_force_magnitude() const; + + void set_wind_attenuation_factor(real_t p_wind_attenuation_factor); + real_t get_wind_attenuation_factor() const; + + void set_wind_source_path(const NodePath &p_wind_source_path); + const NodePath &get_wind_source_path() const; + void set_monitoring(bool p_enable); bool is_monitoring() const; diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index d2424c9a3b..907c6cd03a 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -95,7 +95,7 @@ static const Vector3 speaker_directions[7] = { Vector3(1.0, 0.0, 0.0).normalized(), // side-right }; -void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tightness, AudioStreamPlayer3D::Output &output) { +void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tightness, Vector<AudioFrame> &output) { unsigned int speaker_count = 0; // only main speakers (no LFE) switch (AudioServer::get_singleton()->get_speaker_mode()) { case AudioServer::SPEAKER_MODE_STEREO: @@ -118,182 +118,94 @@ void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tig switch (AudioServer::get_singleton()->get_speaker_mode()) { case AudioServer::SPEAKER_SURROUND_71: - output.vol[3].l = volumes[5]; // side-left - output.vol[3].r = volumes[6]; // side-right + output.write[3].l = volumes[5]; // side-left + output.write[3].r = volumes[6]; // side-right [[fallthrough]]; case AudioServer::SPEAKER_SURROUND_51: - output.vol[2].l = volumes[3]; // rear-left - output.vol[2].r = volumes[4]; // rear-right + output.write[2].l = volumes[3]; // rear-left + output.write[2].r = volumes[4]; // rear-right [[fallthrough]]; case AudioServer::SPEAKER_SURROUND_31: - output.vol[1].r = 1.0; // LFE - always full power - output.vol[1].l = volumes[2]; // center + output.write[1].r = 1.0; // LFE - always full power + output.write[1].l = volumes[2]; // center [[fallthrough]]; case AudioServer::SPEAKER_MODE_STEREO: - output.vol[0].r = volumes[1]; // front-right - output.vol[0].l = volumes[0]; // front-left + output.write[0].r = volumes[1]; // front-right + output.write[0].l = volumes[0]; // front-left break; } } -void AudioStreamPlayer3D::_mix_audio() { - if (!stream_playback.is_valid() || !active.is_set() || - (stream_paused && !stream_paused_fade_out)) { - return; - } - - bool started = false; - if (setseek.get() >= 0.0) { - stream_playback->start(setseek.get()); - setseek.set(-1.0); //reset seek - started = true; - } +void AudioStreamPlayer3D::_calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol) { + reverb_vol.resize(4); + reverb_vol.write[0] = AudioFrame(0, 0); + reverb_vol.write[1] = AudioFrame(0, 0); + reverb_vol.write[2] = AudioFrame(0, 0); + reverb_vol.write[3] = AudioFrame(0, 0); - //get data - AudioFrame *buffer = mix_buffer.ptrw(); - int buffer_size = mix_buffer.size(); + float uniformity = area->get_reverb_uniformity(); + float area_send = area->get_reverb_amount(); - if (stream_paused_fade_out) { - // Short fadeout ramp - buffer_size = MIN(buffer_size, 128); - } - - // Mix if we're not paused or we're fading out - if ((output_count.get() > 0 || out_of_range_mode == OUT_OF_RANGE_MIX)) { - float output_pitch_scale = 0.0; - if (output_count.get()) { - //used for doppler, not realistic but good enough - for (int i = 0; i < output_count.get(); i++) { - output_pitch_scale += outputs[i].pitch_scale; - } - output_pitch_scale /= float(output_count.get()); - } else { - output_pitch_scale = 1.0; - } + if (uniformity > 0.0) { + float distance = listener_area_pos.length(); + float attenuation = Math::db2linear(_get_attenuation_db(distance)); - stream_playback->mix(buffer, pitch_scale * output_pitch_scale, buffer_size); - } + // Determine the fraction of sound that would come from each speaker if they were all driven uniformly. + float center_val[3] = { 0.5f, 0.25f, 0.16666f }; + int channel_count = AudioServer::get_singleton()->get_channel_count(); + AudioFrame center_frame(center_val[channel_count - 1], center_val[channel_count - 1]); - //write all outputs - for (int i = 0; i < output_count.get(); i++) { - Output current = outputs[i]; + if (attenuation < 1.0) { + //pan the uniform sound + Vector3 rev_pos = listener_area_pos; + rev_pos.y = 0; + rev_pos.normalize(); - //see if current output exists, to keep volume ramp - bool found = false; - for (int j = i; j < prev_output_count; j++) { - if (prev_outputs[j].viewport == current.viewport) { - if (j != i) { - SWAP(prev_outputs[j], prev_outputs[i]); - } - found = true; - break; + if (channel_count >= 1) { + // Stereo pair + float c = rev_pos.x * 0.5 + 0.5; + reverb_vol.write[0].l = 1.0 - c; + reverb_vol.write[0].r = c; } - } - bool interpolate_filter = !started; + if (channel_count >= 3) { + // Center pair + Side pair + float xl = Vector3(-1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5; + float xr = Vector3(1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5; - if (!found) { - //create new if was not used before - if (prev_output_count < MAX_OUTPUTS) { - prev_outputs[prev_output_count] = prev_outputs[i]; //may be owned by another viewport - prev_output_count++; + reverb_vol.write[1].l = xl; + reverb_vol.write[1].r = xr; + reverb_vol.write[2].l = 1.0 - xr; + reverb_vol.write[2].r = 1.0 - xl; } - prev_outputs[i] = current; - interpolate_filter = false; - } - - //mix! - - int buffers = AudioServer::get_singleton()->get_channel_count(); - - for (int k = 0; k < buffers; k++) { - AudioFrame target_volume = stream_paused_fade_out ? AudioFrame(0.f, 0.f) : current.vol[k]; - AudioFrame vol_prev = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : prev_outputs[i].vol[k]; - AudioFrame vol_inc = (target_volume - vol_prev) / float(buffer_size); - AudioFrame vol = vol_prev; - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, k)) { - continue; //may have been deleted, will be updated on process + if (channel_count >= 4) { + // Rear pair + // FIXME: Not sure what math should be done here + float c = rev_pos.x * 0.5 + 0.5; + reverb_vol.write[3].l = 1.0 - c; + reverb_vol.write[3].r = c; } - AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, k); - current.filter.set_mode(AudioFilterSW::HIGHSHELF); - current.filter.set_sampling_rate(AudioServer::get_singleton()->get_mix_rate()); - current.filter.set_cutoff(attenuation_filter_cutoff_hz); - current.filter.set_resonance(1); - current.filter.set_stages(1); - current.filter.set_gain(current.filter_gain); - - if (interpolate_filter) { - current.filter_process[k * 2 + 0] = prev_outputs[i].filter_process[k * 2 + 0]; - current.filter_process[k * 2 + 1] = prev_outputs[i].filter_process[k * 2 + 1]; - - current.filter_process[k * 2 + 0].set_filter(¤t.filter, false); - current.filter_process[k * 2 + 1].set_filter(¤t.filter, false); - - current.filter_process[k * 2 + 0].update_coeffs(buffer_size); - current.filter_process[k * 2 + 1].update_coeffs(buffer_size); - for (int j = 0; j < buffer_size; j++) { - AudioFrame f = buffer[j] * vol; - current.filter_process[k * 2 + 0].process_one_interp(f.l); - current.filter_process[k * 2 + 1].process_one_interp(f.r); - - target[j] += f; - vol += vol_inc; - } - } else { - current.filter_process[k * 2 + 0].set_filter(¤t.filter); - current.filter_process[k * 2 + 1].set_filter(¤t.filter); - - current.filter_process[k * 2 + 0].update_coeffs(); - current.filter_process[k * 2 + 1].update_coeffs(); - for (int j = 0; j < buffer_size; j++) { - AudioFrame f = buffer[j] * vol; - current.filter_process[k * 2 + 0].process_one(f.l); - current.filter_process[k * 2 + 1].process_one(f.r); - - target[j] += f; - vol += vol_inc; - } + for (int i = 0; i < channel_count; i++) { + reverb_vol.write[i] = reverb_vol[i].lerp(center_frame, attenuation); } - - if (current.reverb_bus_index >= 0) { - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.reverb_bus_index, k)) { - continue; //may have been deleted, will be updated on process - } - - AudioFrame *rtarget = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.reverb_bus_index, k); - - if (current.reverb_bus_index == prev_outputs[i].reverb_bus_index) { - AudioFrame rvol_inc = (current.reverb_vol[k] - prev_outputs[i].reverb_vol[k]) / float(buffer_size); - AudioFrame rvol = prev_outputs[i].reverb_vol[k]; - - for (int j = 0; j < buffer_size; j++) { - rtarget[j] += buffer[j] * rvol; - rvol += rvol_inc; - } - } else { - AudioFrame rvol = current.reverb_vol[k]; - for (int j = 0; j < buffer_size; j++) { - rtarget[j] += buffer[j] * rvol; - } - } + } else { + for (int i = 0; i < channel_count; i++) { + reverb_vol.write[i] = center_frame; } } - prev_outputs[i] = current; - } - - prev_output_count = output_count.get(); + for (int i = 0; i < channel_count; i++) { + reverb_vol.write[i] = direct_path_vol[i].lerp(reverb_vol[i] * attenuation, uniformity); + reverb_vol.write[i] *= area_send; + } - //stream is no longer active, disable this. - if (!stream_playback->is_playing()) { - active.clear(); + } else { + for (int i = 0; i < 4; i++) { + reverb_vol.write[i] = direct_path_vol[i] * area_send; + } } - - output_ready.clear(); - stream_paused_fade_in = false; - stream_paused_fade_out = false; } float AudioStreamPlayer3D::_get_attenuation_db(float p_distance) const { @@ -329,14 +241,15 @@ float AudioStreamPlayer3D::_get_attenuation_db(float p_distance) const { void AudioStreamPlayer3D::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { velocity_tracker->reset(get_global_transform().origin); - AudioServer::get_singleton()->add_callback(_mix_audios, this); + AudioServer::get_singleton()->add_listener_changed_callback(_listener_changed_cb, this); if (autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); } } if (p_what == NOTIFICATION_EXIT_TREE) { - AudioServer::get_singleton()->remove_callback(_mix_audios, this); + stop(); + AudioServer::get_singleton()->remove_listener_changed_callback(_listener_changed_cb, this); } if (p_what == NOTIFICATION_PAUSED) { @@ -358,281 +271,240 @@ void AudioStreamPlayer3D::_notification(int p_what) { if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course + Vector<AudioFrame> volume_vector; + if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { + volume_vector = _update_panning(); + } - if (!output_ready.is_set()) { - Vector3 linear_velocity; + if (setplay.get() >= 0 && stream.is_valid()) { + active.set(); + Ref<AudioStreamPlayback> new_playback = stream->instance_playback(); + ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback."); + Map<StringName, Vector<AudioFrame>> bus_map; + bus_map[_get_actual_bus()] = volume_vector; + AudioServer::get_singleton()->start_playback_stream(new_playback, bus_map, setplay.get(), linear_attenuation, attenuation_filter_cutoff_hz, actual_pitch_scale); + stream_playbacks.push_back(new_playback); + setplay.set(-1); + } - //compute linear velocity for doppler - if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { - linear_velocity = velocity_tracker->get_tracked_linear_velocity(); + if (!stream_playbacks.is_empty() && active.is_set()) { + // Stop playing if no longer active. + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. + active.clear(); + set_physics_process_internal(false); + } + } - Ref<World3D> world_3d = get_world_3d(); - ERR_FAIL_COND(world_3d.is_null()); - - int new_output_count = 0; + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); + } + } +} - Vector3 global_pos = get_global_transform().origin; +Area3D *AudioStreamPlayer3D::_get_overriding_area() { + //check if any area is diverting sound into a bus + Ref<World3D> world_3d = get_world_3d(); + ERR_FAIL_COND_V(world_3d.is_null(), nullptr); - int bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus); + Vector3 global_pos = get_global_transform().origin; - //check if any area is diverting sound into a bus + PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space()); - PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space()); + PhysicsDirectSpaceState3D::ShapeResult sr[MAX_INTERSECT_AREAS]; - PhysicsDirectSpaceState3D::ShapeResult sr[MAX_INTERSECT_AREAS]; + int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); - int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); - Area3D *area = nullptr; + for (int i = 0; i < areas; i++) { + if (!sr[i].collider) { + continue; + } - for (int i = 0; i < areas; i++) { - if (!sr[i].collider) { - continue; - } + Area3D *tarea = Object::cast_to<Area3D>(sr[i].collider); + if (!tarea) { + continue; + } - Area3D *tarea = Object::cast_to<Area3D>(sr[i].collider); - if (!tarea) { - continue; - } + if (!tarea->is_overriding_audio_bus() && !tarea->is_using_reverb_bus()) { + continue; + } - if (!tarea->is_overriding_audio_bus() && !tarea->is_using_reverb_bus()) { - continue; - } + return tarea; + } + return nullptr; +} - area = tarea; - break; - } +StringName AudioStreamPlayer3D::_get_actual_bus() { + Area3D *overriding_area = _get_overriding_area(); + if (overriding_area && overriding_area->is_overriding_audio_bus() && !overriding_area->is_using_reverb_bus()) { + return overriding_area->get_audio_bus_name(); + } + return bus; +} - for (const Set<Camera3D *>::Element *E = world_3d->get_cameras().front(); E; E = E->next()) { - Camera3D *camera = E->get(); - Viewport *vp = camera->get_viewport(); - if (!vp->is_audio_listener_3d()) { - continue; - } +Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { + Vector<AudioFrame> output_volume_vector; + output_volume_vector.resize(4); + for (AudioFrame &frame : output_volume_vector) { + frame = AudioFrame(0, 0); + } - bool listener_is_camera = true; - Node3D *listener_node = camera; + if (!active.is_set() || stream.is_null()) { + return output_volume_vector; + } - Listener3D *listener = vp->get_listener_3d(); - if (listener) { - listener_node = listener; - listener_is_camera = false; - } + Vector3 linear_velocity; - Vector3 local_pos = listener_node->get_global_transform().orthonormalized().affine_inverse().xform(global_pos); + //compute linear velocity for doppler + if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { + linear_velocity = velocity_tracker->get_tracked_linear_velocity(); + } - float dist = local_pos.length(); + Vector3 global_pos = get_global_transform().origin; - Vector3 area_sound_pos; - Vector3 listener_area_pos; + Ref<World3D> world_3d = get_world_3d(); + ERR_FAIL_COND_V(world_3d.is_null(), output_volume_vector); - if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) { - area_sound_pos = space_state->get_closest_point_to_object_volume(area->get_rid(), listener_node->get_global_transform().origin); - listener_area_pos = listener_node->to_local(area_sound_pos); - } + Set<Camera3D *> cameras = world_3d->get_cameras(); + cameras.insert(get_viewport()->get_camera_3d()); - if (max_distance > 0) { - float total_max = max_distance; + PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space()); - if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) { - total_max = MAX(total_max, listener_area_pos.length()); - } - if (total_max > max_distance) { - continue; //can't hear this sound in this listener - } - } - - float multiplier = Math::db2linear(_get_attenuation_db(dist)); - if (max_distance > 0) { - multiplier *= MAX(0, 1.0 - (dist / max_distance)); - } + for (Camera3D *camera : cameras) { + Viewport *vp = camera->get_viewport(); + if (!vp->is_audio_listener_3d()) { + continue; + } - Output output; - output.bus_index = bus_index; - output.reverb_bus_index = -1; //no reverb by default - output.viewport = vp; + bool listener_is_camera = true; + Node3D *listener_node = camera; - float db_att = (1.0 - MIN(1.0, multiplier)) * attenuation_filter_db; + Listener3D *listener = vp->get_listener_3d(); + if (listener) { + listener_node = listener; + listener_is_camera = false; + } - if (emission_angle_enabled) { - Vector3 listenertopos = global_pos - listener_node->get_global_transform().origin; - float c = listenertopos.normalized().dot(get_global_transform().basis.get_axis(2).normalized()); //it's z negative - float angle = Math::rad2deg(Math::acos(c)); - if (angle > emission_angle) { - db_att -= -emission_angle_filter_attenuation_db; - } - } + Vector3 local_pos = listener_node->get_global_transform().orthonormalized().affine_inverse().xform(global_pos); - output.filter_gain = Math::db2linear(db_att); + float dist = local_pos.length(); - //TODO: The lower the second parameter (tightness) the more the sound will "enclose" the listener (more undirected / playing from - // speakers not facing the source) - this could be made distance dependent. - _calc_output_vol(local_pos.normalized(), 4.0, output); + Vector3 area_sound_pos; + Vector3 listener_area_pos; - unsigned int cc = AudioServer::get_singleton()->get_channel_count(); - for (unsigned int k = 0; k < cc; k++) { - output.vol[k] *= multiplier; - } + Area3D *area = _get_overriding_area(); - bool filled_reverb = false; - int vol_index_max = AudioServer::get_singleton()->get_speaker_mode() + 1; - - if (area) { - if (area->is_overriding_audio_bus()) { - //override audio bus - StringName bus_name = area->get_audio_bus_name(); - output.bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name); - } - - if (area->is_using_reverb_bus()) { - filled_reverb = true; - StringName bus_name = area->get_reverb_bus(); - output.reverb_bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name); - - float uniformity = area->get_reverb_uniformity(); - float area_send = area->get_reverb_amount(); - - if (uniformity > 0.0) { - float distance = listener_area_pos.length(); - float attenuation = Math::db2linear(_get_attenuation_db(distance)); - - //float dist_att_db = -20 * Math::log(dist + 0.00001); //logarithmic attenuation, like in real life - - float center_val[3] = { 0.5f, 0.25f, 0.16666f }; - AudioFrame center_frame(center_val[vol_index_max - 1], center_val[vol_index_max - 1]); - - if (attenuation < 1.0) { - //pan the uniform sound - Vector3 rev_pos = listener_area_pos; - rev_pos.y = 0; - rev_pos.normalize(); - - if (cc >= 1) { - // Stereo pair - float c = rev_pos.x * 0.5 + 0.5; - output.reverb_vol[0].l = 1.0 - c; - output.reverb_vol[0].r = c; - } - - if (cc >= 3) { - // Center pair + Side pair - float xl = Vector3(-1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5; - float xr = Vector3(1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5; - - output.reverb_vol[1].l = xl; - output.reverb_vol[1].r = xr; - output.reverb_vol[2].l = 1.0 - xr; - output.reverb_vol[2].r = 1.0 - xl; - } - - if (cc >= 4) { - // Rear pair - // FIXME: Not sure what math should be done here - float c = rev_pos.x * 0.5 + 0.5; - output.reverb_vol[3].l = 1.0 - c; - output.reverb_vol[3].r = c; - } - - for (int i = 0; i < vol_index_max; i++) { - output.reverb_vol[i] = output.reverb_vol[i].lerp(center_frame, attenuation); - } - } else { - for (int i = 0; i < vol_index_max; i++) { - output.reverb_vol[i] = center_frame; - } - } - - for (int i = 0; i < vol_index_max; i++) { - output.reverb_vol[i] = output.vol[i].lerp(output.reverb_vol[i] * attenuation, uniformity); - output.reverb_vol[i] *= area_send; - } - - } else { - for (int i = 0; i < vol_index_max; i++) { - output.reverb_vol[i] = output.vol[i] * area_send; - } - } - } - } + if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) { + area_sound_pos = space_state->get_closest_point_to_object_volume(area->get_rid(), listener_node->get_global_transform().origin); + listener_area_pos = listener_node->get_global_transform().affine_inverse().xform(area_sound_pos); + } - if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { - Vector3 listener_velocity; + if (max_distance > 0) { + float total_max = max_distance; - if (listener_is_camera) { - listener_velocity = camera->get_doppler_tracked_velocity(); - } + if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) { + total_max = MAX(total_max, listener_area_pos.length()); + } + if (total_max > max_distance) { + continue; //can't hear this sound in this listener + } + } - Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity); + float multiplier = Math::db2linear(_get_attenuation_db(dist)); + if (max_distance > 0) { + multiplier *= MAX(0, 1.0 - (dist / max_distance)); + } - if (local_velocity == Vector3()) { - output.pitch_scale = 1.0; - } else { - float approaching = local_pos.normalized().dot(local_velocity.normalized()); - float velocity = local_velocity.length(); - float speed_of_sound = 343.0; + float db_att = (1.0 - MIN(1.0, multiplier)) * attenuation_filter_db; - output.pitch_scale = speed_of_sound / (speed_of_sound + velocity * approaching); - output.pitch_scale = CLAMP(output.pitch_scale, (1 / 8.0), 8.0); //avoid crazy stuff - } + if (emission_angle_enabled) { + Vector3 listenertopos = global_pos - listener_node->get_global_transform().origin; + float c = listenertopos.normalized().dot(get_global_transform().basis.get_axis(2).normalized()); //it's z negative + float angle = Math::rad2deg(Math::acos(c)); + if (angle > emission_angle) { + db_att -= -emission_angle_filter_attenuation_db; + } + } - } else { - output.pitch_scale = 1.0; - } + linear_attenuation = Math::db2linear(db_att); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_highshelf_params(playback, linear_attenuation, attenuation_filter_cutoff_hz); + } + //TODO: The lower the second parameter (tightness) the more the sound will "enclose" the listener (more undirected / playing from + // speakers not facing the source) - this could be made distance dependent. + _calc_output_vol(local_pos.normalized(), 4.0, output_volume_vector); - if (!filled_reverb) { - for (int i = 0; i < vol_index_max; i++) { - output.reverb_vol[i] = AudioFrame(0, 0); - } - } + for (unsigned int k = 0; k < 4; k++) { + output_volume_vector.write[k] = multiplier * output_volume_vector[k]; + } - outputs[new_output_count] = output; - new_output_count++; - if (new_output_count == MAX_OUTPUTS) { - break; - } + Map<StringName, Vector<AudioFrame>> bus_volumes; + if (area) { + if (area->is_overriding_audio_bus()) { + //override audio bus + bus_volumes[area->get_audio_bus_name()] = output_volume_vector; } - output_count.set(new_output_count); - output_ready.set(); - } - - //start playing if requested - if (setplay.get() >= 0.0) { - setseek.set(setplay.get()); - active.set(); - setplay.set(-1); + if (area->is_using_reverb_bus()) { + StringName reverb_bus_name = area->get_reverb_bus(); + Vector<AudioFrame> reverb_vol; + _calc_reverb_vol(area, listener_area_pos, output_volume_vector, reverb_vol); + bus_volumes[reverb_bus_name] = reverb_vol; + } + } else { + bus_volumes[bus] = output_volume_vector; } - //stop playing if no longer active - if (!active.is_set()) { - set_physics_process_internal(false); - emit_signal(SNAME("finished")); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_volumes_linear(playback, bus_volumes); } - } -} -void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) { - AudioServer::get_singleton()->lock(); + if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { + Vector3 listener_velocity; - mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); + if (listener_is_camera) { + listener_velocity = camera->get_doppler_tracked_velocity(); + } - if (stream_playback.is_valid()) { - stream_playback.unref(); - stream.unref(); - active.clear(); - setseek.set(-1); - } + Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity); - if (p_stream.is_valid()) { - stream = p_stream; - stream_playback = p_stream->instance_playback(); - } + if (local_velocity != Vector3()) { + float approaching = local_pos.normalized().dot(local_velocity.normalized()); + float velocity = local_velocity.length(); + float speed_of_sound = 343.0; - AudioServer::get_singleton()->unlock(); + float doppler_pitch_scale = pitch_scale * speed_of_sound / (speed_of_sound + velocity * approaching); + doppler_pitch_scale = CLAMP(doppler_pitch_scale, (1 / 8.0), 8.0); //avoid crazy stuff - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); + actual_pitch_scale = doppler_pitch_scale; + } else { + actual_pitch_scale = pitch_scale; + } + } else { + actual_pitch_scale = pitch_scale; + } + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, actual_pitch_scale); + } } + return output_volume_vector; +} + +void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) { + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer3D::get_stream() const { @@ -673,49 +545,47 @@ float AudioStreamPlayer3D::get_pitch_scale() const { } void AudioStreamPlayer3D::play(float p_from_pos) { - if (!is_playing()) { - // Reset the prev_output_count if the stream is stopped - prev_output_count = 0; + if (stream.is_null()) { + return; } - - if (stream_playback.is_valid()) { - setplay.set(p_from_pos); - output_ready.clear(); - set_physics_process_internal(true); + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); } + setplay.set(p_from_pos); + active.set(); + set_physics_process_internal(true); } void AudioStreamPlayer3D::seek(float p_seconds) { - if (stream_playback.is_valid()) { - setseek.set(p_seconds); - } + stop(); + play(p_seconds); } void AudioStreamPlayer3D::stop() { - if (stream_playback.is_valid()) { - active.clear(); - set_physics_process_internal(false); - setplay.set(-1); + setplay.set(-1); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_physics_process_internal(false); } bool AudioStreamPlayer3D::is_playing() const { - if (stream_playback.is_valid()) { - return active.is_set() || setplay.get() >= 0; + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer3D::get_playback_position() { - if (stream_playback.is_valid()) { - float ss = setseek.get(); - if (ss >= 0.0) { - return ss; - } - return stream_playback->get_playback_position(); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } @@ -732,7 +602,7 @@ StringName AudioStreamPlayer3D::get_bus() const { return bus; } } - return "Master"; + return SNAME("Master"); } void AudioStreamPlayer3D::set_autoplay(bool p_enable) { @@ -875,19 +745,35 @@ AudioStreamPlayer3D::DopplerTracking AudioStreamPlayer3D::get_doppler_tracking() } void AudioStreamPlayer3D::set_stream_paused(bool p_pause) { - if (p_pause != stream_paused) { - stream_paused = p_pause; - stream_paused_fade_in = !stream_paused; - stream_paused_fade_out = stream_paused; + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer3D::get_stream_paused() const { - return stream_paused; + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); + } + return false; } Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; +} + +void AudioStreamPlayer3D::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer3D::get_max_polyphony() const { + return max_polyphony; } void AudioStreamPlayer3D::_bind_methods() { @@ -955,6 +841,9 @@ void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer3D::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer3D::get_stream_paused); + ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer3D::set_max_polyphony); + ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer3D::get_max_polyphony); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -968,6 +857,7 @@ void AudioStreamPlayer3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,1,or_greater,exp"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "out_of_range_mode", PROPERTY_HINT_ENUM, "Mix,Pause"), "set_out_of_range_mode", "get_out_of_range_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); ADD_GROUP("Emission Angle", "emission_angle"); diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index f86e19403c..abd5a841b2 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -31,10 +31,13 @@ #ifndef AUDIO_STREAM_PLAYER_3D_H #define AUDIO_STREAM_PLAYER_3D_H +#include "core/os/mutex.h" +#include "scene/3d/area_3d.h" #include "scene/3d/node_3d.h" #include "scene/3d/velocity_tracker_3d.h" #include "servers/audio/audio_filter_sw.h" #include "servers/audio/audio_stream.h" +#include "servers/audio_server.h" class Camera3D; class AudioStreamPlayer3D : public Node3D { @@ -66,32 +69,10 @@ private: }; - struct Output { - AudioFilterSW filter; - AudioFilterSW::Processor filter_process[8]; - AudioFrame vol[4]; - float filter_gain = 0.0; - float pitch_scale = 0.0; - int bus_index = -1; - int reverb_bus_index = -1; - AudioFrame reverb_vol[4]; - Viewport *viewport = nullptr; //pointer only used for reference to previous mix - }; - - Output outputs[MAX_OUTPUTS]; - SafeNumeric<int> output_count; - SafeFlag output_ready; - - //these are used by audio thread to have a reference of previous volumes (for ramping volume and avoiding clicks) - Output prev_outputs[MAX_OUTPUTS]; - int prev_output_count = 0; - - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - Vector<AudioFrame> mix_buffer; - SafeNumeric<float> setseek{ -1.0 }; - SafeFlag active; + SafeFlag active{ false }; SafeNumeric<float> setplay{ -1.0 }; AttenuationModel attenuation_model = ATTENUATION_INVERSE_DISTANCE; @@ -99,18 +80,25 @@ private: float unit_size = 10.0; float max_db = 3.0; float pitch_scale = 1.0; + // Internally used to take doppler tracking into account. + float actual_pitch_scale = 1.0; bool autoplay = false; - bool stream_paused = false; - bool stream_paused_fade_in = false; - bool stream_paused_fade_out = false; - StringName bus; + StringName bus = SNAME("Master"); + int max_polyphony = 1; + + uint64_t last_mix_count = -1; - static void _calc_output_vol(const Vector3 &source_dir, real_t tightness, Output &output); - void _mix_audio(); - static void _mix_audios(void *self) { reinterpret_cast<AudioStreamPlayer3D *>(self)->_mix_audio(); } + static void _calc_output_vol(const Vector3 &source_dir, real_t tightness, Vector<AudioFrame> &output); + + void _calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol); + + static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer3D *>(self)->_update_panning(); } void _set_playing(bool p_enable); bool _is_active() const; + StringName _get_actual_bus(); + Area3D *_get_overriding_area(); + Vector<AudioFrame> _update_panning(); void _bus_layout_changed(); @@ -122,6 +110,8 @@ private: float attenuation_filter_cutoff_hz = 5000.0; float attenuation_filter_db = -24.0; + float linear_attenuation = 0; + float max_distance = 0.0; Ref<VelocityTracker3D> velocity_tracker; @@ -162,6 +152,9 @@ public: void set_bus(const StringName &p_bus); StringName get_bus() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + void set_autoplay(bool p_enable); bool is_autoplay_enabled(); diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp index 36d9bfba82..e2f953974a 100644 --- a/scene/3d/collision_object_3d.cpp +++ b/scene/3d/collision_object_3d.cpp @@ -254,10 +254,8 @@ void CollisionObject3D::_apply_enabled() { } } -void CollisionObject3D::_input_event(Node *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_input_event, p_camera, p_input_event, p_pos, p_normal, p_shape); - } +void CollisionObject3D::_input_event_call(Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) { + GDVIRTUAL_CALL(_input_event, p_camera, p_input_event, p_pos, p_normal, p_shape); emit_signal(SceneStringNames::get_singleton()->input_event, p_camera, p_input_event, p_pos, p_normal, p_shape); } @@ -453,7 +451,7 @@ void CollisionObject3D::_bind_methods() { ClassDB::bind_method(D_METHOD("shape_owner_clear_shapes", "owner_id"), &CollisionObject3D::shape_owner_clear_shapes); ClassDB::bind_method(D_METHOD("shape_find_owner", "shape_index"), &CollisionObject3D::shape_find_owner); - BIND_VMETHOD(MethodInfo("_input_event", PropertyInfo(Variant::OBJECT, "camera"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::VECTOR3, "position"), PropertyInfo(Variant::VECTOR3, "normal"), PropertyInfo(Variant::INT, "shape_idx"))); + GDVIRTUAL_BIND(_input_event, "camera", "event", "position", "normal", "shape_idx"); ADD_SIGNAL(MethodInfo("input_event", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::VECTOR3, "position"), PropertyInfo(Variant::VECTOR3, "normal"), PropertyInfo(Variant::INT, "shape_idx"))); ADD_SIGNAL(MethodInfo("mouse_entered")); diff --git a/scene/3d/collision_object_3d.h b/scene/3d/collision_object_3d.h index c066960eb4..1c7e205888 100644 --- a/scene/3d/collision_object_3d.h +++ b/scene/3d/collision_object_3d.h @@ -31,6 +31,7 @@ #ifndef COLLISION_OBJECT_3D_H #define COLLISION_OBJECT_3D_H +#include "scene/3d/camera_3d.h" #include "scene/3d/node_3d.h" class CollisionObject3D : public Node3D { @@ -101,7 +102,7 @@ protected: void _on_transform_changed(); friend class Viewport; - virtual void _input_event(Node *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape); + virtual void _input_event_call(Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape); virtual void _mouse_enter(); virtual void _mouse_exit(); @@ -110,6 +111,7 @@ protected: void set_only_update_transform_changes(bool p_enable); bool is_only_update_transform_changes_enabled() const; + GDVIRTUAL5(_input_event, Camera3D *, Ref<InputEvent>, Vector3, Vector3, int) public: void set_collision_layer(uint32_t p_layer); uint32_t get_collision_layer() const; diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index e2095940ff..48ef41e015 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -212,7 +212,7 @@ TypedArray<String> CPUParticles3D::get_configuration_warnings() const { warnings.push_back(TTR("Nothing is visible because no mesh has been assigned.")); } - if (!anim_material_found && (get_param(PARAM_ANIM_SPEED) != 0.0 || get_param(PARAM_ANIM_OFFSET) != 0.0 || + if (!anim_material_found && (get_param_max(PARAM_ANIM_SPEED) != 0.0 || get_param_max(PARAM_ANIM_OFFSET) != 0.0 || get_param_curve(PARAM_ANIM_SPEED).is_valid() || get_param_curve(PARAM_ANIM_OFFSET).is_valid())) { warnings.push_back(TTR("CPUParticles3D animation requires the usage of a StandardMaterial3D whose Billboard Mode is set to \"Particle Billboard\".")); } @@ -263,28 +263,33 @@ real_t CPUParticles3D::get_flatness() const { return flatness; } -void CPUParticles3D::set_param(Parameter p_param, real_t p_value) { +void CPUParticles3D::set_param_min(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - parameters[p_param] = p_value; + parameters_min[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_max(p_param, p_value); + } } -real_t CPUParticles3D::get_param(Parameter p_param) const { +real_t CPUParticles3D::get_param_min(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return parameters[p_param]; + return parameters_min[p_param]; } -void CPUParticles3D::set_param_randomness(Parameter p_param, real_t p_value) { +void CPUParticles3D::set_param_max(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - - randomness[p_param] = p_value; + parameters_max[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_min(p_param, p_value); + } } -real_t CPUParticles3D::get_param_randomness(Parameter p_param) const { +real_t CPUParticles3D::get_param_max(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return randomness[p_param]; + return parameters_max[p_param]; } static void _adjust_curve_range(const Ref<Curve> &p_curve, real_t p_min, real_t p_max) { @@ -417,6 +422,23 @@ void CPUParticles3D::set_emission_ring_inner_radius(real_t p_radius) { emission_ring_inner_radius = p_radius; } +void CPUParticles3D::set_scale_curve_x(Ref<Curve> p_scale_curve) { + scale_curve_x = p_scale_curve; +} + +void CPUParticles3D::set_scale_curve_y(Ref<Curve> p_scale_curve) { + scale_curve_y = p_scale_curve; +} + +void CPUParticles3D::set_scale_curve_z(Ref<Curve> p_scale_curve) { + scale_curve_z = p_scale_curve; +} + +void CPUParticles3D::set_split_scale(bool p_split_scale) { + split_scale = p_split_scale; + notify_property_list_changed(); +} + real_t CPUParticles3D::get_emission_sphere_radius() const { return emission_sphere_radius; } @@ -465,6 +487,22 @@ Vector3 CPUParticles3D::get_gravity() const { return gravity; } +Ref<Curve> CPUParticles3D::get_scale_curve_x() const { + return scale_curve_x; +} + +Ref<Curve> CPUParticles3D::get_scale_curve_y() const { + return scale_curve_y; +} + +Ref<Curve> CPUParticles3D::get_scale_curve_z() const { + return scale_curve_z; +} + +bool CPUParticles3D::get_split_scale() { + return split_scale; +} + void CPUParticles3D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) { property.usage = PROPERTY_USAGE_NONE; @@ -489,6 +527,10 @@ void CPUParticles3D::_validate_property(PropertyInfo &property) const { if (property.name.begins_with("orbit_") && !particle_flags[PARTICLE_FLAG_DISABLE_Z]) { property.usage = PROPERTY_USAGE_NONE; } + + if (property.name.begins_with("scale_curve_") && !split_scale) { + property.usage = PROPERTY_USAGE_NONE; + } } static uint32_t idhash(uint32_t x) { @@ -707,7 +749,7 @@ void CPUParticles3D::_particles_process(double p_delta) { if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); Vector3 rot = Vector3(Math::cos(angle1_rad), Math::sin(angle1_rad), 0.0); - p.velocity = rot * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp((real_t)1.0, real_t(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); + p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], Math::randf()); } else { //initiate velocity spread in 3D real_t angle1_rad = Math::deg2rad((Math::randf() * (real_t)2.0 - (real_t)1.0) * spread); @@ -731,13 +773,13 @@ void CPUParticles3D::_particles_process(double p_delta) { binormal.normalize(); Vector3 normal = binormal.cross(direction_nrm); spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z; - p.velocity = spread_direction * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp((real_t)1.0, real_t(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); + p.velocity = spread_direction * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], float(Math::randf())); } - real_t base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp((real_t)1.0, p.angle_rand, randomness[PARAM_ANGLE]); + real_t base_angle = tex_angle * Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); p.custom[0] = Math::deg2rad(base_angle); //angle p.custom[1] = 0.0; //phase - p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp((real_t)1.0, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]); //animation offset (0-1) + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand); //animation offset (0-1) p.transform = Transform3D(); p.time = 0; p.lifetime = lifetime * (1.0 - Math::randf() * lifetime_randomness); @@ -894,26 +936,25 @@ void CPUParticles3D::_particles_process(double p_delta) { position.z = 0.0; } //apply linear acceleration - force += p.velocity.length() > 0.0 ? p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector3(); + force += p.velocity.length() > 0.0 ? p.velocity.normalized() * tex_linear_accel * Math::lerp(parameters_min[PARAM_LINEAR_ACCEL], parameters_max[PARAM_LINEAR_ACCEL], rand_from_seed(alt_seed)) : Vector3(); //apply radial acceleration Vector3 org = emission_xform.origin; Vector3 diff = position - org; - force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector3(); - //apply tangential acceleration; + force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(alt_seed)) : Vector3(); if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { Vector2 yx = Vector2(diff.y, diff.x); Vector2 yx2 = (yx * Vector2(-1.0, 1.0)).normalized(); - force += yx.length() > 0.0 ? Vector3(yx2.x, yx2.y, 0.0) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); + force += yx.length() > 0.0 ? Vector3(yx2.x, yx2.y, 0.0) * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector3(); } else { Vector3 crossDiff = diff.normalized().cross(gravity.normalized()); - force += crossDiff.length() > 0.0 ? crossDiff.normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); + force += crossDiff.length() > 0.0 ? crossDiff.normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector3(); } //apply attractor forces p.velocity += force * local_delta; //orbit velocity if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - real_t orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); + real_t orbit_amount = tex_orbit_velocity * Math::lerp(parameters_min[PARAM_ORBIT_VELOCITY], parameters_max[PARAM_ORBIT_VELOCITY], rand_from_seed(alt_seed)); if (orbit_amount != 0.0) { real_t ang = orbit_amount * local_delta * Math_TAU; // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, @@ -927,9 +968,10 @@ void CPUParticles3D::_particles_process(double p_delta) { if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { p.velocity = p.velocity.normalized() * tex_linear_velocity; } - if (parameters[PARAM_DAMPING] + tex_damping > 0.0) { + + if (parameters_max[PARAM_DAMPING] + tex_damping > 0.0) { real_t v = p.velocity.length(); - real_t damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]); + real_t damp = tex_damping * Math::lerp(parameters_min[PARAM_DAMPING], parameters_max[PARAM_DAMPING], rand_from_seed(alt_seed)); v -= damp * local_delta; if (v < 0.0) { p.velocity = Vector3(); @@ -937,17 +979,38 @@ void CPUParticles3D::_particles_process(double p_delta) { p.velocity = p.velocity.normalized() * v; } } - real_t base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp((real_t)1.0, p.angle_rand, randomness[PARAM_ANGLE]); - base_angle += p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]); + real_t base_angle = (tex_angle)*Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); + base_angle += p.custom[1] * lifetime * tex_angular_velocity * Math::lerp(parameters_min[PARAM_ANGULAR_VELOCITY], parameters_max[PARAM_ANGULAR_VELOCITY], rand_from_seed(alt_seed)); p.custom[0] = Math::deg2rad(base_angle); //angle - p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp((real_t)1.0, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + p.custom[1] * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]); //angle + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand) + p.custom[1] * tex_anim_speed * Math::lerp(parameters_min[PARAM_ANIM_SPEED], parameters_max[PARAM_ANIM_SPEED], rand_from_seed(alt_seed)); //angle } //apply color //apply hue rotation - real_t tex_scale = 1.0; - if (curve_parameters[PARAM_SCALE].is_valid()) { - tex_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + Vector3 tex_scale = Vector3(1.0, 1.0, 1.0); + if (split_scale) { + if (scale_curve_x.is_valid()) { + tex_scale.x = scale_curve_x->interpolate(tv); + } else { + tex_scale.x = 1.0; + } + if (scale_curve_y.is_valid()) { + tex_scale.y = scale_curve_y->interpolate(tv); + } else { + tex_scale.y = 1.0; + } + if (scale_curve_z.is_valid()) { + tex_scale.z = scale_curve_z->interpolate(tv); + } else { + tex_scale.z = 1.0; + } + } else { + if (curve_parameters[PARAM_SCALE].is_valid()) { + float tmp_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + tex_scale.x = tmp_scale; + tex_scale.y = tmp_scale; + tex_scale.z = tmp_scale; + } } real_t tex_hue_variation = 0.0; @@ -955,7 +1018,7 @@ void CPUParticles3D::_particles_process(double p_delta) { tex_hue_variation = curve_parameters[PARAM_HUE_VARIATION]->interpolate(tv); } - real_t hue_rot_angle = (parameters[PARAM_HUE_VARIATION] + tex_hue_variation) * Math_TAU * Math::lerp(1, p.hue_rot_rand * 2.0f - 1.0f, randomness[PARAM_HUE_VARIATION]); + real_t hue_rot_angle = (tex_hue_variation)*Math_TAU * Math::lerp(parameters_min[PARAM_HUE_VARIATION], parameters_max[PARAM_HUE_VARIATION], p.hue_rot_rand); real_t hue_rot_c = Math::cos(hue_rot_angle); real_t hue_rot_s = Math::sin(hue_rot_angle); @@ -1025,13 +1088,21 @@ void CPUParticles3D::_particles_process(double p_delta) { } } + p.transform.basis = p.transform.basis.orthonormalized(); //scale by scale - real_t base_scale = tex_scale * Math::lerp(parameters[PARAM_SCALE], (real_t)1.0, p.scale_rand * randomness[PARAM_SCALE]); - if (base_scale < CMP_EPSILON) { - base_scale = CMP_EPSILON; + + Vector3 base_scale = tex_scale * Math::lerp(parameters_min[PARAM_SCALE], parameters_max[PARAM_SCALE], p.scale_rand); + if (base_scale.x < CMP_EPSILON) { + base_scale.x = CMP_EPSILON; + } + if (base_scale.y < CMP_EPSILON) { + base_scale.y = CMP_EPSILON; + } + if (base_scale.z < CMP_EPSILON) { + base_scale.z = CMP_EPSILON; } - p.transform.basis.scale(Vector3(1, 1, 1) * base_scale); + p.transform.basis.scale(base_scale); if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { p.velocity.z = 0.0; @@ -1267,18 +1338,25 @@ void CPUParticles3D::convert_from_particles(Node *p_particles) { set_emission_shape(EmissionShape(material->get_emission_shape())); set_emission_sphere_radius(material->get_emission_sphere_radius()); set_emission_box_extents(material->get_emission_box_extents()); + Ref<CurveXYZTexture> scale3D = material->get_param_texture(ParticlesMaterial::PARAM_SCALE); + if (scale3D.is_valid()) { + split_scale = true; + scale_curve_x = scale3D->get_curve_x(); + scale_curve_y = scale3D->get_curve_y(); + scale_curve_z = scale3D->get_curve_z(); + } set_gravity(material->get_gravity()); set_lifetime_randomness(material->get_lifetime_randomness()); #define CONVERT_PARAM(m_param) \ - set_param(m_param, material->get_param(ParticlesMaterial::m_param)); \ + set_param_min(m_param, material->get_param_min(ParticlesMaterial::m_param)); \ { \ Ref<CurveTexture> ctex = material->get_param_texture(ParticlesMaterial::m_param); \ if (ctex.is_valid()) \ set_param_curve(m_param, ctex->get_curve()); \ } \ - set_param_randomness(m_param, material->get_param_randomness(ParticlesMaterial::m_param)); + set_param_max(m_param, material->get_param_max(ParticlesMaterial::m_param)); CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); @@ -1364,11 +1442,11 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flatness", "amount"), &CPUParticles3D::set_flatness); ClassDB::bind_method(D_METHOD("get_flatness"), &CPUParticles3D::get_flatness); - ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &CPUParticles3D::set_param); - ClassDB::bind_method(D_METHOD("get_param", "param"), &CPUParticles3D::get_param); + ClassDB::bind_method(D_METHOD("set_param_min", "param", "value"), &CPUParticles3D::set_param_min); + ClassDB::bind_method(D_METHOD("get_param_min", "param"), &CPUParticles3D::get_param_min); - ClassDB::bind_method(D_METHOD("set_param_randomness", "param", "randomness"), &CPUParticles3D::set_param_randomness); - ClassDB::bind_method(D_METHOD("get_param_randomness", "param"), &CPUParticles3D::get_param_randomness); + ClassDB::bind_method(D_METHOD("set_param_max", "param", "value"), &CPUParticles3D::set_param_max); + ClassDB::bind_method(D_METHOD("get_param_max", "param"), &CPUParticles3D::get_param_max); ClassDB::bind_method(D_METHOD("set_param_curve", "param", "curve"), &CPUParticles3D::set_param_curve); ClassDB::bind_method(D_METHOD("get_param_curve", "param"), &CPUParticles3D::get_param_curve); @@ -1415,6 +1493,18 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles3D::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles3D::set_gravity); + ClassDB::bind_method(D_METHOD("get_split_scale"), &CPUParticles3D::get_split_scale); + ClassDB::bind_method(D_METHOD("set_split_scale", "split_scale"), &CPUParticles3D::set_split_scale); + + ClassDB::bind_method(D_METHOD("get_scale_curve_x"), &CPUParticles3D::get_scale_curve_x); + ClassDB::bind_method(D_METHOD("set_scale_curve_x", "scale_curve"), &CPUParticles3D::set_scale_curve_x); + + ClassDB::bind_method(D_METHOD("get_scale_curve_y"), &CPUParticles3D::get_scale_curve_y); + ClassDB::bind_method(D_METHOD("set_scale_curve_y", "scale_curve"), &CPUParticles3D::set_scale_curve_y); + + ClassDB::bind_method(D_METHOD("get_scale_curve_z"), &CPUParticles3D::get_scale_curve_z); + ClassDB::bind_method(D_METHOD("set_scale_curve_z", "scale_curve"), &CPUParticles3D::set_scale_curve_z); + ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles3D::convert_from_particles); ADD_GROUP("Emission Shape", "emission_"); @@ -1439,54 +1529,58 @@ void CPUParticles3D::_bind_methods() { ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_INITIAL_LINEAR_VELOCITY); ADD_GROUP("Angular Velocity", "angular_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_min", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_max", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); ADD_GROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); ADD_GROUP("Linear Accel", "linear_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_LINEAR_ACCEL); ADD_GROUP("Radial Accel", "radial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_RADIAL_ACCEL); ADD_GROUP("Tangential Accel", "tangential_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param", "get_param", PARAM_DAMPING); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_min", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_min", "get_param_min", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_max", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_max", "get_param_max", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param", "get_param", PARAM_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_min", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_min", "get_param_min", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_max", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_max", "get_param_max", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGLE); ADD_GROUP("Scale", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_SCALE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_amount_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_SCALE); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "split_scale"), "set_split_scale", "get_split_scale"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_x", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_x", "get_scale_curve_x"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_y", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_y", "get_scale_curve_y"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_z", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_z", "get_scale_curve_z"); ADD_GROUP("Color", ""); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_color_ramp", "get_color_ramp"); ADD_GROUP("Hue Variation", "hue_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param", "get_param", PARAM_HUE_VARIATION); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_max", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_max", "get_param_max", PARAM_HUE_VARIATION); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_HUE_VARIATION); ADD_GROUP("Animation", "anim_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_param", "get_param", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_min", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_min", "get_param_min", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_max", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_max", "get_param_max", PARAM_ANIM_OFFSET); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_OFFSET); BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY); @@ -1527,18 +1621,30 @@ CPUParticles3D::CPUParticles3D() { set_emitting(true); set_amount(8); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); - set_param(PARAM_ANGULAR_VELOCITY, 0); - set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); - set_param(PARAM_RADIAL_ACCEL, 0); - set_param(PARAM_TANGENTIAL_ACCEL, 0); - set_param(PARAM_DAMPING, 0); - set_param(PARAM_ANGLE, 0); - set_param(PARAM_SCALE, 1); - set_param(PARAM_HUE_VARIATION, 0); - set_param(PARAM_ANIM_SPEED, 0); - set_param(PARAM_ANIM_OFFSET, 0); + set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_min(PARAM_ANGULAR_VELOCITY, 0); + set_param_min(PARAM_ORBIT_VELOCITY, 0); + set_param_min(PARAM_LINEAR_ACCEL, 0); + set_param_min(PARAM_RADIAL_ACCEL, 0); + set_param_min(PARAM_TANGENTIAL_ACCEL, 0); + set_param_min(PARAM_DAMPING, 0); + set_param_min(PARAM_ANGLE, 0); + set_param_min(PARAM_SCALE, 1); + set_param_min(PARAM_HUE_VARIATION, 0); + set_param_min(PARAM_ANIM_SPEED, 0); + set_param_min(PARAM_ANIM_OFFSET, 0); + set_param_max(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_max(PARAM_ANGULAR_VELOCITY, 0); + set_param_max(PARAM_ORBIT_VELOCITY, 0); + set_param_max(PARAM_LINEAR_ACCEL, 0); + set_param_max(PARAM_RADIAL_ACCEL, 0); + set_param_max(PARAM_TANGENTIAL_ACCEL, 0); + set_param_max(PARAM_DAMPING, 0); + set_param_max(PARAM_ANGLE, 0); + set_param_max(PARAM_SCALE, 1); + set_param_max(PARAM_HUE_VARIATION, 0); + set_param_max(PARAM_ANIM_SPEED, 0); + set_param_max(PARAM_ANIM_OFFSET, 0); set_emission_shape(EMISSION_SHAPE_POINT); set_emission_sphere_radius(1); set_emission_box_extents(Vector3(1, 1, 1)); @@ -1549,10 +1655,6 @@ CPUParticles3D::CPUParticles3D() { set_gravity(Vector3(0, -9.8, 0)); - for (int i = 0; i < PARAM_MAX; i++) { - set_param_randomness(Parameter(i), 0); - } - for (int i = 0; i < PARTICLE_FLAG_MAX; i++) { particle_flags[i] = false; } diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index 5b60322f05..160814ead4 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -154,8 +154,8 @@ private: real_t spread = 45.0; real_t flatness = 0.0; - real_t parameters[PARAM_MAX]; - real_t randomness[PARAM_MAX] = {}; + real_t parameters_min[PARAM_MAX]; + real_t parameters_max[PARAM_MAX] = {}; Ref<Curve> curve_parameters[PARAM_MAX]; Color color = Color(1, 1, 1, 1); @@ -175,6 +175,11 @@ private: real_t emission_ring_radius; real_t emission_ring_inner_radius; + Ref<Curve> scale_curve_x; + Ref<Curve> scale_curve_y; + Ref<Curve> scale_curve_z; + bool split_scale = false; + Vector3 gravity = Vector3(0, -9.8, 0); void _update_internal(); @@ -246,11 +251,11 @@ public: void set_flatness(real_t p_flatness); real_t get_flatness() const; - void set_param(Parameter p_param, real_t p_value); - real_t get_param(Parameter p_param) const; + void set_param_min(Parameter p_param, real_t p_value); + real_t get_param_min(Parameter p_param) const; - void set_param_randomness(Parameter p_param, real_t p_value); - real_t get_param_randomness(Parameter p_param) const; + void set_param_max(Parameter p_param, real_t p_value); + real_t get_param_max(Parameter p_param) const; void set_param_curve(Parameter p_param, const Ref<Curve> &p_curve); Ref<Curve> get_param_curve(Parameter p_param) const; @@ -275,6 +280,10 @@ public: void set_emission_ring_height(real_t p_height); void set_emission_ring_radius(real_t p_radius); void set_emission_ring_inner_radius(real_t p_radius); + void set_scale_curve_x(Ref<Curve> p_scale_curve); + void set_scale_curve_y(Ref<Curve> p_scale_curve); + void set_scale_curve_z(Ref<Curve> p_scale_curve); + void set_split_scale(bool p_split_scale); EmissionShape get_emission_shape() const; real_t get_emission_sphere_radius() const; @@ -287,6 +296,10 @@ public: real_t get_emission_ring_height() const; real_t get_emission_ring_radius() const; real_t get_emission_ring_inner_radius() const; + Ref<Curve> get_scale_curve_x() const; + Ref<Curve> get_scale_curve_y() const; + Ref<Curve> get_scale_curve_z() const; + bool get_split_scale(); void set_gravity(const Vector3 &p_gravity); Vector3 get_gravity() const; diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index d56228df66..baf28ae102 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -312,7 +312,7 @@ TypedArray<String> GPUParticles3D::get_configuration_warnings() const { } else { const ParticlesMaterial *process = Object::cast_to<ParticlesMaterial>(process_material.ptr()); if (!anim_material_found && process && - (process->get_param(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || + (process->get_param_max(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param_max(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_SPEED).is_valid() || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_OFFSET).is_valid())) { warnings.push_back(TTR("Particles animation requires the usage of a BaseMaterial3D whose Billboard Mode is set to \"Particle Billboard\".")); } diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 4c36618c99..00c6664e65 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -111,9 +111,9 @@ Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_t return Ref<KinematicCollision3D>(); } -bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, const Set<RID> &p_exclude) { +bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, bool p_collide_separation_ray, const Set<RID> &p_exclude) { Transform3D gt = get_global_transform(); - bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_exclude); + bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_collide_separation_ray, p_exclude); // Restore direction of motion to be along original motion, // in order to avoid sliding due to recovery, @@ -223,121 +223,113 @@ Ref<PhysicsMaterial> StaticBody3D::get_physics_material_override() const { return physics_material_override; } -void StaticBody3D::set_kinematic_motion_enabled(bool p_enabled) { - if (p_enabled == kinematic_motion) { - return; - } +void StaticBody3D::set_constant_linear_velocity(const Vector3 &p_vel) { + constant_linear_velocity = p_vel; - kinematic_motion = p_enabled; + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); +} - if (kinematic_motion) { - set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC); - } else { - set_body_mode(PhysicsServer3D::BODY_MODE_STATIC); - } +void StaticBody3D::set_constant_angular_velocity(const Vector3 &p_vel) { + constant_angular_velocity = p_vel; -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - update_configuration_warnings(); - return; - } -#endif + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); +} - _update_kinematic_motion(); +Vector3 StaticBody3D::get_constant_linear_velocity() const { + return constant_linear_velocity; } -bool StaticBody3D::is_kinematic_motion_enabled() const { - return kinematic_motion; +Vector3 StaticBody3D::get_constant_angular_velocity() const { + return constant_angular_velocity; } -void StaticBody3D::set_constant_linear_velocity(const Vector3 &p_vel) { - constant_linear_velocity = p_vel; +void StaticBody3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody3D::set_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody3D::set_constant_angular_velocity); + ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody3D::get_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody3D::get_constant_angular_velocity); + + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody3D::set_physics_material_override); + ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody3D::get_physics_material_override); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); +} + +StaticBody3D::StaticBody3D(PhysicsServer3D::BodyMode p_mode) : + PhysicsBody3D(p_mode) { +} - if (kinematic_motion) { - _update_kinematic_motion(); +void StaticBody3D::_reload_physics_characteristics() { + if (physics_material_override.is_null()) { + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_BOUNCE, 0); + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_FRICTION, 1); } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_BOUNCE, physics_material_override->computed_bounce()); + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_FRICTION, physics_material_override->computed_friction()); } } -void StaticBody3D::set_sync_to_physics(bool p_enable) { +Vector3 AnimatableBody3D::get_linear_velocity() const { + return linear_velocity; +} + +Vector3 AnimatableBody3D::get_angular_velocity() const { + return angular_velocity; +} + +void AnimatableBody3D::set_sync_to_physics(bool p_enable) { if (sync_to_physics == p_enable) { return; } sync_to_physics = p_enable; + _update_kinematic_motion(); +} + +bool AnimatableBody3D::is_sync_to_physics_enabled() const { + return sync_to_physics; +} + +void AnimatableBody3D::_update_kinematic_motion() { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - update_configuration_warnings(); return; } #endif - if (kinematic_motion) { - _update_kinematic_motion(); + if (sync_to_physics) { + set_only_update_transform_changes(true); + set_notify_local_transform(true); + } else { + set_only_update_transform_changes(false); + set_notify_local_transform(false); } } -bool StaticBody3D::is_sync_to_physics_enabled() const { - return sync_to_physics; +void AnimatableBody3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) { + AnimatableBody3D *body = (AnimatableBody3D *)p_instance; + body->_body_state_changed(p_state); } -void StaticBody3D::_direct_state_changed(Object *p_state) { - PhysicsDirectBodyState3D *state = Object::cast_to<PhysicsDirectBodyState3D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument"); - - linear_velocity = state->get_linear_velocity(); - angular_velocity = state->get_angular_velocity(); +void AnimatableBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { + linear_velocity = p_state->get_linear_velocity(); + angular_velocity = p_state->get_angular_velocity(); if (!sync_to_physics) { return; } - last_valid_transform = state->get_transform(); + last_valid_transform = p_state->get_transform(); set_notify_local_transform(false); set_global_transform(last_valid_transform); set_notify_local_transform(true); _on_transform_changed(); } -TypedArray<String> StaticBody3D::get_configuration_warnings() const { - TypedArray<String> warnings = PhysicsBody3D::get_configuration_warnings(); - - if (sync_to_physics && !kinematic_motion) { - warnings.push_back(TTR("Sync to physics works only when kinematic motion is enabled.")); - } - - return warnings; -} - -void StaticBody3D::set_constant_angular_velocity(const Vector3 &p_vel) { - constant_angular_velocity = p_vel; - - if (kinematic_motion) { - _update_kinematic_motion(); - } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); - } -} - -Vector3 StaticBody3D::get_constant_linear_velocity() const { - return constant_linear_velocity; -} - -Vector3 StaticBody3D::get_constant_angular_velocity() const { - return constant_angular_velocity; -} - -Vector3 StaticBody3D::get_linear_velocity() const { - return linear_velocity; -} - -Vector3 StaticBody3D::get_angular_velocity() const { - return angular_velocity; -} - -void StaticBody3D::_notification(int p_what) { +void AnimatableBody3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { last_valid_transform = get_global_transform(); @@ -347,17 +339,6 @@ void StaticBody3D::_notification(int p_what) { // Used by sync to physics, send the new transform to the physics... Transform3D new_transform = get_global_transform(); - real_t delta_time = get_physics_process_delta_time(); - new_transform.origin += constant_linear_velocity * delta_time; - - real_t ang_vel = constant_angular_velocity.length(); - if (!Math::is_zero_approx(ang_vel)) { - Vector3 ang_vel_axis = constant_angular_velocity / ang_vel; - Basis rot(ang_vel_axis, ang_vel * delta_time); - new_transform.basis = rot * new_transform.basis; - new_transform.orthonormalize(); - } - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform); // ... but then revert changes. @@ -366,108 +347,21 @@ void StaticBody3D::_notification(int p_what) { set_notify_local_transform(true); _on_transform_changed(); } break; - - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif - - ERR_FAIL_COND(!kinematic_motion); - - Transform3D new_transform = get_global_transform(); - - real_t delta_time = get_physics_process_delta_time(); - new_transform.origin += constant_linear_velocity * delta_time; - - real_t ang_vel = constant_angular_velocity.length(); - if (!Math::is_zero_approx(ang_vel)) { - Vector3 ang_vel_axis = constant_angular_velocity / ang_vel; - Basis rot(ang_vel_axis, ang_vel * delta_time); - new_transform.basis = rot * new_transform.basis; - new_transform.orthonormalize(); - } - - if (sync_to_physics) { - // Propagate transform change to node. - set_global_transform(new_transform); - } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform); - - // Propagate transform change to node. - set_ignore_transform_notification(true); - set_global_transform(new_transform); - set_ignore_transform_notification(false); - _on_transform_changed(); - } - } break; } } -void StaticBody3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody3D::set_constant_linear_velocity); - ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody3D::set_constant_angular_velocity); - ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody3D::get_constant_linear_velocity); - ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody3D::get_constant_angular_velocity); - - ClassDB::bind_method(D_METHOD("set_kinematic_motion_enabled", "enabled"), &StaticBody3D::set_kinematic_motion_enabled); - ClassDB::bind_method(D_METHOD("is_kinematic_motion_enabled"), &StaticBody3D::is_kinematic_motion_enabled); - - ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody3D::set_physics_material_override); - ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody3D::get_physics_material_override); - - ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &StaticBody3D::set_sync_to_physics); - ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &StaticBody3D::is_sync_to_physics_enabled); +void AnimatableBody3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &AnimatableBody3D::set_sync_to_physics); + ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &AnimatableBody3D::is_sync_to_physics_enabled); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "kinematic_motion"), "set_kinematic_motion_enabled", "is_kinematic_motion_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled"); } -StaticBody3D::StaticBody3D() : - PhysicsBody3D(PhysicsServer3D::BODY_MODE_STATIC) { -} - -void StaticBody3D::_reload_physics_characteristics() { - if (physics_material_override.is_null()) { - PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_BOUNCE, 0); - PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_FRICTION, 1); - } else { - PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_BOUNCE, physics_material_override->computed_bounce()); - PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_FRICTION, physics_material_override->computed_friction()); - } -} - -void StaticBody3D::_update_kinematic_motion() { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif - - if (kinematic_motion && sync_to_physics) { - set_only_update_transform_changes(true); - set_notify_local_transform(true); - } else { - set_only_update_transform_changes(false); - set_notify_local_transform(false); - } - - bool needs_physics_process = false; - if (kinematic_motion) { - PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &StaticBody3D::_direct_state_changed)); - - if (!constant_angular_velocity.is_equal_approx(Vector3()) || !constant_linear_velocity.is_equal_approx(Vector3())) { - needs_physics_process = true; - } - } else { - PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), Callable()); - } +AnimatableBody3D::AnimatableBody3D() : + StaticBody3D(PhysicsServer3D::BODY_MODE_KINEMATIC) { + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); - set_physics_process_internal(needs_physics_process); + _update_kinematic_motion(); } void RigidBody3D::_body_enter_tree(ObjectID p_id) { @@ -582,25 +476,26 @@ struct _RigidBodyInOut { int local_shape = 0; }; -void RigidBody3D::_direct_state_changed(Object *p_state) { -#ifdef DEBUG_ENABLED - state = Object::cast_to<PhysicsDirectBodyState3D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument"); -#else - state = (PhysicsDirectBodyState3D *)p_state; //trust it -#endif +void RigidBody3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) { + RigidBody3D *body = (RigidBody3D *)p_instance; + body->_body_state_changed(p_state); +} +void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { set_ignore_transform_notification(true); - set_global_transform(state->get_transform()); - linear_velocity = state->get_linear_velocity(); - angular_velocity = state->get_angular_velocity(); - inverse_inertia_tensor = state->get_inverse_inertia_tensor(); - if (sleeping != state->is_sleeping()) { - sleeping = state->is_sleeping(); + set_global_transform(p_state->get_transform()); + + linear_velocity = p_state->get_linear_velocity(); + angular_velocity = p_state->get_angular_velocity(); + + inverse_inertia_tensor = p_state->get_inverse_inertia_tensor(); + + if (sleeping != p_state->is_sleeping()) { + sleeping = p_state->is_sleeping(); emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed); } - GDVIRTUAL_CALL(_integrate_forces, state); + GDVIRTUAL_CALL(_integrate_forces, p_state); set_ignore_transform_notification(false); _on_transform_changed(); @@ -617,18 +512,18 @@ void RigidBody3D::_direct_state_changed(Object *p_state) { } } - _RigidBodyInOut *toadd = (_RigidBodyInOut *)alloca(state->get_contact_count() * sizeof(_RigidBodyInOut)); + _RigidBodyInOut *toadd = (_RigidBodyInOut *)alloca(p_state->get_contact_count() * sizeof(_RigidBodyInOut)); int toadd_count = 0; //state->get_contact_count(); RigidBody3D_RemoveAction *toremove = (RigidBody3D_RemoveAction *)alloca(rc * sizeof(RigidBody3D_RemoveAction)); int toremove_count = 0; //put the ones to add - for (int i = 0; i < state->get_contact_count(); i++) { - RID rid = state->get_contact_collider(i); - ObjectID obj = state->get_contact_collider_id(i); - int local_shape = state->get_contact_local_shape(i); - int shape = state->get_contact_collider_shape(i); + for (int i = 0; i < p_state->get_contact_count(); i++) { + RID rid = p_state->get_contact_collider(i); + ObjectID obj = p_state->get_contact_collider_id(i); + int local_shape = p_state->get_contact_local_shape(i); + int shape = p_state->get_contact_collider_shape(i); //bool found=false; @@ -683,8 +578,6 @@ void RigidBody3D::_direct_state_changed(Object *p_state) { contact_monitor->locked = false; } - - state = nullptr; } void RigidBody3D::_notification(int p_what) { @@ -738,6 +631,60 @@ real_t RigidBody3D::get_mass() const { return mass; } +void RigidBody3D::set_inertia(const Vector3 &p_inertia) { + ERR_FAIL_COND(p_inertia.x < 0); + ERR_FAIL_COND(p_inertia.y < 0); + ERR_FAIL_COND(p_inertia.z < 0); + + inertia = p_inertia; + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_INERTIA, inertia); +} + +const Vector3 &RigidBody3D::get_inertia() const { + return inertia; +} + +void RigidBody3D::set_center_of_mass_mode(CenterOfMassMode p_mode) { + if (center_of_mass_mode == p_mode) { + return; + } + + center_of_mass_mode = p_mode; + + switch (center_of_mass_mode) { + case CENTER_OF_MASS_MODE_AUTO: { + center_of_mass = Vector3(); + PhysicsServer3D::get_singleton()->body_reset_mass_properties(get_rid()); + if (inertia != Vector3()) { + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_INERTIA, inertia); + } + } break; + + case CENTER_OF_MASS_MODE_CUSTOM: { + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); + } break; + } +} + +RigidBody3D::CenterOfMassMode RigidBody3D::get_center_of_mass_mode() const { + return center_of_mass_mode; +} + +void RigidBody3D::set_center_of_mass(const Vector3 &p_center_of_mass) { + if (center_of_mass == p_center_of_mass) { + return; + } + + ERR_FAIL_COND(center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM); + center_of_mass = p_center_of_mass; + + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); +} + +const Vector3 &RigidBody3D::get_center_of_mass() const { + return center_of_mass; +} + void RigidBody3D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) { if (physics_material_override.is_valid()) { if (physics_material_override->is_connected(CoreStringNames::get_singleton()->changed, callable_mp(this, &RigidBody3D::_reload_physics_characteristics))) { @@ -787,25 +734,15 @@ real_t RigidBody3D::get_angular_damp() const { } void RigidBody3D::set_axis_velocity(const Vector3 &p_axis) { - Vector3 v = state ? state->get_linear_velocity() : linear_velocity; Vector3 axis = p_axis.normalized(); - v -= axis * axis.dot(v); - v += p_axis; - if (state) { - set_linear_velocity(v); - } else { - PhysicsServer3D::get_singleton()->body_set_axis_velocity(get_rid(), p_axis); - linear_velocity = v; - } + linear_velocity -= axis * axis.dot(linear_velocity); + linear_velocity += p_axis; + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); } void RigidBody3D::set_linear_velocity(const Vector3 &p_velocity) { linear_velocity = p_velocity; - if (state) { - state->set_linear_velocity(linear_velocity); - } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); - } + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); } Vector3 RigidBody3D::get_linear_velocity() const { @@ -814,11 +751,7 @@ Vector3 RigidBody3D::get_linear_velocity() const { void RigidBody3D::set_angular_velocity(const Vector3 &p_velocity) { angular_velocity = p_velocity; - if (state) { - state->set_angular_velocity(angular_velocity); - } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity); - } + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity); } Vector3 RigidBody3D::get_angular_velocity() const { @@ -972,6 +905,15 @@ void RigidBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mass", "mass"), &RigidBody3D::set_mass); ClassDB::bind_method(D_METHOD("get_mass"), &RigidBody3D::get_mass); + ClassDB::bind_method(D_METHOD("set_inertia", "inertia"), &RigidBody3D::set_inertia); + ClassDB::bind_method(D_METHOD("get_inertia"), &RigidBody3D::get_inertia); + + ClassDB::bind_method(D_METHOD("set_center_of_mass_mode", "mode"), &RigidBody3D::set_center_of_mass_mode); + ClassDB::bind_method(D_METHOD("get_center_of_mass_mode"), &RigidBody3D::get_center_of_mass_mode); + + ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &RigidBody3D::set_center_of_mass); + ClassDB::bind_method(D_METHOD("get_center_of_mass"), &RigidBody3D::get_center_of_mass); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &RigidBody3D::set_physics_material_override); ClassDB::bind_method(D_METHOD("get_physics_material_override"), &RigidBody3D::get_physics_material_override); @@ -1025,7 +967,11 @@ void RigidBody3D::_bind_methods() { GDVIRTUAL_BIND(_integrate_forces, "state"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Dynamic,Static,DynamicLocked,Kinematic"), "set_mode", "get_mode"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "inertia", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater,exp"), "set_inertia", "get_inertia"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_center_of_mass_mode", "get_center_of_mass_mode"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_of_mass", PROPERTY_HINT_RANGE, "-10,10,0.01,or_lesser,or_greater"), "set_center_of_mass", "get_center_of_mass"); + ADD_LINKED_PROPERTY("center_of_mass_mode", "center_of_mass"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-128,128,0.01"), "set_gravity_scale", "get_gravity_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_integrator"), "set_use_custom_integrator", "is_using_custom_integrator"); @@ -1051,11 +997,22 @@ void RigidBody3D::_bind_methods() { BIND_ENUM_CONSTANT(MODE_STATIC); BIND_ENUM_CONSTANT(MODE_DYNAMIC_LOCKED); BIND_ENUM_CONSTANT(MODE_KINEMATIC); + + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_AUTO); + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_CUSTOM); +} + +void RigidBody3D::_validate_property(PropertyInfo &property) const { + if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM) { + if (property.name == "center_of_mass") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } } RigidBody3D::RigidBody3D() : PhysicsBody3D(PhysicsServer3D::BODY_MODE_DYNAMIC) { - PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &RigidBody3D::_direct_state_changed)); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); } RigidBody3D::~RigidBody3D() { @@ -1080,12 +1037,10 @@ void RigidBody3D::_reload_physics_characteristics() { #define FLOOR_ANGLE_THRESHOLD 0.01 bool CharacterBody3D::move_and_slide() { - Vector3 body_velocity_normal = linear_velocity.normalized(); - bool was_on_floor = on_floor; // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky - float delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); for (int i = 0; i < 3; i++) { if (locked_axis & (1 << i)) { @@ -1111,11 +1066,11 @@ bool CharacterBody3D::move_and_slide() { floor_normal = Vector3(); floor_velocity = Vector3(); - if (current_floor_velocity != Vector3() && on_floor_body.is_valid()) { + if (!current_floor_velocity.is_equal_approx(Vector3()) && on_floor_body.is_valid()) { PhysicsServer3D::MotionResult floor_result; Set<RID> exclude; exclude.insert(on_floor_body); - if (move_and_collide(current_floor_velocity * delta, floor_result, margin, false, false, exclude)) { + if (move_and_collide(current_floor_velocity * delta, floor_result, margin, false, false, false, exclude)) { motion_results.push_back(floor_result); _set_collision_direction(floor_result); } @@ -1130,39 +1085,35 @@ bool CharacterBody3D::move_and_slide() { for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionResult result; - bool found_collision = false; - bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled); - if (!collided) { - motion = Vector3(); //clear because no collision happened and motion completed - } else { - found_collision = true; - + if (collided) { motion_results.push_back(result); _set_collision_direction(result); - if (on_floor && floor_stop_on_slope) { - if ((body_velocity_normal + up_direction).length() < 0.01) { - Transform3D gt = get_global_transform(); - if (result.travel.length() > margin) { - gt.origin -= result.travel.slide(up_direction); - } else { - gt.origin -= result.travel; - } - set_global_transform(gt); - linear_velocity = Vector3(); - return true; + if (on_floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { + Transform3D gt = get_global_transform(); + if (result.travel.length() > margin) { + gt.origin -= result.travel.slide(up_direction); + } else { + gt.origin -= result.travel; } + set_global_transform(gt); + linear_velocity = Vector3(); + motion = Vector3(); + break; } - if (sliding_enabled || !on_floor) { - motion = result.remainder.slide(result.collision_normal); - linear_velocity = linear_velocity.slide(result.collision_normal); + if (result.remainder.is_equal_approx(Vector3())) { + motion = Vector3(); + break; + } - for (int j = 0; j < 3; j++) { - if (locked_axis & (1 << j)) { - linear_velocity[j] = 0.0; - } + if (sliding_enabled || !on_floor) { + Vector3 slide_motion = result.remainder.slide(result.collision_normal); + if (slide_motion.dot(linear_velocity) > 0.0) { + motion = slide_motion; + } else { + motion = Vector3(); } } else { motion = result.remainder; @@ -1171,16 +1122,16 @@ bool CharacterBody3D::move_and_slide() { sliding_enabled = true; - if (!found_collision || motion == Vector3()) { + if (!collided || motion.is_equal_approx(Vector3())) { break; } } - if (was_on_floor && snap != Vector3()) { + if (was_on_floor && !on_floor && !snap.is_equal_approx(Vector3())) { // Apply snap. Transform3D gt = get_global_transform(); PhysicsServer3D::MotionResult result; - if (move_and_collide(snap, result, margin, true, false)) { + if (move_and_collide(snap, result, margin, true, false, true)) { bool apply = true; if (up_direction != Vector3()) { if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { @@ -1213,6 +1164,11 @@ bool CharacterBody3D::move_and_slide() { linear_velocity += current_floor_velocity; } + // Reset the gravity accumulation when touching the ground. + if (on_floor && linear_velocity.dot(up_direction) <= 0) { + linear_velocity = linear_velocity.slide(up_direction); + } + return motion_results.size() > 0; } @@ -1230,8 +1186,11 @@ void CharacterBody3D::_set_collision_direction(const PhysicsServer3D::MotionResu on_ceiling = true; } else { on_wall = true; - on_floor_body = p_result.collider; - floor_velocity = p_result.collider_velocity; + // Don't apply wall velocity when the collider is a CharacterBody3D. + if (Object::cast_to<CharacterBody3D>(ObjectDB::get_instance(p_result.collider_id)) == nullptr) { + on_floor_body = p_result.collider; + floor_velocity = p_result.collider_velocity; + } } } } @@ -2250,23 +2209,19 @@ void PhysicalBone3D::_notification(int p_what) { } } -void PhysicalBone3D::_direct_state_changed(Object *p_state) { +void PhysicalBone3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) { + PhysicalBone3D *bone = (PhysicalBone3D *)p_instance; + bone->_body_state_changed(p_state); +} + +void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { if (!simulate_physics || !_internal_simulate_physics) { return; } - /// Update bone transform - - PhysicsDirectBodyState3D *state; - -#ifdef DEBUG_ENABLED - state = Object::cast_to<PhysicsDirectBodyState3D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument"); -#else - state = (PhysicsDirectBodyState3D *)p_state; //trust it -#endif + /// Update bone transform. - Transform3D global_transform(state->get_transform()); + Transform3D global_transform(p_state->get_transform()); set_ignore_transform_notification(true); set_global_transform(global_transform); @@ -2330,7 +2285,7 @@ void PhysicalBone3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "body_offset"), "set_body_offset", "get_body_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp"), "set_mass", "get_mass"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_friction", "get_friction"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_bounce", "get_bounce"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-10,10,0.01"), "set_gravity_scale", "get_gravity_scale"); @@ -2728,7 +2683,7 @@ void PhysicalBone3D::_start_physics_simulation() { set_body_mode(PhysicsServer3D::BODY_MODE_DYNAMIC); PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer()); PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask()); - PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &PhysicalBone3D::_direct_state_changed)); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); set_as_top_level(true); _internal_simulate_physics = true; } @@ -2747,7 +2702,7 @@ void PhysicalBone3D::_stop_physics_simulation() { PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), 0); } if (_internal_simulate_physics) { - PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), Callable()); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr); parent_skeleton->set_bone_global_pose_override(bone_id, Transform3D(), 0.0, false); set_as_top_level(false); _internal_simulate_physics = false; diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index 26b9a39047..8e6463f838 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -53,7 +53,7 @@ protected: Ref<KinematicCollision3D> _move(const Vector3 &p_motion, bool p_test_only = false, real_t p_margin = 0.001); public: - bool move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, const Set<RID> &p_exclude = Set<RID>()); + bool move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()); bool test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001); void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock); @@ -73,23 +73,13 @@ public: class StaticBody3D : public PhysicsBody3D { GDCLASS(StaticBody3D, PhysicsBody3D); +private: Vector3 constant_linear_velocity; Vector3 constant_angular_velocity; - Vector3 linear_velocity; - Vector3 angular_velocity; - Ref<PhysicsMaterial> physics_material_override; - bool kinematic_motion = false; - bool sync_to_physics = false; - - Transform3D last_valid_transform; - - void _direct_state_changed(Object *p_state); - protected: - void _notification(int p_what); static void _bind_methods(); public: @@ -102,20 +92,38 @@ public: Vector3 get_constant_linear_velocity() const; Vector3 get_constant_angular_velocity() const; - virtual Vector3 get_linear_velocity() const override; - virtual Vector3 get_angular_velocity() const override; + StaticBody3D(PhysicsServer3D::BodyMode p_mode = PhysicsServer3D::BODY_MODE_STATIC); - virtual TypedArray<String> get_configuration_warnings() const override; +private: + void _reload_physics_characteristics(); +}; - StaticBody3D(); +class AnimatableBody3D : public StaticBody3D { + GDCLASS(AnimatableBody3D, StaticBody3D); private: - void _reload_physics_characteristics(); + Vector3 linear_velocity; + Vector3 angular_velocity; - void _update_kinematic_motion(); + bool sync_to_physics = false; - void set_kinematic_motion_enabled(bool p_enabled); - bool is_kinematic_motion_enabled() const; + Transform3D last_valid_transform; + + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state); + void _body_state_changed(PhysicsDirectBodyState3D *p_state); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + virtual Vector3 get_linear_velocity() const override; + virtual Vector3 get_angular_velocity() const override; + + AnimatableBody3D(); + +private: + void _update_kinematic_motion(); void set_sync_to_physics(bool p_enable); bool is_sync_to_physics_enabled() const; @@ -132,14 +140,22 @@ public: MODE_KINEMATIC, }; + enum CenterOfMassMode { + CENTER_OF_MASS_MODE_AUTO, + CENTER_OF_MASS_MODE_CUSTOM, + }; + GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState3D *) protected: bool can_sleep = true; - PhysicsDirectBodyState3D *state = nullptr; Mode mode = MODE_DYNAMIC; real_t mass = 1.0; + Vector3 inertia; + CenterOfMassMode center_of_mass_mode = CENTER_OF_MASS_MODE_AUTO; + Vector3 center_of_mass; + Ref<PhysicsMaterial> physics_material_override; Vector3 linear_velocity; @@ -197,11 +213,14 @@ protected: void _body_exit_tree(ObjectID p_id); void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape); - virtual void _direct_state_changed(Object *p_state); + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state); + virtual void _body_state_changed(PhysicsDirectBodyState3D *p_state); void _notification(int p_what); static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; + public: void set_mode(Mode p_mode); Mode get_mode() const; @@ -211,6 +230,15 @@ public: virtual real_t get_inverse_mass() const override { return 1.0 / mass; } + void set_inertia(const Vector3 &p_inertia); + const Vector3 &get_inertia() const; + + void set_center_of_mass_mode(CenterOfMassMode p_mode); + CenterOfMassMode get_center_of_mass_mode() const; + + void set_center_of_mass(const Vector3 &p_center_of_mass); + const Vector3 &get_center_of_mass() const; + void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); Ref<PhysicsMaterial> get_physics_material_override() const; @@ -271,6 +299,7 @@ private: }; VARIANT_ENUM_CAST(RigidBody3D::Mode); +VARIANT_ENUM_CAST(RigidBody3D::CenterOfMassMode); class KinematicCollision3D; @@ -525,7 +554,8 @@ protected: bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; void _notification(int p_what); - void _direct_state_changed(Object *p_state); + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state); + void _body_state_changed(PhysicsDirectBodyState3D *p_state); static void _bind_methods(); diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 94fb49ae81..0f5de621ea 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -896,19 +896,25 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) { int len = bones.size(); // calculate global rests and invert them - Vector<int> bones_to_process = get_parentless_bones(); + LocalVector<int> bones_to_process; + bones_to_process = get_parentless_bones(); while (bones_to_process.size() > 0) { int current_bone_idx = bones_to_process[0]; - bones_to_process.erase(current_bone_idx); const Bone &b = bonesptr[current_bone_idx]; - - // Note: the code below may not work by default. May need to track an integer for the bone pose index order - // in the while loop, instead of using current_bone_idx. - if (b.parent >= 0) { - skin->set_bind_pose(current_bone_idx, skin->get_bind_pose(b.parent) * b.rest); - } else { + bones_to_process.erase(current_bone_idx); + LocalVector<int> child_bones_vector; + child_bones_vector = get_bone_children(current_bone_idx); + int child_bones_size = child_bones_vector.size(); + if (b.parent < 0) { skin->set_bind_pose(current_bone_idx, b.rest); } + for (int i = 0; i < child_bones_size; i++) { + int child_bone_idx = child_bones_vector[i]; + const Bone &cb = bonesptr[child_bone_idx]; + skin->set_bind_pose(child_bone_idx, skin->get_bind_pose(current_bone_idx) * cb.rest); + // Add the bone's children to the list of bones to be processed. + bones_to_process.push_back(child_bones_vector[i]); + } } for (int i = 0; i < len; i++) { diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index 28ff3e9412..7eb189e890 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -355,6 +355,11 @@ void SoftBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_drag_coefficient", "drag_coefficient"), &SoftBody3D::set_drag_coefficient); ClassDB::bind_method(D_METHOD("get_drag_coefficient"), &SoftBody3D::get_drag_coefficient); + ClassDB::bind_method(D_METHOD("get_point_transform", "point_index"), &SoftBody3D::get_point_transform); + + ClassDB::bind_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path"), &SoftBody3D::pin_point, DEFVAL(NodePath())); + ClassDB::bind_method(D_METHOD("is_point_pinned", "point_index"), &SoftBody3D::is_point_pinned); + ClassDB::bind_method(D_METHOD("set_ray_pickable", "ray_pickable"), &SoftBody3D::set_ray_pickable); ClassDB::bind_method(D_METHOD("is_ray_pickable"), &SoftBody3D::is_ray_pickable); diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index a28382f4cb..b9a2736918 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -1177,7 +1177,7 @@ void AnimatedSprite3D::stop() { } bool AnimatedSprite3D::is_playing() const { - return is_processing(); + return playing; } void AnimatedSprite3D::_reset_timeout() { diff --git a/scene/3d/vehicle_body_3d.cpp b/scene/3d/vehicle_body_3d.cpp index 92c0e09947..daeea81891 100644 --- a/scene/3d/vehicle_body_3d.cpp +++ b/scene/3d/vehicle_body_3d.cpp @@ -802,24 +802,21 @@ void VehicleBody3D::_update_friction(PhysicsDirectBodyState3D *s) { } } -void VehicleBody3D::_direct_state_changed(Object *p_state) { - RigidBody3D::_direct_state_changed(p_state); +void VehicleBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { + RigidBody3D::_body_state_changed(p_state); - state = Object::cast_to<PhysicsDirectBodyState3D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument"); - - real_t step = state->get_step(); + real_t step = p_state->get_step(); for (int i = 0; i < wheels.size(); i++) { - _update_wheel(i, state); + _update_wheel(i, p_state); } for (int i = 0; i < wheels.size(); i++) { - _ray_cast(i, state); - wheels[i]->set_transform(state->get_transform().inverse() * wheels[i]->m_worldTransform); + _ray_cast(i, p_state); + wheels[i]->set_transform(p_state->get_transform().inverse() * wheels[i]->m_worldTransform); } - _update_suspension(state); + _update_suspension(p_state); for (int i = 0; i < wheels.size(); i++) { //apply suspension force @@ -831,20 +828,20 @@ void VehicleBody3D::_direct_state_changed(Object *p_state) { suspensionForce = wheel.m_maxSuspensionForce; } Vector3 impulse = wheel.m_raycastInfo.m_contactNormalWS * suspensionForce * step; - Vector3 relative_position = wheel.m_raycastInfo.m_contactPointWS - state->get_transform().origin; + Vector3 relative_position = wheel.m_raycastInfo.m_contactPointWS - p_state->get_transform().origin; - state->apply_impulse(impulse, relative_position); + p_state->apply_impulse(impulse, relative_position); } - _update_friction(state); + _update_friction(p_state); for (int i = 0; i < wheels.size(); i++) { VehicleWheel3D &wheel = *wheels[i]; - Vector3 relpos = wheel.m_raycastInfo.m_hardPointWS - state->get_transform().origin; - Vector3 vel = state->get_linear_velocity() + (state->get_angular_velocity()).cross(relpos); // * mPos); + Vector3 relpos = wheel.m_raycastInfo.m_hardPointWS - p_state->get_transform().origin; + Vector3 vel = p_state->get_linear_velocity() + (p_state->get_angular_velocity()).cross(relpos); // * mPos); if (wheel.m_raycastInfo.m_isInContact) { - const Transform3D &chassisWorldTransform = state->get_transform(); + const Transform3D &chassisWorldTransform = p_state->get_transform(); Vector3 fwd( chassisWorldTransform.basis[0][Vector3::AXIS_Z], @@ -864,8 +861,6 @@ void VehicleBody3D::_direct_state_changed(Object *p_state) { wheel.m_deltaRotation *= real_t(0.99); //damping of rotation when not in contact } - - state = nullptr; } void VehicleBody3D::set_engine_force(real_t p_engine_force) { @@ -926,7 +921,5 @@ void VehicleBody3D::_bind_methods() { VehicleBody3D::VehicleBody3D() { exclude.insert(get_rid()); - //PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &VehicleBody3D::_direct_state_changed)); - set_mass(40); } diff --git a/scene/3d/vehicle_body_3d.h b/scene/3d/vehicle_body_3d.h index 2c10205ea3..f29c3d89b7 100644 --- a/scene/3d/vehicle_body_3d.h +++ b/scene/3d/vehicle_body_3d.h @@ -192,7 +192,8 @@ class VehicleBody3D : public RigidBody3D { static void _bind_methods(); - void _direct_state_changed(Object *p_state) override; + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state); + virtual void _body_state_changed(PhysicsDirectBodyState3D *p_state) override; public: void set_engine_force(real_t p_engine_force); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 049f3483ff..d11387902a 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -1124,6 +1124,7 @@ void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) cons void AnimationNodeBlendTree::reset_state() { graph_offset = Vector2(); nodes.clear(); + _initialize_node_tree(); emit_changed(); emit_signal(SNAME("tree_changed")); } @@ -1162,7 +1163,7 @@ void AnimationNodeBlendTree::_bind_methods() { BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_EXISTS); } -AnimationNodeBlendTree::AnimationNodeBlendTree() { +void AnimationNodeBlendTree::_initialize_node_tree() { Ref<AnimationNodeOutput> output; output.instantiate(); Node n; @@ -1172,5 +1173,9 @@ AnimationNodeBlendTree::AnimationNodeBlendTree() { nodes["output"] = n; } +AnimationNodeBlendTree::AnimationNodeBlendTree() { + _initialize_node_tree(); +} + AnimationNodeBlendTree::~AnimationNodeBlendTree() { } diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index 8508aaf71b..258443a999 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -345,6 +345,8 @@ class AnimationNodeBlendTree : public AnimationRootNode { void _tree_changed(); void _node_changed(const StringName &p_node); + void _initialize_node_tree(); + protected: static void _bind_methods(); bool _set(const StringName &p_name, const Variant &p_value); diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index 298d75b668..0334ba667b 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -31,127 +31,40 @@ #include "audio_stream_player.h" #include "core/config/engine.h" - -void AudioStreamPlayer::_mix_to_bus(const AudioFrame *p_frames, int p_amount) { - int bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus); - - AudioFrame *targets[4] = { nullptr, nullptr, nullptr, nullptr }; - - if (AudioServer::get_singleton()->get_speaker_mode() == AudioServer::SPEAKER_MODE_STEREO) { - targets[0] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 0); - } else { - switch (mix_target) { - case MIX_TARGET_STEREO: { - targets[0] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 0); - } break; - case MIX_TARGET_SURROUND: { - for (int i = 0; i < AudioServer::get_singleton()->get_channel_count(); i++) { - targets[i] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, i); - } - } break; - case MIX_TARGET_CENTER: { - targets[0] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 1); - } break; - } - } - - for (int c = 0; c < 4; c++) { - if (!targets[c]) { - break; - } - for (int i = 0; i < p_amount; i++) { - targets[c][i] += p_frames[i]; - } - } -} - -void AudioStreamPlayer::_mix_internal(bool p_fadeout) { - //get data - AudioFrame *buffer = mix_buffer.ptrw(); - int buffer_size = mix_buffer.size(); - - if (p_fadeout) { - // Short fadeout ramp - buffer_size = MIN(buffer_size, 128); - } - - stream_playback->mix(buffer, pitch_scale, buffer_size); - - //multiply volume interpolating to avoid clicks if this changes - float target_volume = p_fadeout ? -80.0 : volume_db; - float vol = Math::db2linear(mix_volume_db); - float vol_inc = (Math::db2linear(target_volume) - vol) / float(buffer_size); - - for (int i = 0; i < buffer_size; i++) { - buffer[i] *= vol; - vol += vol_inc; - } - - //set volume for next mix - mix_volume_db = target_volume; - - _mix_to_bus(buffer, buffer_size); -} - -void AudioStreamPlayer::_mix_audio() { - if (use_fadeout) { - _mix_to_bus(fadeout_buffer.ptr(), fadeout_buffer.size()); - use_fadeout = false; - } - - if (!stream_playback.is_valid() || !active.is_set() || - (stream_paused && !stream_paused_fade)) { - return; - } - - if (stream_paused) { - if (stream_paused_fade && stream_playback->is_playing()) { - _mix_internal(true); - stream_paused_fade = false; - } - return; - } - - if (setstop.is_set()) { - _mix_internal(true); - stream_playback->stop(); - setstop.clear(); - } - - if (setseek.get() >= 0.0 && !stop_has_priority.is_set()) { - if (stream_playback->is_playing()) { - //fade out to avoid pops - _mix_internal(true); - } - - stream_playback->start(setseek.get()); - setseek.set(-1.0); //reset seek - mix_volume_db = volume_db; //reset ramp - } - - stop_has_priority.clear(); - - _mix_internal(false); -} +#include "core/math/audio_frame.h" +#include "servers/audio_server.h" void AudioStreamPlayer::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - AudioServer::get_singleton()->add_callback(_mix_audios, this); if (autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); } } if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - if (!active.is_set() || (setseek.get() < 0 && !stream_playback->is_playing())) { + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. active.clear(); set_process_internal(false); - emit_signal(SNAME("finished")); } } if (p_what == NOTIFICATION_EXIT_TREE) { - AudioServer::get_singleton()->remove_callback(_mix_audios, this); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); + } + stream_playbacks.clear(); } if (p_what == NOTIFICATION_PAUSED) { @@ -167,50 +80,8 @@ void AudioStreamPlayer::_notification(int p_what) { } void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) { - AudioServer::get_singleton()->lock(); - - if (active.is_set() && stream_playback.is_valid() && !stream_paused) { - //changing streams out of the blue is not a great idea, but at least - //let's try to somehow avoid a click - - AudioFrame *buffer = fadeout_buffer.ptrw(); - int buffer_size = fadeout_buffer.size(); - - stream_playback->mix(buffer, pitch_scale, buffer_size); - - //multiply volume interpolating to avoid clicks if this changes - float target_volume = -80.0; - float vol = Math::db2linear(mix_volume_db); - float vol_inc = (Math::db2linear(target_volume) - vol) / float(buffer_size); - - for (int i = 0; i < buffer_size; i++) { - buffer[i] *= vol; - vol += vol_inc; - } - - use_fadeout = true; - } - - mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); - - if (stream_playback.is_valid()) { - stream_playback.unref(); - stream.unref(); - active.clear(); - setseek.set(-1); - setstop.clear(); - } - - if (p_stream.is_valid()) { - stream = p_stream; - stream_playback = p_stream->instance_playback(); - } - - AudioServer::get_singleton()->unlock(); - - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); - } + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer::get_stream() const { @@ -219,6 +90,11 @@ Ref<AudioStream> AudioStreamPlayer::get_stream() const { void AudioStreamPlayer::set_volume_db(float p_volume) { volume_db = p_volume; + + Vector<AudioFrame> volume_vector = _get_volume_vector(); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_all_bus_volumes_linear(playback, volume_vector); + } } float AudioStreamPlayer::get_volume_db() const { @@ -228,69 +104,94 @@ float AudioStreamPlayer::get_volume_db() const { void AudioStreamPlayer::set_pitch_scale(float p_pitch_scale) { ERR_FAIL_COND(p_pitch_scale <= 0.0); pitch_scale = p_pitch_scale; + + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); + } } float AudioStreamPlayer::get_pitch_scale() const { return pitch_scale; } +void AudioStreamPlayer::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer::get_max_polyphony() const { + return max_polyphony; +} + void AudioStreamPlayer::play(float p_from_pos) { - if (stream_playback.is_valid()) { - //mix_volume_db = volume_db; do not reset volume ramp here, can cause clicks - setseek.set(p_from_pos); - stop_has_priority.clear(); - active.set(); - set_process_internal(true); + if (stream.is_null()) { + return; + } + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); + } + Ref<AudioStreamPlayback> stream_playback = stream->instance_playback(); + ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback."); + + AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos); + stream_playbacks.push_back(stream_playback); + active.set(); + set_process_internal(true); + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); } } void AudioStreamPlayer::seek(float p_seconds) { - if (stream_playback.is_valid()) { - setseek.set(p_seconds); + if (is_playing()) { + stop(); + play(p_seconds); } } void AudioStreamPlayer::stop() { - if (stream_playback.is_valid() && active.is_set()) { - setstop.set(); - stop_has_priority.set(); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_process_internal(false); } bool AudioStreamPlayer::is_playing() const { - if (stream_playback.is_valid()) { - return active.is_set() && !setstop.is_set(); //&& stream_playback->is_playing(); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer::get_playback_position() { - if (stream_playback.is_valid()) { - float ss = setseek.get(); - if (ss >= 0.0) { - return ss; - } - return stream_playback->get_playback_position(); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } void AudioStreamPlayer::set_bus(const StringName &p_bus) { - //if audio is active, must lock this - AudioServer::get_singleton()->lock(); bus = p_bus; - AudioServer::get_singleton()->unlock(); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_exclusive(playback, p_bus, _get_volume_vector()); + } } StringName AudioStreamPlayer::get_bus() const { for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (AudioServer::get_singleton()->get_bus_name(i) == bus) { + if (AudioServer::get_singleton()->get_bus_name(i) == String(bus)) { return bus; } } - return "Master"; + return SNAME("Master"); } void AudioStreamPlayer::set_autoplay(bool p_enable) { @@ -318,18 +219,64 @@ void AudioStreamPlayer::_set_playing(bool p_enable) { } bool AudioStreamPlayer::_is_active() const { - return active.is_set(); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } + } + return false; } void AudioStreamPlayer::set_stream_paused(bool p_pause) { - if (p_pause != stream_paused) { - stream_paused = p_pause; - stream_paused_fade = p_pause; + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer::get_stream_paused() const { - return stream_paused; + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); + } + return false; +} + +Vector<AudioFrame> AudioStreamPlayer::_get_volume_vector() { + Vector<AudioFrame> volume_vector; + // We need at most four stereo pairs (for 7.1 systems). + volume_vector.resize(4); + + // Initialize the volume vector to zero. + for (AudioFrame &channel_volume_db : volume_vector) { + channel_volume_db = AudioFrame(0, 0); + } + + float volume_linear = Math::db2linear(volume_db); + + // Set the volume vector up according to the speaker mode and mix target. + // TODO do we need to scale the volume down when we output to more channels? + if (AudioServer::get_singleton()->get_speaker_mode() == AudioServer::SPEAKER_MODE_STEREO) { + volume_vector.write[0] = AudioFrame(volume_linear, volume_linear); + } else { + switch (mix_target) { + case MIX_TARGET_STEREO: { + volume_vector.write[0] = AudioFrame(volume_linear, volume_linear); + } break; + case MIX_TARGET_SURROUND: { + // TODO Make sure this is right. + volume_vector.write[0] = AudioFrame(volume_linear, volume_linear); + volume_vector.write[1] = AudioFrame(volume_linear, /* LFE= */ 1.0f); + volume_vector.write[2] = AudioFrame(volume_linear, volume_linear); + volume_vector.write[3] = AudioFrame(volume_linear, volume_linear); + } break; + case MIX_TARGET_CENTER: { + // TODO Make sure this is right. + volume_vector.write[1] = AudioFrame(volume_linear, /* LFE= */ 1.0f); + } break; + } + } + return volume_vector; } void AudioStreamPlayer::_validate_property(PropertyInfo &property) const { @@ -352,7 +299,10 @@ void AudioStreamPlayer::_bus_layout_changed() { } Ref<AudioStreamPlayback> AudioStreamPlayer::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; } void AudioStreamPlayer::_bind_methods() { @@ -387,6 +337,9 @@ void AudioStreamPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer::get_stream_paused); + ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer::set_max_polyphony); + ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer::get_max_polyphony); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -396,6 +349,7 @@ void AudioStreamPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_SIGNAL(MethodInfo("finished")); @@ -406,8 +360,6 @@ void AudioStreamPlayer::_bind_methods() { } AudioStreamPlayer::AudioStreamPlayer() { - fadeout_buffer.resize(512); - AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer::_bus_layout_changed)); } diff --git a/scene/audio/audio_stream_player.h b/scene/audio/audio_stream_player.h index aa8d088be5..7205fce204 100644 --- a/scene/audio/audio_stream_player.h +++ b/scene/audio/audio_stream_player.h @@ -46,24 +46,16 @@ public: }; private: - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - Vector<AudioFrame> mix_buffer; - Vector<AudioFrame> fadeout_buffer; - bool use_fadeout = false; - SafeNumeric<float> setseek{ -1.0 }; SafeFlag active; - SafeFlag setstop; - SafeFlag stop_has_priority; - float mix_volume_db = 0.0; float pitch_scale = 1.0; float volume_db = 0.0; bool autoplay = false; - bool stream_paused = false; - bool stream_paused_fade = false; - StringName bus; + StringName bus = SNAME("Master"); + int max_polyphony = 1; MixTarget mix_target = MIX_TARGET_STEREO; @@ -77,6 +69,8 @@ private: void _bus_layout_changed(); void _mix_to_bus(const AudioFrame *p_frames, int p_amount); + Vector<AudioFrame> _get_volume_vector(); + protected: void _validate_property(PropertyInfo &property) const override; void _notification(int p_what); @@ -92,6 +86,9 @@ public: void set_pitch_scale(float p_pitch_scale); float get_pitch_scale() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + void play(float p_from_pos = 0.0); void seek(float p_seconds); void stop(); diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 03c75b25f4..bef1011364 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -52,7 +52,7 @@ void BaseButton::_unpress_group() { } } -void BaseButton::_gui_input(Ref<InputEvent> p_event) { +void BaseButton::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (status.disabled) { // no interaction with disabled button @@ -338,7 +338,7 @@ Ref<Shortcut> BaseButton::get_shortcut() const { return shortcut; } -void BaseButton::_unhandled_key_input(Ref<InputEvent> p_event) { +void BaseButton::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!_is_focus_owner_in_shorcut_context()) { @@ -407,8 +407,6 @@ bool BaseButton::_is_focus_owner_in_shorcut_context() const { } void BaseButton::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &BaseButton::_gui_input); - ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &BaseButton::_unhandled_key_input); ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &BaseButton::set_pressed); ClassDB::bind_method(D_METHOD("is_pressed"), &BaseButton::is_pressed); ClassDB::bind_method(D_METHOD("set_pressed_no_signal", "pressed"), &BaseButton::set_pressed_no_signal); diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index cf1904344b..cd6e78464f 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -75,8 +75,8 @@ protected: virtual void pressed(); virtual void toggled(bool p_pressed); static void _bind_methods(); - virtual void _gui_input(Ref<InputEvent> p_event); - virtual void _unhandled_key_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); bool _is_focus_owner_in_shorcut_context() const; diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index a2f1d2b15a..cf2df4e1a2 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -351,11 +351,11 @@ MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control Label *l = memnew(Label); l->set_theme_type_variation("HeaderSmall"); l->set_text(p_label); - add_child(l); + add_child(l, false, INTERNAL_MODE_FRONT); MarginContainer *mc = memnew(MarginContainer); mc->add_theme_constant_override("margin_left", 0); mc->add_child(p_control); - add_child(mc); + add_child(mc, false, INTERNAL_MODE_FRONT); if (p_expand) { mc->set_v_size_flags(SIZE_EXPAND_FILL); } diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index d93107df2d..411fb2e1f0 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -34,11 +34,13 @@ Size2 CheckBox::get_icon_size() const { Ref<Texture2D> checked = Control::get_theme_icon(SNAME("checked")); - Ref<Texture2D> checked_disabled = Control::get_theme_icon(SNAME("checked_disabled")); Ref<Texture2D> unchecked = Control::get_theme_icon(SNAME("unchecked")); - Ref<Texture2D> unchecked_disabled = Control::get_theme_icon(SNAME("unchecked_disabled")); Ref<Texture2D> radio_checked = Control::get_theme_icon(SNAME("radio_checked")); Ref<Texture2D> radio_unchecked = Control::get_theme_icon(SNAME("radio_unchecked")); + Ref<Texture2D> checked_disabled = Control::get_theme_icon(SNAME("checked_disabled")); + Ref<Texture2D> unchecked_disabled = Control::get_theme_icon(SNAME("unchecked_disabled")); + Ref<Texture2D> radio_checked_disabled = Control::get_theme_icon(SNAME("radio_checked_disabled")); + Ref<Texture2D> radio_unchecked_disabled = Control::get_theme_icon(SNAME("radio_unchecked_disabled")); Size2 tex_size = Size2(0, 0); if (!checked.is_null()) { @@ -53,6 +55,18 @@ Size2 CheckBox::get_icon_size() const { if (!radio_unchecked.is_null()) { tex_size = Size2(MAX(tex_size.width, radio_unchecked->get_width()), MAX(tex_size.height, radio_unchecked->get_height())); } + if (!checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, checked_disabled->get_width()), MAX(tex_size.height, checked_disabled->get_height())); + } + if (!unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, unchecked_disabled->get_width()), MAX(tex_size.height, unchecked_disabled->get_height())); + } + if (!radio_checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, radio_checked_disabled->get_width()), MAX(tex_size.height, radio_checked_disabled->get_height())); + } + if (!radio_unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, radio_unchecked_disabled->get_width()), MAX(tex_size.height, radio_unchecked_disabled->get_height())); + } return tex_size; } diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 7cc2352353..d05762b6c0 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -248,7 +248,7 @@ void CodeEdit::_notification(int p_what) { } } -void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { +void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventMouseButton> mb = p_gui_input; if (mb.is_valid()) { @@ -354,7 +354,7 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventKey> k = p_gui_input; bool update_code_completion = false; if (!k.is_valid()) { - TextEdit::_gui_input(p_gui_input); + TextEdit::gui_input(p_gui_input); return; } @@ -519,7 +519,7 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { set_code_hint(""); } - TextEdit::_gui_input(p_gui_input); + TextEdit::gui_input(p_gui_input); if (update_code_completion) { _filter_code_completion_candidates_impl(); @@ -1400,7 +1400,8 @@ void CodeEdit::fold_line(int p_line) { } /* Find the last line to be hidden. */ - int end_line = get_line_count(); + const int line_count = get_line_count() - 1; + int end_line = line_count; int in_comment = is_in_comment(p_line); int in_string = (in_comment == -1) ? is_in_string(p_line) : -1; @@ -1408,7 +1409,7 @@ void CodeEdit::fold_line(int p_line) { end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y; /* End line is the same therefore we have a block. */ if (end_line == p_line) { - for (int i = p_line + 1; i < get_line_count(); i++) { + for (int i = p_line + 1; i <= line_count; i++) { if ((in_string != -1 && is_in_string(i) == -1) || (in_comment != -1 && is_in_comment(i) == -1)) { end_line = i - 1; break; @@ -1417,14 +1418,27 @@ void CodeEdit::fold_line(int p_line) { } } else { int start_indent = get_indent_level(p_line); - for (int i = p_line + 1; i < get_line_count(); i++) { + for (int i = p_line + 1; i <= line_count; i++) { if (get_line(p_line).strip_edges().size() == 0 || is_in_string(i) != -1 || is_in_comment(i) != -1) { end_line = i; continue; } - if (get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0) { + if (i == line_count) { + /* Do not fold empty last line of script if any */ + end_line = i; + if (get_line(i).strip_edges().size() == 0) { + end_line--; + } + break; + } + + if ((get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0)) { + /* Keep an empty line unfolded if any */ end_line = i - 1; + if (get_line(i - 1).strip_edges().size() == 0 && i - 2 > p_line) { + end_line = i - 2; + } break; } } @@ -1937,7 +1951,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) { if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column() > 0 && get_caret_column() < get_line(caret_line).length()) { pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column() + 1); - if (pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1)) { + if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1)) { remove_text(caret_line, get_caret_column() - 2, caret_line, get_caret_column()); if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1) != pre_brace_pair) { set_caret_column(get_caret_column() - 1); @@ -2651,7 +2665,7 @@ TypedArray<String> CodeEdit::_get_delimiters(DelimiterType p_type) const { void CodeEdit::_filter_code_completion_candidates_impl() { int line_height = get_line_height(); - if (GDVIRTUAL_IS_OVERRIDEN(_filter_code_completion_candidates)) { + if (GDVIRTUAL_IS_OVERRIDDEN(_filter_code_completion_candidates)) { code_completion_options.clear(); code_completion_base = ""; @@ -2744,7 +2758,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { /* If we have a space, previous word might be a keyword. eg "func |". */ } else if (cofs > 0 && line[cofs - 1] == ' ') { int ofs = cofs - 1; - while (ofs >= 0 && line[ofs] == ' ') { + while (ofs > 0 && line[ofs] == ' ') { ofs--; } prev_is_word = _is_char(line[ofs]); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index aa62cbdf3c..4fbb5194e6 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -248,7 +248,7 @@ private: void _text_changed(); protected: - void _gui_input(const Ref<InputEvent> &p_gui_input) override; + void gui_input(const Ref<InputEvent> &p_gui_input) override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 261480bcdd..1afb0b8e9d 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -46,13 +46,13 @@ void ColorPicker::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { btn_pick->set_icon(get_theme_icon(SNAME("screen_picker"), SNAME("ColorPicker"))); - bt_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); - + btn_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); + _update_presets(); _update_controls(); } break; case NOTIFICATION_ENTER_TREE: { btn_pick->set_icon(get_theme_icon(SNAME("screen_picker"), SNAME("ColorPicker"))); - bt_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); + btn_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); _update_controls(); _update_color(); @@ -69,7 +69,6 @@ void ColorPicker::_notification(int p_what) { for (int i = 0; i < preset_cache.size(); i++) { presets.push_back(preset_cache[i]); } - preset->update(); } #endif } break; @@ -98,6 +97,8 @@ Ref<Shader> ColorPicker::circle_shader; void ColorPicker::init_shaders() { wheel_shader.instantiate(); wheel_shader->set_code(R"( +// ColorPicker wheel shader. + shader_type canvas_item; void fragment() { @@ -120,6 +121,8 @@ void fragment() { circle_shader.instantiate(); circle_shader->set_code(R"( +// ColorPicker circle shader. + shader_type canvas_item; uniform float v = 1.0; @@ -372,22 +375,23 @@ void ColorPicker::_update_color(bool p_update_sliders) { } void ColorPicker::_update_presets() { - return; - //presets should be shown using buttons or something else, this method is not a good idea - - presets_per_row = 10; - Size2 size = bt_add_preset->get_size(); - Size2 preset_size = Size2(MIN(size.width * presets.size(), presets_per_row * size.width), size.height * (Math::ceil((float)presets.size() / presets_per_row))); - preset->set_custom_minimum_size(preset_size); - preset_container->set_custom_minimum_size(preset_size); - preset->draw_rect(Rect2(Point2(), preset_size), Color(1, 1, 1, 0)); - - for (int i = 0; i < presets.size(); i++) { - int x = (i % presets_per_row) * size.width; - int y = (Math::floor((float)i / presets_per_row)) * size.height; - preset->draw_rect(Rect2(Point2(x, y), size), presets[i]); + int preset_size = _get_preset_size(); + // Only update the preset button size if it has changed. + if (preset_size != prev_preset_size) { + prev_preset_size = preset_size; + btn_add_preset->set_custom_minimum_size(Size2(preset_size, preset_size)); + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *cpb = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + cpb->set_custom_minimum_size(Size2(preset_size, preset_size)); + } + } + // Only load preset buttons when the only child is the add-preset button. + if (preset_container->get_child_count() == 1) { + for (int i = 0; i < preset_cache.size(); i++) { + _add_preset_button(preset_size, preset_cache[i]); + } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } - _notification(NOTIFICATION_VISIBILITY_CHANGED); } void ColorPicker::_text_type_toggled() { @@ -422,14 +426,37 @@ ColorPicker::PickerShapeType ColorPicker::get_picker_shape() const { return picker_type; } +inline int ColorPicker::_get_preset_size() { + return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("hseparation")) * (preset_column_count - 1))) / preset_column_count; +} + +void ColorPicker::_add_preset_button(int p_size, const Color &p_color) { + ColorPresetButton *btn_preset = memnew(ColorPresetButton(p_color)); + btn_preset->set_preset_color(p_color); + btn_preset->set_custom_minimum_size(Size2(p_size, p_size)); + btn_preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input), varray(p_color)); + btn_preset->set_tooltip(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); + preset_container->add_child(btn_preset); +} + void ColorPicker::add_preset(const Color &p_color) { if (presets.find(p_color)) { presets.move_to_back(presets.find(p_color)); + + // Find button to move to the end. + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + preset_container->move_child(current_btn, preset_container->get_child_count() - 1); + break; + } + } } else { presets.push_back(p_color); preset_cache.push_back(p_color); + + _add_preset_button(_get_preset_size(), p_color); } - preset->update(); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -443,7 +470,15 @@ void ColorPicker::erase_preset(const Color &p_color) { if (presets.find(p_color)) { presets.erase(presets.find(p_color)); preset_cache.erase(preset_cache.find(p_color)); - preset->update(); + + // Find preset button to remove. + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->queue_delete(); + break; + } + } #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -560,7 +595,7 @@ void ColorPicker::_sample_draw() { const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95)); if (display_old_color && old_color.a < 1.0) { - sample->draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPicker")), rect_old, true); + sample->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), rect_old, true); } sample->draw_rect(rect_old, old_color); @@ -574,7 +609,7 @@ void ColorPicker::_sample_draw() { } if (color.a < 1.0) { - sample->draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPicker")), rect_new, true); + sample->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), rect_new, true); } sample->draw_rect(rect_new, color); @@ -734,7 +769,7 @@ void ColorPicker::_slider_draw(int p_which) { #endif if (p_which == 3) { - scroll[p_which]->draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + scroll[p_which]->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -932,43 +967,19 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { } } -void ColorPicker::_preset_input(const Ref<InputEvent> &p_event) { +void ColorPicker::_preset_input(const Ref<InputEvent> &p_event, const Color &p_color) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - int index = 0; if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { - for (int i = 0; i < presets.size(); i++) { - int x = (i % presets_per_row) * bt_add_preset->get_size().x; - int y = (Math::floor((float)i / presets_per_row)) * bt_add_preset->get_size().y; - if (bev->get_position().x > x && bev->get_position().x < x + preset->get_size().x && bev->get_position().y > y && bev->get_position().y < y + preset->get_size().y) { - index = i; - } - } - set_pick_color(presets[index]); + set_pick_color(p_color); _update_color(); - emit_signal(SNAME("color_changed"), color); + emit_signal(SNAME("color_changed"), p_color); } else if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_RIGHT && presets_enabled) { - index = bev->get_position().x / (preset->get_size().x / presets.size()); - Color clicked_preset = presets[index]; - erase_preset(clicked_preset); - emit_signal(SNAME("preset_removed"), clicked_preset); - bt_add_preset->show(); + erase_preset(p_color); + emit_signal(SNAME("preset_removed"), p_color); } } - - Ref<InputEventMouseMotion> mev = p_event; - - if (mev.is_valid()) { - int index = mev->get_position().x * presets.size(); - if (preset->get_size().x != 0) { - index /= preset->get_size().x; - } - if (index < 0 || index >= presets.size()) { - return; - } - preset->set_tooltip(vformat(RTR("Color: #%s\nLMB: Set color\nRMB: Remove preset"), presets[index].to_html(presets[index].a < 1))); - } } void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { @@ -1064,11 +1075,11 @@ void ColorPicker::_html_focus_exit() { void ColorPicker::set_presets_enabled(bool p_enabled) { presets_enabled = p_enabled; if (!p_enabled) { - bt_add_preset->set_disabled(true); - bt_add_preset->set_focus_mode(FOCUS_NONE); + btn_add_preset->set_disabled(true); + btn_add_preset->set_focus_mode(FOCUS_NONE); } else { - bt_add_preset->set_disabled(false); - bt_add_preset->set_focus_mode(FOCUS_ALL); + btn_add_preset->set_disabled(false); + btn_add_preset->set_focus_mode(FOCUS_ALL); } } @@ -1080,7 +1091,6 @@ void ColorPicker::set_presets_visible(bool p_visible) { presets_visible = p_visible; preset_separator->set_visible(p_visible); preset_container->set_visible(p_visible); - preset_container2->set_visible(p_visible); } bool ColorPicker::are_presets_visible() const { @@ -1129,7 +1139,7 @@ void ColorPicker::_bind_methods() { ColorPicker::ColorPicker() : BoxContainer(true) { HBoxContainer *hb_edit = memnew(HBoxContainer); - add_child(hb_edit); + add_child(hb_edit, false, INTERNAL_MODE_FRONT); hb_edit->set_v_size_flags(SIZE_EXPAND_FILL); hb_edit->add_child(uv_edit); @@ -1141,7 +1151,7 @@ ColorPicker::ColorPicker() : uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, uv_edit)); HBoxContainer *hb_smpl = memnew(HBoxContainer); - add_child(hb_smpl); + add_child(hb_smpl, false, INTERNAL_MODE_FRONT); hb_smpl->add_child(sample); sample->set_h_size_flags(SIZE_EXPAND_FILL); @@ -1155,12 +1165,12 @@ ColorPicker::ColorPicker() : btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed)); VBoxContainer *vbl = memnew(VBoxContainer); - add_child(vbl); + add_child(vbl, false, INTERNAL_MODE_FRONT); - add_child(memnew(HSeparator)); + add_child(memnew(HSeparator), false, INTERNAL_MODE_FRONT); VBoxContainer *vbr = memnew(VBoxContainer); - add_child(vbr); + add_child(vbr, false, INTERNAL_MODE_FRONT); vbr->set_h_size_flags(SIZE_EXPAND_FILL); for (int i = 0; i < 4; i++) { @@ -1263,20 +1273,16 @@ ColorPicker::ColorPicker() : set_pick_color(Color(1, 1, 1)); - add_child(preset_separator); + add_child(preset_separator, false, INTERNAL_MODE_FRONT); preset_container->set_h_size_flags(SIZE_EXPAND_FILL); - add_child(preset_container); + preset_container->set_columns(preset_column_count); + add_child(preset_container, false, INTERNAL_MODE_FRONT); - preset_container->add_child(preset); - preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input)); - preset->connect("draw", callable_mp(this, &ColorPicker::_update_presets)); - - preset_container2->set_h_size_flags(SIZE_EXPAND_FILL); - add_child(preset_container2); - preset_container2->add_child(bt_add_preset); - bt_add_preset->set_tooltip(RTR("Add current color as a preset.")); - bt_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed)); + btn_add_preset->set_icon_align(Button::ALIGN_CENTER); + btn_add_preset->set_tooltip(RTR("Add current color as a preset.")); + btn_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed)); + preset_container->add_child(btn_add_preset); } ///////////////// @@ -1303,6 +1309,7 @@ void ColorPickerButton::pressed() { _update_picker(); popup->set_as_minsize(); + picker->_update_presets(); Rect2i usable_rect = popup->get_usable_parent_rect(); //let's try different positions to see which one we can use @@ -1398,7 +1405,7 @@ void ColorPickerButton::_update_picker() { picker = memnew(ColorPicker); picker->set_anchors_and_offsets_preset(PRESET_WIDE); popup->add_child(picker); - add_child(popup); + add_child(popup, false, INTERNAL_MODE_FRONT); picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed)); popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup)); popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed)); @@ -1428,3 +1435,64 @@ void ColorPickerButton::_bind_methods() { ColorPickerButton::ColorPickerButton() { set_toggle_mode(true); } + +///////////////// + +void ColorPresetButton::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_DRAW: { + const Rect2 r = Rect2(Point2(0, 0), get_size()); + Ref<StyleBox> sb_raw = get_theme_stylebox(SNAME("preset_fg"), SNAME("ColorPresetButton"))->duplicate(); + Ref<StyleBoxFlat> sb_flat = sb_raw; + Ref<StyleBoxTexture> sb_texture = sb_raw; + + if (sb_flat.is_valid()) { + if (preset_color.a < 1) { + // Draw a background pattern when the color is transparent. + sb_flat->set_bg_color(Color(1, 1, 1)); + sb_flat->draw(get_canvas_item(), r); + + Rect2 bg_texture_rect = r.grow_side(SIDE_LEFT, -sb_flat->get_margin(SIDE_LEFT)); + bg_texture_rect = bg_texture_rect.grow_side(SIDE_RIGHT, -sb_flat->get_margin(SIDE_RIGHT)); + bg_texture_rect = bg_texture_rect.grow_side(SIDE_TOP, -sb_flat->get_margin(SIDE_TOP)); + bg_texture_rect = bg_texture_rect.grow_side(SIDE_BOTTOM, -sb_flat->get_margin(SIDE_BOTTOM)); + + draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPresetButton")), bg_texture_rect, true); + sb_flat->set_bg_color(preset_color); + } + sb_flat->set_bg_color(preset_color); + sb_flat->draw(get_canvas_item(), r); + } else if (sb_texture.is_valid()) { + if (preset_color.a < 1) { + // Draw a background pattern when the color is transparent. + bool use_tile_texture = (sb_texture->get_h_axis_stretch_mode() == StyleBoxTexture::AxisStretchMode::AXIS_STRETCH_MODE_TILE) || (sb_texture->get_h_axis_stretch_mode() == StyleBoxTexture::AxisStretchMode::AXIS_STRETCH_MODE_TILE_FIT); + draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPresetButton")), r, use_tile_texture); + } + sb_texture->set_modulate(preset_color); + sb_texture->draw(get_canvas_item(), r); + } else { + WARN_PRINT("Unsupported StyleBox used for ColorPresetButton. Use StyleBoxFlat or StyleBoxTexture instead."); + } + if (preset_color.r > 1 || preset_color.g > 1 || preset_color.b > 1) { + // Draw an indicator to denote that the color is "overbright" and can't be displayed accurately in the preview + draw_texture(Control::get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPresetButton")), Vector2(0, 0)); + } + + } break; + } +} + +void ColorPresetButton::set_preset_color(const Color &p_color) { + preset_color = p_color; +} + +Color ColorPresetButton::get_preset_color() const { + return preset_color; +} + +ColorPresetButton::ColorPresetButton(Color p_color) { + preset_color = p_color; +} + +ColorPresetButton::~ColorPresetButton() { +} diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 60da3957aa..67ca007eb5 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -35,6 +35,7 @@ #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/check_button.h" +#include "scene/gui/grid_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/popup.h" @@ -43,6 +44,22 @@ #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" +class ColorPresetButton : public BaseButton { + GDCLASS(ColorPresetButton, BaseButton); + + Color preset_color; + +protected: + void _notification(int); + +public: + void set_preset_color(const Color &p_color); + Color get_preset_color() const; + + ColorPresetButton(Color p_color); + ~ColorPresetButton(); +}; + class ColorPicker : public BoxContainer { GDCLASS(ColorPicker, BoxContainer); @@ -69,12 +86,9 @@ private: Control *wheel = memnew(Control); Control *wheel_uv = memnew(Control); TextureRect *sample = memnew(TextureRect); - TextureRect *preset = memnew(TextureRect); - HBoxContainer *preset_container = memnew(HBoxContainer); - HBoxContainer *preset_container2 = memnew(HBoxContainer); + GridContainer *preset_container = memnew(GridContainer); HSeparator *preset_separator = memnew(HSeparator); - Button *bt_add_preset = memnew(Button); - List<Color> presets; + Button *btn_add_preset = memnew(Button); Button *btn_pick = memnew(Button); CheckButton *btn_hsv = memnew(CheckButton); CheckButton *btn_raw = memnew(CheckButton); @@ -83,14 +97,19 @@ private: Label *labels[4]; Button *text_type = memnew(Button); LineEdit *c_text = memnew(LineEdit); + bool edit_alpha = true; Size2i ms; bool text_is_constructor = false; - int presets_per_row = 0; PickerShapeType picker_type = SHAPE_HSV_WHEEL; + const int preset_column_count = 9; + int prev_preset_size = 0; + List<Color> presets; + Color color; Color old_color; + bool display_old_color = false; bool raw_mode_enabled = false; bool hsv_mode_enabled = false; @@ -100,6 +119,7 @@ private: bool spinning = false; bool presets_enabled = true; bool presets_visible = true; + float h = 0.0; float s = 0.0; float v = 0.0; @@ -109,7 +129,6 @@ private: void _value_changed(double); void _update_controls(); void _update_color(bool p_update_sliders = true); - void _update_presets(); void _update_text_value(); void _text_type_toggled(); void _sample_input(const Ref<InputEvent> &p_event); @@ -119,7 +138,7 @@ private: void _uv_input(const Ref<InputEvent> &p_event, Control *c); void _w_input(const Ref<InputEvent> &p_event); - void _preset_input(const Ref<InputEvent> &p_event); + void _preset_input(const Ref<InputEvent> &p_event, const Color &p_color); void _screen_input(const Ref<InputEvent> &p_event); void _add_preset_pressed(); void _screen_pick_pressed(); @@ -127,6 +146,9 @@ private: void _focus_exit(); void _html_focus_exit(); + inline int _get_preset_size(); + void _add_preset_button(int p_size, const Color &p_color); + protected: void _notification(int); static void _bind_methods(); @@ -152,6 +174,7 @@ public: void add_preset(const Color &p_color); void erase_preset(const Color &p_color); PackedColorArray get_presets() const; + void _update_presets(); void set_hsv_mode(bool p_enabled); bool is_hsv_mode() const; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 6dba23d3c7..81411d5844 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -30,6 +30,7 @@ #include "control.h" +#include "container.h" #include "core/config/project_settings.h" #include "core/math/geometry_2d.h" #include "core/object/message_queue.h" @@ -168,6 +169,20 @@ Size2 Control::_edit_get_minimum_size() const { } #endif +String Control::properties_managed_by_container[] = { + "offset_left", + "offset_top", + "offset_right", + "offset_bottom", + "anchor_left", + "anchor_top", + "anchor_right", + "anchor_bottom", + "rect_position", + "rect_scale", + "rect_size" +}; + void Control::accept_event() { if (is_inside_tree()) { get_viewport()->_gui_accept_event(); @@ -442,6 +457,20 @@ void Control::_validate_property(PropertyInfo &property) const { property.hint_string = hint_string; } + if (!Object::cast_to<Container>(get_parent())) { + return; + } + // Disable the property if it's managed by the parent container. + bool property_is_managed_by_container = false; + for (unsigned i = 0; i < properties_managed_by_container_count; i++) { + property_is_managed_by_container = properties_managed_by_container[i] == property.name; + if (property_is_managed_by_container) { + break; + } + } + if (property_is_managed_by_container) { + property.usage |= PROPERTY_USAGE_READ_ONLY; + } } Control *Control::get_parent_control() const { @@ -780,6 +809,20 @@ void Control::set_drag_preview(Control *p_control) { get_viewport()->_gui_set_drag_preview(this, p_control); } +void Control::_call_gui_input(const Ref<InputEvent> &p_event) { + emit_signal(SceneStringNames::get_singleton()->gui_input, p_event); //signal should be first, so it's possible to override an event (and then accept it) + if (!is_inside_tree() || get_viewport()->is_input_handled()) { + return; //input was handled, abort + } + GDVIRTUAL_CALL(_gui_input, p_event); + if (!is_inside_tree() || get_viewport()->is_input_handled()) { + return; //input was handled, abort + } + gui_input(p_event); +} +void Control::gui_input(const Ref<InputEvent> &p_event) { +} + Size2 Control::get_minimum_size() const { Vector2 ms; if (GDVIRTUAL_CALL(_get_minimum_size, ms)) { @@ -2798,8 +2841,6 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_auto_translate", "enable"), &Control::set_auto_translate); ClassDB::bind_method(D_METHOD("is_auto_translating"), &Control::is_auto_translating); - BIND_VMETHOD(MethodInfo("_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); - ADD_GROUP("Anchor", "anchor_"); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_LEFT); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_top", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_TOP); @@ -2960,8 +3001,10 @@ void Control::_bind_methods() { GDVIRTUAL_BIND(_structured_text_parser, "args", "text"); GDVIRTUAL_BIND(_get_minimum_size); - GDVIRTUAL_BIND(_get_drag_data, "at_position") - GDVIRTUAL_BIND(_can_drop_data, "at_position", "data") - GDVIRTUAL_BIND(_drop_data, "at_position", "data") - GDVIRTUAL_BIND(_make_custom_tooltip, "for_text") + GDVIRTUAL_BIND(_get_drag_data, "at_position"); + GDVIRTUAL_BIND(_can_drop_data, "at_position", "data"); + GDVIRTUAL_BIND(_drop_data, "at_position", "data"); + GDVIRTUAL_BIND(_make_custom_tooltip, "for_text"); + + GDVIRTUAL_BIND(_gui_input, "event"); } diff --git a/scene/gui/control.h b/scene/gui/control.h index 0d7a3b8de0..9cec5d6e8d 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -31,10 +31,10 @@ #ifndef CONTROL_H #define CONTROL_H +#include "core/input/shortcut.h" #include "core/math/transform_2d.h" #include "core/object/gdvirtual.gen.inc" #include "core/templates/rid.h" -#include "scene/gui/shortcut.h" #include "scene/main/canvas_item.h" #include "scene/main/node.h" #include "scene/main/timer.h" @@ -230,6 +230,9 @@ private: } data; + static constexpr unsigned properties_managed_by_container_count = 11; + static String properties_managed_by_container[properties_managed_by_container_count]; + // used internally Control *_find_control_at_pos(CanvasItem *p_node, const Point2 &p_pos, const Transform2D &p_xform, Transform2D &r_inv_xform); @@ -263,6 +266,8 @@ private: friend class Viewport; + void _call_gui_input(const Ref<InputEvent> &p_event); + void _update_minimum_size_cache(); friend class Window; static void _propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign = true); @@ -272,17 +277,6 @@ private: static bool has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types); _FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const; - GDVIRTUAL1RC(bool, _has_point, Vector2) - GDVIRTUAL2RC(Array, _structured_text_parser, Array, String) - GDVIRTUAL0RC(Vector2, _get_minimum_size) - - GDVIRTUAL1RC(Variant, _get_drag_data, Vector2) - GDVIRTUAL2RC(bool, _can_drop_data, Vector2, Variant) - GDVIRTUAL2(_drop_data, Vector2, Variant) - GDVIRTUAL1RC(Object *, _make_custom_tooltip, String) - - //GDVIRTUAL1(_gui_input, Ref<InputEvent>) - protected: virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; @@ -301,6 +295,17 @@ protected: //bind helpers + GDVIRTUAL1RC(bool, _has_point, Vector2) + GDVIRTUAL2RC(Array, _structured_text_parser, Array, String) + GDVIRTUAL0RC(Vector2, _get_minimum_size) + + GDVIRTUAL1RC(Variant, _get_drag_data, Vector2) + GDVIRTUAL2RC(bool, _can_drop_data, Vector2, Variant) + GDVIRTUAL2(_drop_data, Vector2, Variant) + GDVIRTUAL1RC(Object *, _make_custom_tooltip, String) + + GDVIRTUAL1(_gui_input, Ref<InputEvent>) + public: enum { /* NOTIFICATION_DRAW=30, @@ -343,6 +348,8 @@ public: virtual Size2 _edit_get_minimum_size() const override; #endif + virtual void gui_input(const Ref<InputEvent> &p_event); + void accept_event(); virtual Size2 get_minimum_size() const; diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index da858e8e83..5d98aaa698 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -319,7 +319,7 @@ AcceptDialog::AcceptDialog() { set_clamp_to_embedder(true); bg = memnew(Panel); - add_child(bg); + add_child(bg, false, INTERNAL_MODE_FRONT); hbc = memnew(HBoxContainer); @@ -331,9 +331,9 @@ AcceptDialog::AcceptDialog() { label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END); label->set_begin(Point2(margin, margin)); label->set_end(Point2(-margin, -button_margin - 10)); - add_child(label); + add_child(label, false, INTERNAL_MODE_FRONT); - add_child(hbc); + add_child(hbc, false, INTERNAL_MODE_FRONT); hbc->add_spacer(); ok = memnew(Button); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 2e4204e171..973b72973d 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -95,7 +95,7 @@ void FileDialog::_notification(int p_what) { } } -void FileDialog::_unhandled_input(const Ref<InputEvent> &p_event) { +void FileDialog::unhandled_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventKey> k = p_event; @@ -854,8 +854,6 @@ void FileDialog::_update_drives() { bool FileDialog::default_show_hidden_files = false; void FileDialog::_bind_methods() { - ClassDB::bind_method(D_METHOD("_unhandled_input"), &FileDialog::_unhandled_input); - ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed); ClassDB::bind_method(D_METHOD("clear_filters"), &FileDialog::clear_filters); @@ -926,7 +924,7 @@ FileDialog::FileDialog() { show_hidden_files = default_show_hidden_files; vbox = memnew(VBoxContainer); - add_child(vbox); + add_child(vbox, false, INTERNAL_MODE_FRONT); vbox->connect("theme_changed", callable_mp(this, &FileDialog::_theme_changed)); mode = FILE_MODE_SAVE_FILE; @@ -1025,8 +1023,7 @@ FileDialog::FileDialog() { filter->connect("item_selected", callable_mp(this, &FileDialog::_filter_selected)); confirm_save = memnew(ConfirmationDialog); - // confirm_save->set_as_top_level(true); - add_child(confirm_save); + add_child(confirm_save, false, INTERNAL_MODE_FRONT); confirm_save->connect("confirmed", callable_mp(this, &FileDialog::_save_confirm_pressed)); @@ -1038,16 +1035,16 @@ FileDialog::FileDialog() { makedirname = memnew(LineEdit); makedirname->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE); makevb->add_margin_child(TTRC("Name:"), makedirname); - add_child(makedialog); + add_child(makedialog, false, INTERNAL_MODE_FRONT); makedialog->register_text_enter(makedirname); makedialog->connect("confirmed", callable_mp(this, &FileDialog::_make_dir_confirm)); mkdirerr = memnew(AcceptDialog); mkdirerr->set_text(TTRC("Could not create folder.")); - add_child(mkdirerr); + add_child(mkdirerr, false, INTERNAL_MODE_FRONT); exterr = memnew(AcceptDialog); exterr->set_text(TTRC("Must use a valid extension.")); - add_child(exterr); + add_child(exterr, false, INTERNAL_MODE_FRONT); update_filters(); update_dir(); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 7fbafc4bb4..b5190bdab1 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -132,7 +132,7 @@ private: void _update_drives(); - void _unhandled_input(const Ref<InputEvent> &p_event); + virtual void unhandled_input(const Ref<InputEvent> &p_event) override; bool _is_open_should_be_disabled(); diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index 635f3c51b9..56b8a936e1 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -48,7 +48,7 @@ GradientEdit::GradientEdit() { picker = memnew(ColorPicker); popup->add_child(picker); - add_child(popup); + add_child(popup, false, INTERNAL_MODE_FRONT); } int GradientEdit::_get_point_from_pos(int x) { @@ -88,7 +88,7 @@ void GradientEdit::_show_color_picker() { GradientEdit::~GradientEdit() { } -void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { +void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventKey> k = p_event; @@ -458,6 +458,5 @@ Vector<Gradient::Point> &GradientEdit::get_points() { } void GradientEdit::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &GradientEdit::_gui_input); ADD_SIGNAL(MethodInfo("ramp_changed")); } diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h index b0ee2c4abb..a173631963 100644 --- a/scene/gui/gradient_edit.h +++ b/scene/gui/gradient_edit.h @@ -52,7 +52,7 @@ class GradientEdit : public Control { void _show_color_picker(); protected: - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index ac1dea5e94..cabae9feb2 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -51,10 +51,6 @@ GraphEditFilter::GraphEditFilter(GraphEdit *p_edit) { ge = p_edit; } -void GraphEditMinimap::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &GraphEditMinimap::_gui_input); -} - GraphEditMinimap::GraphEditMinimap(GraphEdit *p_edit) { ge = p_edit; @@ -148,7 +144,7 @@ Vector2 GraphEditMinimap::_convert_to_graph_position(const Vector2 &p_position) return graph_position; } -void GraphEditMinimap::_gui_input(const Ref<InputEvent> &p_ev) { +void GraphEditMinimap::gui_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); if (!ge->is_minimap_enabled()) { @@ -370,7 +366,6 @@ void GraphEdit::_graph_node_raised(Node *p_gn) { } move_child(connections_layer, first_not_comment); - top_layer->raise(); emit_signal(SNAME("node_selected"), p_gn); } @@ -806,8 +801,9 @@ bool GraphEdit::is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos, c } PackedVector2Array GraphEdit::get_connection_line(const Vector2 &p_from, const Vector2 &p_to) { - if (get_script_instance() && get_script_instance()->get_script().is_valid() && get_script_instance()->has_method("_get_connection_line")) { - return get_script_instance()->call("_get_connection_line", p_from, p_to); + Vector<Vector2> ret; + if (GDVIRTUAL_CALL(_get_connection_line, p_from, p_to, ret)) { + return ret; } Curve2D curve; @@ -823,9 +819,9 @@ void GraphEdit::_draw_connection_line(CanvasItem *p_where, const Vector2 &p_from Vector<Vector2> points = get_connection_line(p_from / p_zoom, p_to / p_zoom); Vector<Vector2> scaled_points; Vector<Color> colors; - float length = p_from.distance_to(p_to); + float length = (p_from / p_zoom).distance_to(p_to / p_zoom); for (int i = 0; i < points.size(); i++) { - float d = p_from.distance_to(points[i]) / length; + float d = (p_from / p_zoom).distance_to(points[i]) / length; colors.push_back(p_color.lerp(p_to_color, d)); scaled_points.push_back(points[i] * p_zoom); } @@ -1047,7 +1043,7 @@ void GraphEdit::set_selected(Node *p_child) { } } -void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { +void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventMouseMotion> mm = p_ev; @@ -2191,7 +2187,6 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_right_disconnects", "enable"), &GraphEdit::set_right_disconnects); ClassDB::bind_method(D_METHOD("is_right_disconnects_enabled"), &GraphEdit::is_right_disconnects_enabled); - ClassDB::bind_method(D_METHOD("_gui_input"), &GraphEdit::_gui_input); ClassDB::bind_method(D_METHOD("_update_scroll_offset"), &GraphEdit::_update_scroll_offset); ClassDB::bind_method(D_METHOD("get_zoom_hbox"), &GraphEdit::get_zoom_hbox); @@ -2200,7 +2195,7 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected); - BIND_VMETHOD(MethodInfo(Variant::PACKED_VECTOR2_ARRAY, "_get_connection_line", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to"))); + GDVIRTUAL_BIND(_get_connection_line, "from", "to") ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset"), "set_scroll_ofs", "get_scroll_ofs"); @@ -2250,14 +2245,14 @@ GraphEdit::GraphEdit() { zoom_max = (1 * Math::pow(zoom_step, 4)); top_layer = memnew(GraphEditFilter(this)); - add_child(top_layer); + add_child(top_layer, false, INTERNAL_MODE_BACK); top_layer->set_mouse_filter(MOUSE_FILTER_PASS); top_layer->set_anchors_and_offsets_preset(Control::PRESET_WIDE); top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw)); top_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_layer_input)); connections_layer = memnew(Control); - add_child(connections_layer); + add_child(connections_layer, false, INTERNAL_MODE_FRONT); connections_layer->connect("draw", callable_mp(this, &GraphEdit::_connections_layer_draw)); connections_layer->set_name("CLAYER"); connections_layer->set_disable_visibility_clip(true); // so it can draw freely and be offset diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 7a9286be0f..44e50aa3c2 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -62,8 +62,6 @@ class GraphEditMinimap : public Control { GraphEdit *ge; protected: - static void _bind_methods(); - public: GraphEditMinimap(GraphEdit *p_edit); @@ -88,7 +86,7 @@ private: Vector2 _convert_from_graph_position(const Vector2 &p_position); Vector2 _convert_to_graph_position(const Vector2 &p_position); - void _gui_input(const Ref<InputEvent> &p_ev); + virtual void gui_input(const Ref<InputEvent> &p_ev) override; void _adjust_graph_scroll(const Vector2 &p_offset); }; @@ -178,7 +176,7 @@ private: void _update_scroll(); void _scroll_moved(double); - void _gui_input(const Ref<InputEvent> &p_ev); + virtual void gui_input(const Ref<InputEvent> &p_ev) override; Control *connections_layer; GraphEditFilter *top_layer; @@ -255,6 +253,8 @@ protected: virtual void remove_child_notify(Node *p_child) override; void _notification(int p_what); + GDVIRTUAL2RC(Vector<Vector2>, _get_connection_line, Vector2, Vector2) + public: Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); bool is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index e85cefcb7b..08c8c60d7a 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -863,7 +863,7 @@ Color GraphNode::get_connection_output_color(int p_idx) { return conn_output_cache[p_idx].color; } -void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) { +void GraphNode::gui_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventMouseButton> mb = p_ev; @@ -946,8 +946,6 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_language", "language"), &GraphNode::set_language); ClassDB::bind_method(D_METHOD("get_language"), &GraphNode::get_language); - ClassDB::bind_method(D_METHOD("_gui_input"), &GraphNode::_gui_input); - ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>())); ClassDB::bind_method(D_METHOD("clear_slot", "idx"), &GraphNode::clear_slot); ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index c70f616b47..c7c7006bfc 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -99,7 +99,7 @@ private: Overlay overlay = OVERLAY_DISABLED; protected: - void _gui_input(const Ref<InputEvent> &p_ev); + virtual void gui_input(const Ref<InputEvent> &p_ev) override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 258d65112a..d10ad90c1f 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -48,6 +48,8 @@ void ItemList::_shape(int p_idx) { } else { item.text_buf->set_flags(TextServer::BREAK_NONE); } + item.text_buf->set_text_overrun_behavior(text_overrun_behavior); + item.text_buf->set_max_lines_visible(max_text_lines); } int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bool p_selectable) { @@ -453,6 +455,7 @@ void ItemList::set_max_text_lines(int p_lines) { for (int i = 0; i < items.size(); i++) { if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { items.write[i].text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + items.write[i].text_buf->set_max_lines_visible(p_lines); } else { items.write[i].text_buf->set_flags(TextServer::BREAK_NONE); } @@ -534,7 +537,7 @@ Size2 ItemList::Item::get_icon_size() const { return size_result; } -void ItemList::_gui_input(const Ref<InputEvent> &p_event) { +void ItemList::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); double prev_scroll = scroll_bar->get_value(); @@ -930,8 +933,14 @@ void ItemList::_notification(int p_what) { } if (items[i].text != "") { + int max_width = -1; + if (fixed_column_width) { + max_width = fixed_column_width; + } else if (same_column_width) { + max_width = items[i].rect_cache.size.x; + } + items.write[i].text_buf->set_width(max_width); Size2 s = items[i].text_buf->get_size(); - //s.width=MIN(s.width,fixed_column_width); if (icon_mode == ICON_MODE_TOP) { minsize.x = MAX(minsize.x, s.width); @@ -1139,11 +1148,8 @@ void ItemList::_notification(int p_what) { if (icon_mode == ICON_MODE_TOP) { pos.x += Math::floor((items[i].rect_cache.size.width - icon_size.width) / 2); - pos.y += MIN( - Math::floor((items[i].rect_cache.size.height - icon_size.height) / 2), - items[i].rect_cache.size.height - items[i].min_rect_cache.size.height); - text_ofs.y = icon_size.height + icon_margin; - text_ofs.y += items[i].rect_cache.size.height - items[i].min_rect_cache.size.height; + pos.y += icon_margin; + text_ofs.y = icon_size.height + icon_margin * 2; } else { pos.y += Math::floor((items[i].rect_cache.size.height - icon_size.height) / 2); text_ofs.x = icon_size.width + icon_margin; @@ -1210,7 +1216,6 @@ void ItemList::_notification(int p_what) { text_ofs.x = size.width - text_ofs.x - max_len; } - items.write[i].text_buf->set_width(max_len); items.write[i].text_buf->set_align(HALIGN_CENTER); if (outline_size > 0 && font_outline_color.a > 0) { @@ -1488,6 +1493,21 @@ bool ItemList::has_auto_height() const { return auto_height; } +void ItemList::set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavior) { + if (text_overrun_behavior != p_behavior) { + text_overrun_behavior = p_behavior; + for (int i = 0; i < items.size(); i++) { + items.write[i].text_buf->set_text_overrun_behavior(p_behavior); + } + shape_changed = true; + update(); + } +} + +TextParagraph::OverrunBehavior ItemList::get_text_overrun_behavior() const { + return text_overrun_behavior; +} + void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("add_item", "text", "icon", "selectable"), &ItemList::add_item, DEFVAL(Variant()), DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_icon_item", "icon", "selectable"), &ItemList::add_icon_item, DEFVAL(true)); @@ -1594,11 +1614,12 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("get_v_scroll"), &ItemList::get_v_scroll); - ClassDB::bind_method(D_METHOD("_gui_input"), &ItemList::_gui_input); - ClassDB::bind_method(D_METHOD("_set_items"), &ItemList::_set_items); ClassDB::bind_method(D_METHOD("_get_items"), &ItemList::_get_items); + ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &ItemList::set_text_overrun_behavior); + ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &ItemList::get_text_overrun_behavior); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items"); ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Multi"), "set_select_mode", "get_select_mode"); @@ -1606,6 +1627,7 @@ void ItemList::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_text_lines", PROPERTY_HINT_RANGE, "1,10,1,or_greater"), "set_max_text_lines", "get_max_text_lines"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_height"), "set_auto_height", "has_auto_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); ADD_GROUP("Columns", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_columns", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), "set_max_columns", "get_max_columns"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "same_column_width"), "set_same_column_width", "is_same_column_width"); @@ -1634,7 +1656,7 @@ void ItemList::_bind_methods() { ItemList::ItemList() { scroll_bar = memnew(VScrollBar); - add_child(scroll_bar); + add_child(scroll_bar, false, INTERNAL_MODE_FRONT); scroll_bar->connect("value_changed", callable_mp(this, &ItemList::_scroll_changed)); diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index 86a0174a20..148fa7ba9f 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -95,6 +95,7 @@ private: SelectMode select_mode = SELECT_SINGLE; IconMode icon_mode = ICON_MODE_LEFT; VScrollBar *scroll_bar; + TextParagraph::OverrunBehavior text_overrun_behavior = TextParagraph::OVERRUN_NO_TRIMMING; uint64_t search_time_msec = 0; String search_string; @@ -122,7 +123,6 @@ private: void _set_items(const Array &p_items); void _scroll_changed(double); - void _gui_input(const Ref<InputEvent> &p_event); void _shape(int p_idx); protected: @@ -130,6 +130,8 @@ protected: static void _bind_methods(); public: + virtual void gui_input(const Ref<InputEvent> &p_event) override; + int add_item(const String &p_item, const Ref<Texture2D> &p_texture = Ref<Texture2D>(), bool p_selectable = true); int add_icon_item(const Ref<Texture2D> &p_item, bool p_selectable = true); @@ -182,6 +184,9 @@ public: void set_item_custom_fg_color(int p_idx, const Color &p_custom_fg_color); Color get_item_custom_fg_color(int p_idx) const; + void set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavior); + TextParagraph::OverrunBehavior get_text_overrun_behavior() const; + void select(int p_idx, bool p_single = true); void deselect(int p_idx); void deselect_all(); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 50db1fc3ce..3f87003423 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -66,11 +66,11 @@ bool Label::is_uppercase() const { int Label::get_line_height(int p_line) const { Ref<Font> font = get_theme_font(SNAME("font")); if (p_line >= 0 && p_line < lines_rid.size()) { - return TS->shaped_text_get_size(lines_rid[p_line]).y + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM); + return TS->shaped_text_get_size(lines_rid[p_line]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); } else if (lines_rid.size() > 0) { int h = 0; for (int i = 0; i < lines_rid.size(); i++) { - h = MAX(h, TS->shaped_text_get_size(lines_rid[i]).y) + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM); + h = MAX(h, TS->shaped_text_get_size(lines_rid[i]).y) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); } return h; } else { @@ -89,7 +89,10 @@ void Label::_shape() { } else { TS->shaped_text_set_direction(text_rid, (TextServer::Direction)text_direction); } - TS->shaped_text_add_string(text_rid, (uppercase) ? xl_text.to_upper() : xl_text, get_theme_font(SNAME("font"))->get_rids(), get_theme_font_size(SNAME("font_size")), opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + const Ref<Font> &font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); + ERR_FAIL_COND(font.is_null()); + TS->shaped_text_add_string(text_rid, (uppercase) ? xl_text.to_upper() : xl_text, font->get_rids(), font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, xl_text)); dirty = false; lines_dirty = true; @@ -217,7 +220,7 @@ void Label::_update_visible() { minsize.height = 0; int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped); for (int64_t i = lines_skipped; i < last_line; i++) { - minsize.height += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM) + line_spacing; + minsize.height += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; if (minsize.height > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } @@ -286,7 +289,7 @@ void Label::_notification(int p_what) { // Get number of lines to fit to the height. for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { - total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM) + line_spacing; + total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } @@ -302,7 +305,7 @@ void Label::_notification(int p_what) { // Get real total height. total_h = 0; for (int64_t i = lines_skipped; i < last_line; i++) { - total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM) + line_spacing; + total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; } total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM); @@ -357,7 +360,7 @@ void Label::_notification(int p_what) { for (int i = lines_skipped; i < last_line; i++) { Size2 line_size = TS->shaped_text_get_size(lines_rid[i]); ofs.x = 0; - ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + font->get_spacing(Font::SPACING_TOP); + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + font->get_spacing(TextServer::SPACING_TOP); switch (align) { case ALIGN_FILL: if (rtl && autowrap_mode != AUTOWRAP_OFF) { @@ -433,7 +436,7 @@ void Label::_notification(int p_what) { } } } - ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing + font->get_spacing(Font::SPACING_BOTTOM); + ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing + font->get_spacing(TextServer::SPACING_BOTTOM); } } @@ -455,7 +458,7 @@ Size2 Label::get_minimum_size() const { Size2 min_size = minsize; Ref<Font> font = get_theme_font(SNAME("font")); - min_size.height = MAX(min_size.height, font->get_height(get_theme_font_size(SNAME("font_size"))) + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM)); + min_size.height = MAX(min_size.height, font->get_height(get_theme_font_size(SNAME("font_size"))) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM)); Size2 min_style = get_theme_stylebox(SNAME("normal"))->get_minimum_size(); if (autowrap_mode != AUTOWRAP_OFF) { @@ -486,7 +489,7 @@ int Label::get_visible_line_count() const { int lines_visible = 0; float total_h = 0.0; for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { - total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM) + line_spacing; + total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 11e08b231e..d9acbeb828 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -216,7 +216,7 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) { } } -void LineEdit::_gui_input(Ref<InputEvent> p_event) { +void LineEdit::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseButton> b = p_event; @@ -641,7 +641,7 @@ void LineEdit::_notification(int p_what) { int x_ofs = 0; bool using_placeholder = text.is_empty() && ime_text.is_empty(); float text_width = TS->shaped_text_get_size(text_rid).x; - float text_height = TS->shaped_text_get_size(text_rid).y + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM); + float text_height = TS->shaped_text_get_size(text_rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); switch (align) { case ALIGN_FILL: @@ -674,7 +674,7 @@ void LineEdit::_notification(int p_what) { int y_ofs = style->get_offset().y + (y_area - text_height) / 2; Color selection_color = get_theme_color(SNAME("selection_color")); - Color font_color = is_editable() ? get_theme_color(SNAME("font_color")) : get_theme_color(SNAME("font_uneditable_color")); + Color font_color = get_theme_color(is_editable() ? SNAME("font_color") : SNAME("font_uneditable_color")); Color font_selected_color = get_theme_color(SNAME("font_selected_color")); Color caret_color = get_theme_color(SNAME("caret_color")); @@ -1531,7 +1531,7 @@ Size2 LineEdit::get_minimum_size() const { min_size.width = MAX(min_size.width, full_width + em_space_size); } - min_size.height = MAX(TS->shaped_text_get_size(text_rid).y + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM), font->get_height(font_size)); + min_size.height = MAX(TS->shaped_text_get_size(text_rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM), font->get_height(font_size)); // Take icons into account. bool using_placeholder = text.is_empty() && ime_text.is_empty(); @@ -1941,6 +1941,7 @@ void LineEdit::_shape() { const Ref<Font> &font = get_theme_font(SNAME("font")); int font_size = get_theme_font_size(SNAME("font_size")); + ERR_FAIL_COND(font.is_null()); TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, t)); @@ -2084,7 +2085,6 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_align", "align"), &LineEdit::set_align); ClassDB::bind_method(D_METHOD("get_align"), &LineEdit::get_align); - ClassDB::bind_method(D_METHOD("_gui_input"), &LineEdit::_gui_input); ClassDB::bind_method(D_METHOD("clear"), &LineEdit::clear); ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("select_all"), &LineEdit::select_all); @@ -2220,7 +2220,7 @@ void LineEdit::_bind_methods() { void LineEdit::_ensure_menu() { if (!menu) { menu = memnew(PopupMenu); - add_child(menu); + add_child(menu, false, INTERNAL_MODE_FRONT); menu_dir = memnew(PopupMenu); menu_dir->set_name("DirMenu"); @@ -2305,7 +2305,7 @@ LineEdit::LineEdit() { set_mouse_filter(MOUSE_FILTER_STOP); caret_blink_timer = memnew(Timer); - add_child(caret_blink_timer); + add_child(caret_blink_timer, false, INTERNAL_MODE_FRONT); caret_blink_timer->set_wait_time(0.65); caret_blink_timer->connect("timeout", callable_mp(this, &LineEdit::_toggle_draw_caret)); set_caret_blink_enabled(false); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 0e9c032e88..e364a79c83 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -202,7 +202,7 @@ private: protected: void _notification(int p_what); static void _bind_methods(); - void _gui_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index 419d49bccf..925e6f5b97 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -41,12 +41,16 @@ void LinkButton::_shape() { } else { text_buf->set_direction((TextServer::Direction)text_direction); } - TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, text)); - text_buf->add_string(text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, xl_text)); + text_buf->add_string(xl_text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); } void LinkButton::set_text(const String &p_text) { + if (text == p_text) { + return; + } text = p_text; + xl_text = atr(text); _shape(); minimum_size_changed(); update(); @@ -141,7 +145,13 @@ Size2 LinkButton::get_minimum_size() const { void LinkButton::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: { + xl_text = atr(text); + _shape(); + + minimum_size_changed(); + update(); + } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { update(); } break; diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h index 7eaa9f88b6..231543c63c 100644 --- a/scene/gui/link_button.h +++ b/scene/gui/link_button.h @@ -47,6 +47,7 @@ public: private: String text; + String xl_text; Ref<TextLine> text_buf; UnderlineMode underline_mode = UNDERLINE_MODE_ALWAYS; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index dc6c7fec28..737ba84617 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -33,7 +33,7 @@ #include "core/os/keyboard.h" #include "scene/main/window.h" -void MenuButton::_unhandled_key_input(Ref<InputEvent> p_event) { +void MenuButton::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!_is_focus_owner_in_shorcut_context()) { @@ -100,8 +100,8 @@ void MenuButton::pressed() { popup->popup(); } -void MenuButton::_gui_input(Ref<InputEvent> p_event) { - BaseButton::_gui_input(p_event); +void MenuButton::gui_input(const Ref<InputEvent> &p_event) { + BaseButton::gui_input(p_event); } PopupMenu *MenuButton::get_popup() const { @@ -172,7 +172,7 @@ MenuButton::MenuButton() { popup = memnew(PopupMenu); popup->hide(); - add_child(popup); + add_child(popup, false, INTERNAL_MODE_FRONT); popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(true)); popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(false)); } diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index cc2ca117c4..730495b65d 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -47,14 +47,14 @@ class MenuButton : public Button { Array _get_items() const; void _set_items(const Array &p_items); - void _gui_input(Ref<InputEvent> p_event) override; + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _popup_visibility_changed(bool p_visible); protected: void _notification(int p_what); static void _bind_methods(); - virtual void _unhandled_key_input(Ref<InputEvent> p_event) override; + virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; public: virtual void pressed() override; diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index cd55f258b3..d16e96dbec 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -351,7 +351,7 @@ OptionButton::OptionButton() { popup = memnew(PopupMenu); popup->hide(); - add_child(popup); + add_child(popup, false, INTERNAL_MODE_FRONT); popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected)); popup->connect("id_focused", callable_mp(this, &OptionButton::_focused)); popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(false)); diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index d846e395ad..953337ecce 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -56,6 +56,10 @@ protected: static void _bind_methods(); public: + // ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes, + // this value should be updated to reflect the new size. + static const int ITEM_PROPERTY_SIZE = 5; + void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1); void add_item(const String &p_label, int p_id = -1); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index f7e7e1cd60..e9414598a2 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -255,5 +255,5 @@ void PopupPanel::_notification(int p_what) { PopupPanel::PopupPanel() { panel = memnew(Panel); - add_child(panel); + add_child(panel, false, INTERNAL_MODE_FRONT); } diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index aff367e398..c0a559e624 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -228,6 +228,7 @@ void PopupMenu::_activate_submenu(int p_over) { // Set autohide areas PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup); if (submenu_pum) { + submenu_pum->take_mouse_focus(); // Make the position of the parent popup relative to submenu popup this_rect.position = this_rect.position - submenu_pum->get_position(); @@ -251,7 +252,7 @@ void PopupMenu::_submenu_timeout() { submenu_over = -1; } -void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { +void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (p_event->is_action("ui_down") && p_event->is_pressed()) { @@ -1581,8 +1582,6 @@ void PopupMenu::take_mouse_focus() { } void PopupMenu::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &PopupMenu::_gui_input); - ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0)); @@ -1691,7 +1690,7 @@ PopupMenu::PopupMenu() { // Margin Container margin_container = memnew(MarginContainer); margin_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - add_child(margin_container); + add_child(margin_container, false, INTERNAL_MODE_FRONT); margin_container->connect("draw", callable_mp(this, &PopupMenu::_draw_background)); // Scroll Container @@ -1705,22 +1704,22 @@ PopupMenu::PopupMenu() { control->set_anchors_and_offsets_preset(Control::PRESET_WIDE); control->set_h_size_flags(Control::SIZE_EXPAND_FILL); control->set_v_size_flags(Control::SIZE_EXPAND_FILL); - scroll_container->add_child(control); + scroll_container->add_child(control, false, INTERNAL_MODE_FRONT); control->connect("draw", callable_mp(this, &PopupMenu::_draw_items)); - connect("window_input", callable_mp(this, &PopupMenu::_gui_input)); + connect("window_input", callable_mp(this, &PopupMenu::gui_input)); submenu_timer = memnew(Timer); submenu_timer->set_wait_time(0.3); submenu_timer->set_one_shot(true); submenu_timer->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout)); - add_child(submenu_timer); + add_child(submenu_timer, false, INTERNAL_MODE_FRONT); minimum_lifetime_timer = memnew(Timer); minimum_lifetime_timer->set_wait_time(0.3); minimum_lifetime_timer->set_one_shot(true); minimum_lifetime_timer->connect("timeout", callable_mp(this, &PopupMenu::_minimum_lifetime_timeout)); - add_child(minimum_lifetime_timer); + add_child(minimum_lifetime_timer, false, INTERNAL_MODE_FRONT); } PopupMenu::~PopupMenu() { diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index aedc5d0155..428076c6da 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -31,10 +31,10 @@ #ifndef POPUP_MENU_H #define POPUP_MENU_H +#include "core/input/shortcut.h" #include "scene/gui/margin_container.h" #include "scene/gui/popup.h" #include "scene/gui/scroll_container.h" -#include "scene/gui/shortcut.h" #include "scene/resources/text_line.h" class PopupMenu : public Popup { @@ -107,7 +107,7 @@ class PopupMenu : public Popup { void _shape_item(int p_item); - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event); void _activate_submenu(int p_over); void _submenu_timeout(); @@ -144,6 +144,10 @@ protected: static void _bind_methods(); public: + // ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes, + // this value should be updated to reflect the new size. + static const int ITEM_PROPERTY_SIZE = 10; + void add_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); diff --git a/scene/gui/rich_text_effect.h b/scene/gui/rich_text_effect.h index e674b2f62f..f5506542bb 100644 --- a/scene/gui/rich_text_effect.h +++ b/scene/gui/rich_text_effect.h @@ -49,7 +49,7 @@ public: Color color; double elapsed_time = 0.0f; Dictionary environment; - uint32_t glpyh_index = 0; + uint32_t glyph_index = 0; RID font; CharFXTransform(); @@ -68,8 +68,8 @@ public: Color get_color() { return color; } void set_color(Color p_color) { color = p_color; } - uint32_t get_glyph_index() const { return glpyh_index; }; - void set_glyph_index(uint32_t p_glpyh_index) { glpyh_index = p_glpyh_index; }; + uint32_t get_glyph_index() const { return glyph_index; }; + void set_glyph_index(uint32_t p_glyph_index) { glyph_index = p_glyph_index; }; RID get_font() const { return font; }; void set_font(RID p_font) { font = p_font; }; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 75c2da7ef9..aeadfd78ee 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -874,7 +874,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o charfx->visibility = visible; charfx->outline = true; charfx->font = frid; - charfx->glpyh_index = gl; + charfx->glyph_index = gl; charfx->offset = fx_offset; charfx->color = font_color; @@ -884,7 +884,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o fx_offset += charfx->offset; font_color = charfx->color; frid = charfx->font; - gl = charfx->glpyh_index; + gl = charfx->glyph_index; visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { @@ -1026,7 +1026,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o charfx->visibility = visible; charfx->outline = false; charfx->font = frid; - charfx->glpyh_index = gl; + charfx->glyph_index = gl; charfx->offset = fx_offset; charfx->color = font_color; @@ -1036,7 +1036,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o fx_offset += charfx->offset; font_color = charfx->color; frid = charfx->font; - gl = charfx->glpyh_index; + gl = charfx->glyph_index; visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { @@ -1451,7 +1451,7 @@ void RichTextLabel::_notification(int p_what) { Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const { if (!underline_meta) { - return CURSOR_ARROW; + return get_default_cursor_shape(); } if (selection.click_item) { @@ -1459,11 +1459,11 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const } if (main->first_invalid_line < main->lines.size()) { - return CURSOR_ARROW; //invalid + return get_default_cursor_shape(); //invalid } if (main->first_resized_line < main->lines.size()) { - return CURSOR_ARROW; //invalid + return get_default_cursor_shape(); //invalid } Item *item = nullptr; @@ -1474,10 +1474,10 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const return CURSOR_POINTING_HAND; } - return CURSOR_ARROW; + return get_default_cursor_shape(); } -void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { +void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseButton> b = p_event; @@ -3946,24 +3946,15 @@ float RichTextLabel::get_percent_visible() const { return percent_visible; } -void RichTextLabel::set_effects(const Vector<Variant> &effects) { - custom_effects.clear(); - for (int i = 0; i < effects.size(); i++) { - Ref<RichTextEffect> effect = Ref<RichTextEffect>(effects[i]); - custom_effects.push_back(effect); - } - +void RichTextLabel::set_effects(Array p_effects) { + custom_effects = p_effects; if ((bbcode != "") && use_bbcode) { parse_bbcode(bbcode); } } -Vector<Variant> RichTextLabel::get_effects() { - Vector<Variant> r; - for (int i = 0; i < custom_effects.size(); i++) { - r.push_back(custom_effects[i]); - } - return r; +Array RichTextLabel::get_effects() { + return custom_effects; } void RichTextLabel::install_effect(const Variant effect) { @@ -3993,7 +3984,6 @@ void RichTextLabel::_validate_property(PropertyInfo &property) const { } void RichTextLabel::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &RichTextLabel::_gui_input); ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text); ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text); ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text); @@ -4280,12 +4270,13 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_identifier) { for (int i = 0; i < custom_effects.size(); i++) { - if (!custom_effects[i].is_valid()) { + Ref<RichTextEffect> effect = custom_effects[i]; + if (!effect.is_valid()) { continue; } - if (custom_effects[i]->get_bbcode() == p_bbcode_identifier) { - return custom_effects[i]; + if (effect->get_bbcode() == p_bbcode_identifier) { + return effect; } } @@ -4362,7 +4353,7 @@ RichTextLabel::RichTextLabel() { current_frame = main; vscroll = memnew(VScrollBar); - add_child(vscroll); + add_child(vscroll, false, INTERNAL_MODE_FRONT); vscroll->set_drag_node(String("..")); vscroll->set_step(1); vscroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 28dfe74b08..f25a8bf193 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -363,7 +363,7 @@ private: ItemMeta *meta_hovering = nullptr; Variant current_meta; - Vector<Ref<RichTextEffect>> custom_effects; + Array custom_effects; void _invalidate_current_line(ItemFrame *p_frame); void _validate_line_caches(ItemFrame *p_frame); @@ -443,7 +443,7 @@ private: void _update_fx(ItemFrame *p_frame, double p_delta_time); void _scroll_changed(double); - void _gui_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; Item *_get_next_item(Item *p_item, bool p_free = false) const; Item *_get_prev_item(Item *p_item, bool p_free = false) const; @@ -577,8 +577,8 @@ public: void set_percent_visible(float p_percent); float get_percent_visible() const; - void set_effects(const Vector<Variant> &effects); - Vector<Variant> get_effects(); + void set_effects(Array p_effects); + Array get_effects(); void install_effect(const Variant effect); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index ce04a0204b..08bcb0bdda 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -41,7 +41,7 @@ void ScrollBar::set_can_focus_by_default(bool p_can_focus) { focus_by_default = p_can_focus; } -void ScrollBar::_gui_input(Ref<InputEvent> p_event) { +void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> m = p_event; @@ -597,7 +597,6 @@ bool ScrollBar::is_smooth_scroll_enabled() const { } void ScrollBar::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &ScrollBar::_gui_input); ClassDB::bind_method(D_METHOD("set_custom_step", "step"), &ScrollBar::set_custom_step); ClassDB::bind_method(D_METHOD("get_custom_step"), &ScrollBar::get_custom_step); diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index 24b3b33e82..fbc035397f 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -86,7 +86,7 @@ class ScrollBar : public Range { void _drag_node_exit(); void _drag_node_input(const Ref<InputEvent> &p_input); - void _gui_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; protected: void _notification(int p_what); diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index eb5fc924da..0c051f61e2 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -83,7 +83,7 @@ void ScrollContainer::_cancel_drag() { } } -void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { +void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { ERR_FAIL_COND(p_gui_input.is_null()); double prev_v_scroll = v_scroll->get_value(); @@ -228,9 +228,6 @@ void ScrollContainer::_update_scrollbar_position() { v_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0); v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); - h_scroll->raise(); - v_scroll->raise(); - _updating_scrollbars = false; } @@ -568,7 +565,6 @@ VScrollBar *ScrollContainer::get_v_scrollbar() { } void ScrollContainer::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &ScrollContainer::_gui_input); ClassDB::bind_method(D_METHOD("_update_scrollbar_position"), &ScrollContainer::_update_scrollbar_position); ClassDB::bind_method(D_METHOD("set_h_scroll", "value"), &ScrollContainer::set_h_scroll); @@ -619,12 +615,12 @@ void ScrollContainer::_bind_methods() { ScrollContainer::ScrollContainer() { h_scroll = memnew(HScrollBar); h_scroll->set_name("_h_scroll"); - add_child(h_scroll); + add_child(h_scroll, false, INTERNAL_MODE_BACK); h_scroll->connect("value_changed", callable_mp(this, &ScrollContainer::_scroll_moved)); v_scroll = memnew(VScrollBar); v_scroll->set_name("_v_scroll"); - add_child(v_scroll); + add_child(v_scroll, false, INTERNAL_MODE_BACK); v_scroll->connect("value_changed", callable_mp(this, &ScrollContainer::_scroll_moved)); deadzone = GLOBAL_GET("gui/common/default_scroll_deadzone"); diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index 4733fdabca..9f4ec558dc 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -68,7 +68,7 @@ class ScrollContainer : public Container { protected: Size2 get_minimum_size() const override; - void _gui_input(const Ref<InputEvent> &p_gui_input); + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; void _gui_focus_changed(Control *p_control); void _update_dimensions(); void _notification(int p_what); diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 61b5325175..352f87954e 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -45,7 +45,7 @@ Size2 Slider::get_minimum_size() const { } } -void Slider::_gui_input(Ref<InputEvent> p_event) { +void Slider::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!editable) { @@ -253,7 +253,6 @@ bool Slider::is_scrollable() const { } void Slider::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &Slider::_gui_input); ClassDB::bind_method(D_METHOD("set_ticks", "count"), &Slider::set_ticks); ClassDB::bind_method(D_METHOD("get_ticks"), &Slider::get_ticks); diff --git a/scene/gui/slider.h b/scene/gui/slider.h index 65a4036cd1..46fa08bbf0 100644 --- a/scene/gui/slider.h +++ b/scene/gui/slider.h @@ -50,7 +50,7 @@ class Slider : public Range { bool scrollable = true; protected: - void _gui_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); static void _bind_methods(); bool ticks_on_borders = false; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 3f0368a4e2..1074d0d8a0 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -99,7 +99,7 @@ void SpinBox::_release_mouse() { } } -void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { +void SpinBox::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!is_editable()) { @@ -258,7 +258,7 @@ void SpinBox::apply() { void SpinBox::_bind_methods() { //ClassDB::bind_method(D_METHOD("_value_changed"),&SpinBox::_value_changed); - ClassDB::bind_method(D_METHOD("_gui_input"), &SpinBox::_gui_input); + ClassDB::bind_method(D_METHOD("set_align", "align"), &SpinBox::set_align); ClassDB::bind_method(D_METHOD("get_align"), &SpinBox::get_align); ClassDB::bind_method(D_METHOD("set_suffix", "suffix"), &SpinBox::set_suffix); @@ -278,7 +278,7 @@ void SpinBox::_bind_methods() { SpinBox::SpinBox() { line_edit = memnew(LineEdit); - add_child(line_edit); + add_child(line_edit, false, INTERNAL_MODE_FRONT); line_edit->set_anchors_and_offsets_preset(Control::PRESET_WIDE); line_edit->set_mouse_filter(MOUSE_FILTER_PASS); @@ -291,5 +291,5 @@ SpinBox::SpinBox() { range_click_timer = memnew(Timer); range_click_timer->connect("timeout", callable_mp(this, &SpinBox::_range_click_timeout)); - add_child(range_click_timer); + add_child(range_click_timer, false, INTERNAL_MODE_FRONT); } diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index fb10379296..9ec3885f1f 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -65,7 +65,7 @@ class SpinBox : public Range { inline void _adjust_width_for_icon(const Ref<Texture2D> &icon); protected: - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 3114e5b7c0..4736a1ad37 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -206,7 +206,7 @@ void SplitContainer::_notification(int p_what) { } } -void SplitContainer::_gui_input(const Ref<InputEvent> &p_event) { +void SplitContainer::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (collapsed || !_getch(0) || !_getch(1) || dragger_visibility != DRAGGER_VISIBLE) { @@ -337,8 +337,6 @@ bool SplitContainer::is_collapsed() const { } void SplitContainer::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &SplitContainer::_gui_input); - ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::set_split_offset); ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::get_split_offset); ClassDB::bind_method(D_METHOD("clamp_split_offset"), &SplitContainer::clamp_split_offset); diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index 6cb94d6ecf..47fd30a122 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -60,7 +60,7 @@ private: void _resort(); protected: - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index bfc7e29f9c..53ea32e1b7 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -139,7 +139,7 @@ void SubViewportContainer::_notification(int p_what) { } } -void SubViewportContainer::_input(const Ref<InputEvent> &p_event) { +void SubViewportContainer::input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (Engine::get_singleton()->is_editor_hint()) { @@ -162,11 +162,11 @@ void SubViewportContainer::_input(const Ref<InputEvent> &p_event) { continue; } - c->input(ev); + c->push_input(ev); } } -void SubViewportContainer::_unhandled_input(const Ref<InputEvent> &p_event) { +void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (Engine::get_singleton()->is_editor_hint()) { @@ -189,13 +189,11 @@ void SubViewportContainer::_unhandled_input(const Ref<InputEvent> &p_event) { continue; } - c->unhandled_input(ev); + c->push_unhandled_input(ev); } } void SubViewportContainer::_bind_methods() { - ClassDB::bind_method(D_METHOD("_unhandled_input", "event"), &SubViewportContainer::_unhandled_input); - ClassDB::bind_method(D_METHOD("_input", "event"), &SubViewportContainer::_input); ClassDB::bind_method(D_METHOD("set_stretch", "enable"), &SubViewportContainer::set_stretch); ClassDB::bind_method(D_METHOD("is_stretch_enabled"), &SubViewportContainer::is_stretch_enabled); diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h index 77cf4c16b3..7853f1590e 100644 --- a/scene/gui/subviewport_container.h +++ b/scene/gui/subviewport_container.h @@ -47,8 +47,8 @@ public: void set_stretch(bool p_enable); bool is_stretch_enabled() const; - void _input(const Ref<InputEvent> &p_event); - void _unhandled_input(const Ref<InputEvent> &p_event); + virtual void input(const Ref<InputEvent> &p_event) override; + virtual void unhandled_input(const Ref<InputEvent> &p_event) override; void set_stretch_shrink(int p_shrink); int get_stretch_shrink() const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 53a2fe4d93..137ce7e96f 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -71,7 +71,7 @@ int TabContainer::_get_top_margin() const { return tab_height + content_height; } -void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { +void TabContainer::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseButton> mb = p_event; @@ -434,14 +434,14 @@ void TabContainer::_notification(int p_what) { } int tab_width = tab_widths[i]; - if (get_tab_disabled(index)) { + if (index == current) { + x_current = x; + } else if (get_tab_disabled(index)) { if (rtl) { _draw_tab(tab_disabled, font_disabled_color, index, size.width - (tabs_ofs_cache + x) - tab_width); } else { _draw_tab(tab_disabled, font_disabled_color, index, tabs_ofs_cache + x); } - } else if (index == current) { - x_current = x; } else { if (rtl) { _draw_tab(tab_unselected, font_unselected_color, index, size.width - (tabs_ofs_cache + x) - tab_width); @@ -459,12 +459,13 @@ void TabContainer::_notification(int p_what) { panel->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height)); } - // Draw selected tab in front. only draw selected tab when it's in visible range. + // Draw selected tab in front. Only draw selected tab when it's in visible range. if (tabs.size() > 0 && current - first_tab_cache < tab_widths.size() && current >= first_tab_cache) { + Ref<StyleBox> current_style_box = get_tab_disabled(current) ? tab_disabled : tab_selected; if (rtl) { - _draw_tab(tab_selected, font_selected_color, current, size.width - (tabs_ofs_cache + x_current) - tab_widths[current]); + _draw_tab(current_style_box, font_selected_color, current, size.width - (tabs_ofs_cache + x_current) - tab_widths[current]); } else { - _draw_tab(tab_selected, font_selected_color, current, tabs_ofs_cache + x_current); + _draw_tab(current_style_box, font_selected_color, current, tabs_ofs_cache + x_current); } } @@ -640,8 +641,8 @@ void TabContainer::_on_mouse_exited() { int TabContainer::_get_tab_width(int p_index) const { ERR_FAIL_INDEX_V(p_index, get_tab_count(), 0); - Control *control = Object::cast_to<Control>(_get_tabs()[p_index]); - if (!control || control->is_set_as_top_level() || get_tab_hidden(p_index)) { + Control *control = get_tab_control(p_index); + if (!control || get_tab_hidden(p_index)) { return 0; } @@ -905,7 +906,7 @@ void TabContainer::drop_data(const Point2 &p_point, const Variant &p_data) { if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { Control *moving_tabc = from_tabc->get_tab_control(tab_from_id); from_tabc->remove_child(moving_tabc); - add_child(moving_tabc); + add_child(moving_tabc, false, INTERNAL_MODE_FRONT); if (hover_now < 0) { hover_now = get_tab_count() - 1; } @@ -1193,7 +1194,6 @@ bool TabContainer::get_use_hidden_tabs_for_min_size() const { } void TabContainer::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &TabContainer::_gui_input); ClassDB::bind_method(D_METHOD("get_tab_count"), &TabContainer::get_tab_count); ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &TabContainer::set_current_tab); ClassDB::bind_method(D_METHOD("get_current_tab"), &TabContainer::get_current_tab); @@ -1212,6 +1212,7 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled); ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabContainer::get_tab_disabled); + ClassDB::bind_method(D_METHOD("get_tab_idx_at_point", "point"), &TabContainer::get_tab_idx_at_point); ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup); ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup); ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabContainer::set_drag_to_rearrange_enabled); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 35f18eff8e..fe96df25e8 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -76,7 +76,7 @@ private: protected: void _child_renamed_callback(); - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); virtual void add_child_notify(Node *p_child) override; virtual void move_child_notify(Node *p_child) override; diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index 103860ad78..3ca2d1c1e9 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -90,7 +90,7 @@ Size2 Tabs::get_minimum_size() const { return ms; } -void Tabs::_gui_input(const Ref<InputEvent> &p_event) { +void Tabs::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> mm = p_event; @@ -1107,7 +1107,6 @@ bool Tabs::get_select_with_rmb() const { } void Tabs::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &Tabs::_gui_input); ClassDB::bind_method(D_METHOD("_update_hover"), &Tabs::_update_hover); ClassDB::bind_method(D_METHOD("get_tab_count"), &Tabs::get_tab_count); ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &Tabs::set_current_tab); diff --git a/scene/gui/tabs.h b/scene/gui/tabs.h index 61c9a5d96a..b044453803 100644 --- a/scene/gui/tabs.h +++ b/scene/gui/tabs.h @@ -112,7 +112,7 @@ private: void _shape(int p_tab); protected: - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index a65abd5f49..f64c07df76 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -37,6 +37,7 @@ #include "core/object/script_language.h" #include "core/os/keyboard.h" #include "core/os/os.h" +#include "core/string/string_builder.h" #include "core/string/translation.h" #include "scene/main/window.h" @@ -78,7 +79,11 @@ void TextEdit::Text::set_font_size(int p_font_size) { } void TextEdit::Text::set_tab_size(int p_tab_size) { + if (tab_size == p_tab_size) { + return; + } tab_size = p_tab_size; + tab_size_dirty = true; } int TextEdit::Text::get_tab_size() const { @@ -118,10 +123,8 @@ int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const { return text[p_line].data_buf->get_size().x; } -int TextEdit::Text::get_line_height(int p_line, int p_wrap_index) const { - ERR_FAIL_INDEX_V(p_line, text.size(), 0); - - return text[p_line].data_buf->get_line_size(p_wrap_index).y; +int TextEdit::Text::get_line_height() const { + return line_height; } void TextEdit::Text::set_width(float p_width) { @@ -153,6 +156,36 @@ _FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const { return text[p_line].data; } +void TextEdit::Text::_calculate_line_height() { + int height = 0; + for (int i = 0; i < text.size(); i++) { + // Found another line with the same height...nothing to update. + if (text[i].height == line_height) { + height = line_height; + break; + } + height = MAX(height, text[i].height); + } + line_height = height; +} + +void TextEdit::Text::_calculate_max_line_width() { + int width = 0; + for (int i = 0; i < text.size(); i++) { + if (is_hidden(i)) { + continue; + } + + // Found another line with the same width...nothing to update. + if (text[i].width == max_width) { + width = max_width; + break; + } + width = MAX(width, text[i].width); + } + max_width = width; +} + void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ime_text, const Vector<Vector2i> &p_bidi_override) { ERR_FAIL_INDEX(p_line, text.size()); @@ -182,17 +215,54 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); text.write[p_line].data_buf->tab_align(tabs); } + + // Update height. + const int old_height = text.write[p_line].height; + const int wrap_amount = get_line_wrap_amount(p_line); + int height = font->get_height(font_size); + for (int i = 0; i <= wrap_amount; i++) { + height = MAX(height, text[p_line].data_buf->get_line_size(i).y); + } + text.write[p_line].height = height; + + // If this line has shrunk, this may no longer the the tallest line. + if (old_height == line_height && height < line_height) { + _calculate_line_height(); + } else { + line_height = MAX(height, line_height); + } + + // Update width. + const int old_width = text.write[p_line].width; + int width = get_line_width(p_line); + text.write[p_line].width = width; + + // If this line has shrunk, this may no longer the the longest line. + if (old_width == max_width && width < max_width) { + _calculate_max_line_width(); + } else if (!is_hidden(p_line)) { + max_width = MAX(width, max_width); + } } void TextEdit::Text::invalidate_all_lines() { for (int i = 0; i < text.size(); i++) { text.write[i].data_buf->set_width(width); - if (tab_size > 0) { - Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); - text.write[i].data_buf->tab_align(tabs); + if (tab_size_dirty) { + if (tab_size > 0) { + Vector<float> tabs; + tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); + text.write[i].data_buf->tab_align(tabs); + } + // Tabs have changes, force width update. + text.write[i].width = get_line_width(i); } } + + if (tab_size_dirty) { + _calculate_max_line_width(); + tab_size_dirty = false; + } } void TextEdit::Text::invalidate_all() { @@ -211,16 +281,8 @@ void TextEdit::Text::clear() { insert(0, "", Vector<Vector2i>()); } -int TextEdit::Text::get_max_width(bool p_exclude_hidden) const { - // Quite some work, but should be fast enough. - - int max = 0; - for (int i = 0; i < text.size(); i++) { - if (!p_exclude_hidden || !is_hidden(i)) { - max = MAX(max, get_line_width(i)); - } - } - return max; +int TextEdit::Text::get_max_width() const { + return max_width; } void TextEdit::Text::set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override) { @@ -243,7 +305,20 @@ void TextEdit::Text::insert(int p_at, const String &p_text, const Vector<Vector2 } void TextEdit::Text::remove(int p_at) { + int height = text[p_at].height; + int width = text[p_at].width; + text.remove(p_at); + + // If this is the tallest line, we need to get the next tallest. + if (height == line_height) { + _calculate_line_height(); + } + + // If this is the longest line, we need to get the next longest. + if (width == max_width) { + _calculate_max_line_width(); + } } void TextEdit::Text::add_gutter(int p_at) { @@ -293,7 +368,7 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { call_deferred(SNAME("_update_scrollbars")); - call_deferred(SNAME("_update_wrap_at")); + call_deferred(SNAME("_update_wrap_at_column")); } } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: @@ -557,13 +632,25 @@ void TextEdit::_notification(int p_what) { } int minimap_draw_amount = minimap_visible_lines + get_line_wrap_count(minimap_line + 1); - // draw the minimap - Color viewport_color = (background_color.get_v() < 0.5) ? Color(1, 1, 1, 0.1) : Color(0, 0, 0, 0.1); + // Draw the minimap. + + // Add visual feedback when dragging or hovering the the visible area rectangle. + float viewport_alpha; + if (dragging_minimap) { + viewport_alpha = 0.25; + } else if (hovering_minimap) { + viewport_alpha = 0.175; + } else { + viewport_alpha = 0.1; + } + + const Color viewport_color = (background_color.get_v() < 0.5) ? Color(1, 1, 1, viewport_alpha) : Color(0, 0, 0, viewport_alpha); if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, viewport_offset_y, minimap_width, viewport_height), viewport_color); } else { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, minimap_width, viewport_height), viewport_color); } + for (int i = 0; i < minimap_draw_amount; i++) { minimap_line++; @@ -632,8 +719,9 @@ void TextEdit::_notification(int p_what) { int characters = 0; int tabs = 0; for (int j = 0; j < str.length(); j++) { - if (color_map.has(last_wrap_column + j)) { - current_color = color_map[last_wrap_column + j].get("color"); + const Variant *color_data = color_map.getptr(last_wrap_column + j); + if (color_data != nullptr) { + current_color = (color_data->operator Dictionary()).get("color", font_color); if (!editable) { current_color.a = font_readonly_color.a; } @@ -888,7 +976,7 @@ void TextEdit::_notification(int p_what) { // Draw line. RID rid = ldata->get_line_rid(line_wrap_index); - float text_height = TS->shaped_text_get_size(rid).y + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM); + float text_height = TS->shaped_text_get_size(rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); if (rtl) { char_margin = size.width - char_margin - TS->shaped_text_get_size(rid).x; @@ -1011,8 +1099,9 @@ void TextEdit::_notification(int p_what) { char_ofs = 0; } for (int j = 0; j < gl_size; j++) { - if (color_map.has(glyphs[j].start)) { - current_color = color_map[glyphs[j].start].get("color"); + const Variant *color_data = color_map.getptr(glyphs[j].start); + if (color_data != nullptr) { + current_color = (color_data->operator Dictionary()).get("color", font_color); if (!editable && current_color.a > font_readonly_color.a) { current_color.a = font_readonly_color.a; } @@ -1305,7 +1394,7 @@ void TextEdit::_notification(int p_what) { } } -void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { +void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { ERR_FAIL_COND(p_gui_input.is_null()); double prev_v_scroll = v_scroll->get_value(); @@ -1549,6 +1638,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } + if (draw_minimap && !dragging_selection) { + _update_minimap_hover(); + } + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { accept_event(); // Accept event if scroll changed. } @@ -2150,7 +2243,6 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { next_column = column; } else { // Delete one character - next_column = caret.column < curline_len ? (caret.column + 1) : 0; if (caret_mid_grapheme_enabled) { next_column = caret.column < curline_len ? (caret.column + 1) : 0; } else { @@ -2533,15 +2625,15 @@ void TextEdit::set_text(const String &p_text) { } String TextEdit::get_text() const { - String longthing; - int len = text.size(); - for (int i = 0; i < len; i++) { - longthing += text[i]; - if (i != len - 1) { - longthing += "\n"; + StringBuilder ret_text; + const int text_size = text.size(); + for (int i = 0; i < text_size; i++) { + ret_text += text[i]; + if (i != text_size - 1) { + ret_text += "\n"; } } - return longthing; + return ret_text.as_string(); } int TextEdit::get_line_count() const { @@ -2577,13 +2669,7 @@ int TextEdit::get_line_width(int p_line, int p_wrap_index) const { } int TextEdit::get_line_height() const { - int height = font->get_height(font_size); - for (int i = 0; i < text.size(); i++) { - for (int j = 0; j <= text.get_line_wrap_amount(i); j++) { - height = MAX(height, text.get_line_height(i, j)); - } - } - return height + line_spacing; + return text.get_line_height() + line_spacing; } int TextEdit::get_indent_level(int p_line) const { @@ -4136,6 +4222,9 @@ TextEdit::GutterType TextEdit::get_gutter_type(int p_gutter) const { void TextEdit::set_gutter_width(int p_gutter, int p_width) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + if (gutters[p_gutter].width == p_width) { + return; + } gutters.write[p_gutter].width = p_width; _update_gutter_width(); } @@ -4151,6 +4240,9 @@ int TextEdit::get_total_gutter_width() const { void TextEdit::set_gutter_draw(int p_gutter, bool p_draw) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + if (gutters[p_gutter].draw == p_draw) { + return; + } gutters.write[p_gutter].draw = p_draw; _update_gutter_width(); } @@ -4367,7 +4459,7 @@ bool TextEdit::is_drawing_spaces() const { void TextEdit::_bind_methods() { /*Internal. */ - ClassDB::bind_method(D_METHOD("_gui_input"), &TextEdit::_gui_input); + ClassDB::bind_method(D_METHOD("_text_changed_emit"), &TextEdit::_text_changed_emit); /* Text */ @@ -5005,7 +5097,7 @@ void TextEdit::_paste_internal() { void TextEdit::_generate_context_menu() { if (!menu) { menu = memnew(PopupMenu); - add_child(menu); + add_child(menu, false, INTERNAL_MODE_FRONT); menu_dir = memnew(PopupMenu); menu_dir->set_name("DirMenu"); @@ -5013,7 +5105,7 @@ void TextEdit::_generate_context_menu() { menu_dir->add_radio_check_item(RTR("Auto-detect direction"), MENU_DIR_AUTO); menu_dir->add_radio_check_item(RTR("Left-to-right"), MENU_DIR_LTR); menu_dir->add_radio_check_item(RTR("Right-to-left"), MENU_DIR_RTL); - menu->add_child(menu_dir); + menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT); menu_ctl = memnew(PopupMenu); menu_ctl->set_name("CTLMenu"); @@ -5035,7 +5127,7 @@ void TextEdit::_generate_context_menu() { menu_ctl->add_item(RTR("Zero width non-joiner (ZWNJ)"), MENU_INSERT_ZWNJ); menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ); menu_ctl->add_item(RTR("Soft hyphen (SHY)"), MENU_INSERT_SHY); - menu->add_child(menu_ctl); + menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT); menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option)); menu_dir->connect("id_pressed", callable_mp(this, &TextEdit::menu_option)); @@ -5443,7 +5535,7 @@ void TextEdit::_update_scrollbars() { } int visible_width = size.width - style_normal->get_minimum_size().width; - int total_width = text.get_max_width(true) + vmin.x + gutters_width + gutter_padding; + int total_width = text.get_max_width() + vmin.x + gutters_width + gutter_padding; if (draw_minimap) { total_width += minimap_width; @@ -5645,6 +5737,33 @@ void TextEdit::_scroll_lines_down() { } // Minimap + +void TextEdit::_update_minimap_hover() { + const Point2 mp = get_local_mouse_pos(); + const int xmargin_end = get_size().width - style_normal->get_margin(SIDE_RIGHT); + + const bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end; + if (!hovering_sidebar) { + if (hovering_minimap) { + // Only redraw if the hovering status changed. + hovering_minimap = false; + update(); + } + + // Return early to avoid running the operations below when not needed. + return; + } + + const int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y)); + + const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line(); + if (new_hovering_minimap != hovering_minimap) { + // Only redraw if the hovering status changed. + hovering_minimap = new_hovering_minimap; + update(); + } +} + void TextEdit::_update_minimap_click() { Point2 mp = get_local_mouse_pos(); @@ -5868,8 +5987,6 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i text.set_hidden(p_line, false); } - text.invalidate_cache(p_line); - r_end_line = p_line + substrings.size() - 1; r_end_column = text[r_end_line].length() - postinsert_text.length(); @@ -5926,8 +6043,6 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li } text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text)); - text.invalidate_cache(p_from_line); - if (!text_changed_dirty && !setting_text) { if (is_inside_tree()) { MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); @@ -5944,13 +6059,12 @@ TextEdit::TextEdit() { set_default_cursor_shape(CURSOR_IBEAM); text.set_tab_size(text.get_tab_size()); - text.clear(); h_scroll = memnew(HScrollBar); v_scroll = memnew(VScrollBar); - add_child(h_scroll); - add_child(v_scroll); + add_child(h_scroll, false, INTERNAL_MODE_FRONT); + add_child(v_scroll, false, INTERNAL_MODE_FRONT); h_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved)); v_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved)); @@ -5959,19 +6073,19 @@ TextEdit::TextEdit() { /* Caret. */ caret_blink_timer = memnew(Timer); - add_child(caret_blink_timer); + add_child(caret_blink_timer, false, INTERNAL_MODE_FRONT); caret_blink_timer->set_wait_time(0.65); caret_blink_timer->connect("timeout", callable_mp(this, &TextEdit::_toggle_draw_caret)); set_caret_blink_enabled(false); /* Selection. */ click_select_held = memnew(Timer); - add_child(click_select_held); + add_child(click_select_held, false, INTERNAL_MODE_FRONT); click_select_held->set_wait_time(0.05); click_select_held->connect("timeout", callable_mp(this, &TextEdit::_click_selection_held)); idle_detect = memnew(Timer); - add_child(idle_detect); + add_child(idle_detect, false, INTERNAL_MODE_FRONT); idle_detect->set_one_shot(true); idle_detect->set_wait_time(GLOBAL_GET("gui/timers/text_edit_idle_detect_sec")); idle_detect->connect("timeout", callable_mp(this, &TextEdit::_push_current_op)); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 7d00ce14af..e996bba983 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -144,6 +144,8 @@ private: Color background_color = Color(0, 0, 0, 0); bool hidden = false; + int height = 0; + int width = 0; Line() { data_buf.instantiate(); @@ -152,6 +154,7 @@ private: private: bool is_dirty = false; + bool tab_size_dirty = false; mutable Vector<Line> text; Ref<Font> font; @@ -162,11 +165,16 @@ private: TextServer::Direction direction = TextServer::DIRECTION_AUTO; bool draw_control_chars = false; + int line_height = -1; + int max_width = -1; int width = -1; int tab_size = 4; int gutter_count = 0; + void _calculate_line_height(); + void _calculate_max_line_width(); + public: void set_tab_size(int p_tab_size); int get_tab_size() const; @@ -176,9 +184,9 @@ private: void set_direction_and_language(TextServer::Direction p_direction, const String &p_language); void set_draw_control_chars(bool p_draw_control_chars); - int get_line_height(int p_line, int p_wrap_index) const; + int get_line_height() const; int get_line_width(int p_line, int p_wrap_index = -1) const; - int get_max_width(bool p_exclude_hidden = false) const; + int get_max_width() const; void set_width(float p_width); int get_line_wrap_amount(int p_line) const; @@ -187,7 +195,14 @@ private: const Ref<TextParagraph> get_line_data(int p_line) const; void set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override); - void set_hidden(int p_line, bool p_hidden) { text.write[p_line].hidden = p_hidden; } + void set_hidden(int p_line, bool p_hidden) { + text.write[p_line].hidden = p_hidden; + if (!p_hidden && text[p_line].width > max_width) { + max_width = text[p_line].width; + } else if (p_hidden && text[p_line].width == max_width) { + _calculate_max_line_width(); + } + } bool is_hidden(int p_line) const { return text[p_line].hidden; } void insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override); void remove(int p_at); @@ -446,12 +461,14 @@ private: // minimap scroll bool minimap_clicked = false; + bool hovering_minimap = false; bool dragging_minimap = false; bool can_drag_minimap = false; double minimap_scroll_ratio = 0.0; double minimap_scroll_click_pos = 0.0; + void _update_minimap_hover(); void _update_minimap_click(); void _update_minimap_drag(); @@ -528,7 +545,7 @@ private: protected: void _notification(int p_what); - virtual void _gui_input(const Ref<InputEvent> &p_gui_input); + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; static void _bind_methods(); diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index 5e5dec3579..286f01ee33 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -100,6 +100,15 @@ Ref<Texture2D> TextureProgressBar::get_progress_texture() const { return progress; } +void TextureProgressBar::set_progress_offset(Point2 p_offset) { + progress_offset = p_offset; + update(); +} + +Point2 TextureProgressBar::get_progress_offset() const { + return progress_offset; +} + void TextureProgressBar::set_tint_under(const Color &p_tint) { tint_under = p_tint; update(); @@ -360,6 +369,9 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu } } + if (p_texture == progress) { + dst_rect.position += progress_offset; + } p_texture->get_rect_region(dst_rect, src_rect, dst_rect, src_rect); RID ci = get_canvas_item(); @@ -403,20 +415,24 @@ void TextureProgressBar::_notification(int p_what) { Size2 s = progress->get_size(); switch (mode) { case FILL_LEFT_TO_RIGHT: { - Rect2 region = Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset, Size2(s.x * get_as_ratio(), s.y)); + Rect2 source = Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_RIGHT_TO_LEFT: { - Rect2 region = Rect2(Point2(s.x - s.x * get_as_ratio(), 0), Size2(s.x * get_as_ratio(), s.y)); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset + Point2(s.x - s.x * get_as_ratio(), 0), Size2(s.x * get_as_ratio(), s.y)); + Rect2 source = Rect2(Point2(s.x - s.x * get_as_ratio(), 0), Size2(s.x * get_as_ratio(), s.y)); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_TOP_TO_BOTTOM: { - Rect2 region = Rect2(Point2(), Size2(s.x, s.y * get_as_ratio())); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset + Point2(), Size2(s.x, s.y * get_as_ratio())); + Rect2 source = Rect2(Point2(), Size2(s.x, s.y * get_as_ratio())); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_BOTTOM_TO_TOP: { - Rect2 region = Rect2(Point2(0, s.y - s.y * get_as_ratio()), Size2(s.x, s.y * get_as_ratio())); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset + Point2(0, s.y - s.y * get_as_ratio()), Size2(s.x, s.y * get_as_ratio())); + Rect2 source = Rect2(Point2(0, s.y - s.y * get_as_ratio()), Size2(s.x, s.y * get_as_ratio())); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_CLOCKWISE: case FILL_COUNTER_CLOCKWISE: @@ -427,8 +443,9 @@ void TextureProgressBar::_notification(int p_what) { float val = get_as_ratio() * rad_max_degrees / 360; if (val == 1) { - Rect2 region = Rect2(Point2(), s); - draw_texture_rect(progress, region, false, tint_progress); + Rect2 region = Rect2(progress_offset, s); + Rect2 source = Rect2(Point2(), s); + draw_texture_rect_region(progress, region, source, tint_progress); } else if (val != 0) { Array pts; float direction = mode == FILL_COUNTER_CLOCKWISE ? -1 : 1; @@ -454,14 +471,14 @@ void TextureProgressBar::_notification(int p_what) { Vector<Point2> uvs; Vector<Point2> points; uvs.push_back(get_relative_center()); - points.push_back(Point2(s.x * get_relative_center().x, s.y * get_relative_center().y)); + points.push_back(progress_offset + Point2(s.x * get_relative_center().x, s.y * get_relative_center().y)); for (int i = 0; i < pts.size(); i++) { Point2 uv = unit_val_to_uv(pts[i]); if (uvs.find(uv) >= 0) { continue; } uvs.push_back(uv); - points.push_back(Point2(uv.x * s.x, uv.y * s.y)); + points.push_back(progress_offset + Point2(uv.x * s.x, uv.y * s.y)); } Vector<Color> colors; colors.push_back(tint_progress); @@ -484,17 +501,19 @@ void TextureProgressBar::_notification(int p_what) { } } break; case FILL_BILINEAR_LEFT_AND_RIGHT: { - Rect2 region = Rect2(Point2(s.x / 2 - s.x * get_as_ratio() / 2, 0), Size2(s.x * get_as_ratio(), s.y)); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset + Point2(s.x / 2 - s.x * get_as_ratio() / 2, 0), Size2(s.x * get_as_ratio(), s.y)); + Rect2 source = Rect2(Point2(s.x / 2 - s.x * get_as_ratio() / 2, 0), Size2(s.x * get_as_ratio(), s.y)); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_BILINEAR_TOP_AND_BOTTOM: { - Rect2 region = Rect2(Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio())); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset + Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio())); + Rect2 source = Rect2(Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio())); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_MODE_MAX: break; default: - draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress); + draw_texture_rect_region(progress, Rect2(progress_offset, Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress); } } if (over.is_valid()) { @@ -585,6 +604,9 @@ void TextureProgressBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tint_over", "tint"), &TextureProgressBar::set_tint_over); ClassDB::bind_method(D_METHOD("get_tint_over"), &TextureProgressBar::get_tint_over); + ClassDB::bind_method(D_METHOD("set_texture_progress_offset", "offset"), &TextureProgressBar::set_progress_offset); + ClassDB::bind_method(D_METHOD("get_texture_progress_offset"), &TextureProgressBar::get_progress_offset); + ClassDB::bind_method(D_METHOD("set_radial_initial_angle", "mode"), &TextureProgressBar::set_radial_initial_angle); ClassDB::bind_method(D_METHOD("get_radial_initial_angle"), &TextureProgressBar::get_radial_initial_angle); @@ -604,6 +626,7 @@ void TextureProgressBar::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_under", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_under_texture", "get_under_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_over_texture", "get_over_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_progress_texture", "get_progress_texture"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_progress_offset"), "set_texture_progress_offset", "get_texture_progress_offset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode"); ADD_GROUP("Tint", "tint_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under"), "set_tint_under", "get_tint_under"); diff --git a/scene/gui/texture_progress_bar.h b/scene/gui/texture_progress_bar.h index d147c43a26..c508f41387 100644 --- a/scene/gui/texture_progress_bar.h +++ b/scene/gui/texture_progress_bar.h @@ -61,6 +61,9 @@ public: void set_fill_mode(int p_fill); int get_fill_mode(); + void set_progress_offset(Point2 p_offset); + Point2 get_progress_offset() const; + void set_radial_initial_angle(float p_angle); float get_radial_initial_angle(); @@ -100,6 +103,7 @@ public: private: FillMode mode = FILL_LEFT_TO_RIGHT; + Point2 progress_offset; float rad_init_angle = 0.0; float rad_max_degrees = 360.0; Point2 rad_center_off; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index d9892b53fc..cb990892ed 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -889,11 +889,22 @@ void TreeItem::set_custom_font(int p_column, const Ref<Font> &p_font) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].custom_font = p_font; } + Ref<Font> TreeItem::get_custom_font(int p_column) const { ERR_FAIL_INDEX_V(p_column, cells.size(), Ref<Font>()); return cells[p_column].custom_font; } +void TreeItem::set_custom_font_size(int p_column, int p_font_size) { + ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].custom_font_size = p_font_size; +} + +int TreeItem::get_custom_font_size(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), -1); + return cells[p_column].custom_font_size; +} + void TreeItem::set_tooltip(int p_column, const String &p_tooltip) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].tooltip = p_tooltip; @@ -1132,6 +1143,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_custom_font", "column", "font"), &TreeItem::set_custom_font); ClassDB::bind_method(D_METHOD("get_custom_font", "column"), &TreeItem::get_custom_font); + ClassDB::bind_method(D_METHOD("set_custom_font_size", "column", "font_size"), &TreeItem::set_custom_font_size); + ClassDB::bind_method(D_METHOD("get_custom_font_size", "column"), &TreeItem::get_custom_font_size); + ClassDB::bind_method(D_METHOD("set_custom_bg_color", "column", "color", "just_outline"), &TreeItem::set_custom_bg_color, DEFVAL(false)); ClassDB::bind_method(D_METHOD("clear_custom_bg_color", "column"), &TreeItem::clear_custom_bg_color); ClassDB::bind_method(D_METHOD("get_custom_bg_color", "column"), &TreeItem::get_custom_bg_color); @@ -1507,7 +1521,14 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { } else { font = cache.font; } - p_item->cells.write[p_col].text_buf->add_string(valtext, font, cache.font_size, p_item->cells[p_col].opentype_features, (p_item->cells[p_col].language != "") ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); + + int font_size; + if (p_item->cells[p_col].custom_font_size > 0) { + font_size = p_item->cells[p_col].custom_font_size; + } else { + font_size = cache.font_size; + } + p_item->cells.write[p_col].text_buf->add_string(valtext, font, font_size, p_item->cells[p_col].opentype_features, (p_item->cells[p_col].language != "") ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); TS->shaped_text_set_bidi_override(p_item->cells[p_col].text_buf->get_rid(), structured_text_parser(p_item->cells[p_col].st_parser, p_item->cells[p_col].st_args, valtext)); p_item->cells.write[p_col].dirty = false; } @@ -1696,24 +1717,30 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } } - if (drop_mode_flags && drop_mode_over == p_item) { + if (drop_mode_flags && drop_mode_over) { Rect2 r = cell_rect; - bool has_parent = p_item->get_first_child() != nullptr; if (rtl) { r.position.x = get_size().width - r.position.x - r.size.x; } - - if (drop_mode_section == -1 || has_parent || drop_mode_section == 0) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color); - } - - if (drop_mode_section == 0) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, 1, r.size.y), cache.drop_position_color); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x + r.size.x - 1, r.position.y, 1, r.size.y), cache.drop_position_color); - } - - if ((drop_mode_section == 1 && !has_parent) || drop_mode_section == 0) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y + r.size.y, r.size.x, 1), cache.drop_position_color); + if (drop_mode_over == p_item) { + if (drop_mode_section == 0 || drop_mode_section == -1) { + // Line above. + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color); + } + if (drop_mode_section == 0) { + // Side lines. + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, 1, r.size.y), cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x + r.size.x - 1, r.position.y, 1, r.size.y), cache.drop_position_color); + } + if (drop_mode_section == 0 || (drop_mode_section == 1 && (!p_item->get_first_child() || p_item->is_collapsed()))) { + // Line below. + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y + r.size.y, r.size.x, 1), cache.drop_position_color); + } + } else if (drop_mode_over == p_item->get_parent()) { + if (drop_mode_section == 1 && !p_item->get_prev() /* && !drop_mode_over->is_collapsed() */) { // The drop_mode_over shouldn't ever be collapsed in here, otherwise we would be drawing a child of a collapsed item. + // Line above. + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color); + } } } @@ -2768,7 +2795,7 @@ void Tree::_go_down() { accept_event(); } -void Tree::_gui_input(Ref<InputEvent> p_event) { +void Tree::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventKey> k = p_event; @@ -4650,8 +4677,6 @@ bool Tree::get_allow_reselect() const { } void Tree::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &Tree::_gui_input); - ClassDB::bind_method(D_METHOD("clear"), &Tree::clear); ClassDB::bind_method(D_METHOD("create_item", "parent", "idx"), &Tree::_create_item, DEFVAL(Variant()), DEFVAL(-1)); @@ -4770,12 +4795,11 @@ Tree::Tree() { popup_menu = memnew(PopupMenu); popup_menu->hide(); - add_child(popup_menu); - // popup_menu->set_as_top_level(true); + add_child(popup_menu, false, INTERNAL_MODE_FRONT); popup_editor = memnew(Popup); popup_editor->set_wrap_controls(true); - add_child(popup_editor); + add_child(popup_editor, false, INTERNAL_MODE_FRONT); popup_editor_vb = memnew(VBoxContainer); popup_editor->add_child(popup_editor_vb); popup_editor_vb->add_theme_constant_override("separation", 0); @@ -4793,12 +4817,12 @@ Tree::Tree() { h_scroll = memnew(HScrollBar); v_scroll = memnew(VScrollBar); - add_child(h_scroll); - add_child(v_scroll); + add_child(h_scroll, false, INTERNAL_MODE_FRONT); + add_child(v_scroll, false, INTERNAL_MODE_FRONT); range_click_timer = memnew(Timer); range_click_timer->connect("timeout", callable_mp(this, &Tree::_range_click_timeout)); - add_child(range_click_timer); + add_child(range_click_timer, false, INTERNAL_MODE_FRONT); h_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved)); v_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved)); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index c207737cc0..8b7ddc3faf 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -114,6 +114,7 @@ private: Vector<Button> buttons; Ref<Font> custom_font; + int custom_font_size = -1; Cell() { text_buf.instantiate(); @@ -299,6 +300,9 @@ public: void set_custom_font(int p_column, const Ref<Font> &p_font); Ref<Font> get_custom_font(int p_column) const; + void set_custom_font_size(int p_column, int p_font_size); + int get_custom_font_size(int p_column) const; + void set_custom_bg_color(int p_column, const Color &p_color, bool p_bg_outline = false); void clear_custom_bg_color(int p_column); Color get_custom_bg_color(int p_column) const; @@ -462,7 +466,6 @@ private: void popup_select(int p_option); - void _gui_input(Ref<InputEvent> p_event); void _notification(int p_what); void item_edited(int p_column, TreeItem *p_item, bool p_lmb = true); @@ -626,6 +629,8 @@ protected: } public: + virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual String get_tooltip(const Point2 &p_pos) const override; TreeItem *get_item_at_position(const Point2 &p_pos) const; diff --git a/scene/gui/video_player.cpp b/scene/gui/video_player.cpp index 229b5384ea..8734037a57 100644 --- a/scene/gui/video_player.cpp +++ b/scene/gui/video_player.cpp @@ -129,7 +129,7 @@ void VideoPlayer::_mix_audio() { void VideoPlayer::_notification(int p_notification) { switch (p_notification) { case NOTIFICATION_ENTER_TREE: { - AudioServer::get_singleton()->add_callback(_mix_audios, this); + AudioServer::get_singleton()->add_mix_callback(_mix_audios, this); if (stream.is_valid() && autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); @@ -138,8 +138,7 @@ void VideoPlayer::_notification(int p_notification) { } break; case NOTIFICATION_EXIT_TREE: { - AudioServer::get_singleton()->remove_callback(_mix_audios, this); - + AudioServer::get_singleton()->remove_mix_callback(_mix_audios, this); } break; case NOTIFICATION_INTERNAL_PROCESS: { diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index f329332725..64b169b1fb 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -571,6 +571,12 @@ void CanvasItem::draw_texture_rect_region(const Ref<Texture2D> &p_texture, const p_texture->draw_rect_region(canvas_item, p_rect, p_src_rect, p_modulate, p_transpose, p_clip_uv); } +void CanvasItem::draw_msdf_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, double p_outline, double p_pixel_range) { + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); + ERR_FAIL_COND(p_texture.is_null()); + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(canvas_item, p_rect, p_texture->get_rid(), p_src_rect, p_modulate, p_outline, p_pixel_range); +} + void CanvasItem::draw_style_box(const Ref<StyleBox> &p_style_box, const Rect2 &p_rect) { ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); @@ -881,6 +887,7 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_texture", "texture", "position", "modulate"), &CanvasItem::draw_texture, DEFVAL(Color(1, 1, 1, 1))); ClassDB::bind_method(D_METHOD("draw_texture_rect", "texture", "rect", "tile", "modulate", "transpose"), &CanvasItem::draw_texture_rect, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_texture_rect_region", "texture", "rect", "src_rect", "modulate", "transpose", "clip_uv"), &CanvasItem::draw_texture_rect_region, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(false), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("draw_msdf_texture_rect_region", "texture", "rect", "src_rect", "modulate", "outline", "pixel_range"), &CanvasItem::draw_msdf_texture_rect_region, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(0.0), DEFVAL(4.0)); ClassDB::bind_method(D_METHOD("draw_style_box", "style_box", "rect"), &CanvasItem::draw_style_box); ClassDB::bind_method(D_METHOD("draw_primitive", "points", "colors", "uvs", "texture", "width"), &CanvasItem::draw_primitive, DEFVAL(Ref<Texture2D>()), DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("draw_polygon", "points", "colors", "uvs", "texture"), &CanvasItem::draw_polygon, DEFVAL(PackedVector2Array()), DEFVAL(Ref<Texture2D>())); diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index a591cab485..01ed47d4dc 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -226,6 +226,7 @@ public: void draw_texture(const Ref<Texture2D> &p_texture, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1, 1)); void draw_texture_rect(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false); void draw_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = false); + void draw_msdf_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), double p_outline = 0.0, double p_pixel_range = 4.0); void draw_style_box(const Ref<StyleBox> &p_style_box, const Rect2 &p_rect); void draw_primitive(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs, Ref<Texture2D> p_texture = Ref<Texture2D>(), real_t p_width = 1); void draw_polygon(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), Ref<Texture2D> p_texture = Ref<Texture2D>()); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 1d617d1ff7..05409b7bfe 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -48,6 +48,7 @@ #include <stdint.h> VARIANT_ENUM_CAST(Node::ProcessMode); +VARIANT_ENUM_CAST(Node::InternalMode); int Node::orphan_node_count = 0; @@ -58,7 +59,7 @@ void Node::_notification(int p_notification) { } break; case NOTIFICATION_PHYSICS_PROCESS: { - GDVIRTUAL_CALL(_physics_process, get_process_delta_time()); + GDVIRTUAL_CALL(_physics_process, get_physics_process_delta_time()); } break; case NOTIFICATION_ENTER_TREE: { @@ -122,28 +123,27 @@ void Node::_notification(int p_notification) { } } break; case NOTIFICATION_READY: { - if (get_script_instance()) { - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_input)) { - set_process_input(true); - } - - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_unhandled_input)) { - set_process_unhandled_input(true); - } + if (GDVIRTUAL_IS_OVERRIDDEN(_input)) { + set_process_input(true); + } - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_unhandled_key_input)) { - set_process_unhandled_key_input(true); - } + if (GDVIRTUAL_IS_OVERRIDDEN(_unhandled_input)) { + set_process_unhandled_input(true); + } - if (GDVIRTUAL_IS_OVERRIDEN(_process)) { - set_process(true); - } - if (GDVIRTUAL_IS_OVERRIDEN(_physics_process)) { - set_physics_process(true); - } + if (GDVIRTUAL_IS_OVERRIDDEN(_unhandled_key_input)) { + set_process_unhandled_key_input(true); + } - GDVIRTUAL_CALL(_ready); + if (GDVIRTUAL_IS_OVERRIDDEN(_process)) { + set_process(true); + } + if (GDVIRTUAL_IS_OVERRIDDEN(_physics_process)) { + set_physics_process(true); } + + GDVIRTUAL_CALL(_ready); + if (data.filename.length()) { ERR_FAIL_COND(!is_inside_tree()); get_multiplayer()->scene_enter_exit_notify(data.filename, this, true); @@ -291,14 +291,40 @@ void Node::_propagate_exit_tree() { void Node::move_child(Node *p_child, int p_pos) { ERR_FAIL_NULL(p_child); - ERR_FAIL_INDEX_MSG(p_pos, data.children.size() + 1, vformat("Invalid new child position: %d.", p_pos)); ERR_FAIL_COND_MSG(p_child->data.parent != this, "Child is not a child of this node."); + + // We need to check whether node is internal and move it only in the relevant node range. + if (p_child->_is_internal_front()) { + ERR_FAIL_INDEX_MSG(p_pos, data.internal_children_front, vformat("Invalid new child position: %d. Child is internal.", p_pos)); + _move_child(p_child, p_pos); + } else if (p_child->_is_internal_back()) { + ERR_FAIL_INDEX_MSG(p_pos, data.internal_children_back, vformat("Invalid new child position: %d. Child is internal.", p_pos)); + _move_child(p_child, data.children.size() - data.internal_children_back + p_pos); + } else { + ERR_FAIL_INDEX_MSG(p_pos, data.children.size() + 1 - data.internal_children_front - data.internal_children_back, vformat("Invalid new child position: %d.", p_pos)); + _move_child(p_child, p_pos + data.internal_children_front); + } +} + +void Node::_move_child(Node *p_child, int p_pos, bool p_ignore_end) { ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, move_child() failed. Consider using call_deferred(\"move_child\") instead (or \"popup\" if this is from a popup)."); // Specifying one place beyond the end // means the same as moving to the last position - if (p_pos == data.children.size()) { - p_pos--; + if (!p_ignore_end) { // p_ignore_end is a little hack to make back internal children work properly. + if (p_child->_is_internal_front()) { + if (p_pos == data.internal_children_front) { + p_pos--; + } + } else if (p_child->_is_internal_back()) { + if (p_pos == data.children.size()) { + p_pos--; + } + } else { + if (p_pos == data.children.size() - data.internal_children_back) { + p_pos--; + } + } } if (p_child->data.pos == p_pos) { @@ -339,7 +365,14 @@ void Node::raise() { return; } - data.parent->move_child(this, data.parent->data.children.size() - 1); + // Internal children move within a different index range. + if (_is_internal_front()) { + data.parent->move_child(this, data.parent->data.internal_children_front - 1); + } else if (_is_internal_back()) { + data.parent->move_child(this, data.parent->data.internal_children_back - 1); + } else { + data.parent->move_child(this, data.parent->get_child_count(false) - 1); + } } void Node::add_child_notify(Node *p_child) { @@ -483,32 +516,32 @@ void Node::_propagate_process_owner(Node *p_owner, int p_pause_notification, int } } -void Node::set_network_master(int p_peer_id, bool p_recursive) { - data.network_master = p_peer_id; +void Node::set_multiplayer_authority(int p_peer_id, bool p_recursive) { + data.multiplayer_authority = p_peer_id; if (p_recursive) { for (int i = 0; i < data.children.size(); i++) { - data.children[i]->set_network_master(p_peer_id, true); + data.children[i]->set_multiplayer_authority(p_peer_id, true); } } } -int Node::get_network_master() const { - return data.network_master; +int Node::get_multiplayer_authority() const { + return data.multiplayer_authority; } -bool Node::is_network_master() const { +bool Node::is_multiplayer_authority() const { ERR_FAIL_COND_V(!is_inside_tree(), false); - return get_multiplayer()->get_network_unique_id() == data.network_master; + return get_multiplayer()->get_unique_id() == data.multiplayer_authority; } /***** RPC CONFIG ********/ -uint16_t Node::rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_rpc_mode, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel) { +uint16_t Node::rpc_config(const StringName &p_method, Multiplayer::RPCMode p_rpc_mode, Multiplayer::TransferMode p_transfer_mode, int p_channel) { for (int i = 0; i < data.rpc_methods.size(); i++) { if (data.rpc_methods[i].name == p_method) { - MultiplayerAPI::RPCConfig &nd = data.rpc_methods.write[i]; + Multiplayer::RPCConfig &nd = data.rpc_methods.write[i]; nd.rpc_mode = p_rpc_mode; nd.transfer_mode = p_transfer_mode; nd.channel = p_channel; @@ -516,7 +549,7 @@ uint16_t Node::rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_ } } // New method - MultiplayerAPI::RPCConfig nd; + Multiplayer::RPCConfig nd; nd.name = p_method; nd.rpc_mode = p_rpc_mode; nd.transfer_mode = p_transfer_mode; @@ -609,7 +642,7 @@ Variant Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::Cal void Node::rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { ERR_FAIL_COND(!is_inside_tree()); - get_multiplayer()->rpcp(this, p_peer_id, true, p_method, p_arg, p_argcount); + get_multiplayer()->rpcp(this, p_peer_id, p_method, p_arg, p_argcount); } Ref<MultiplayerAPI> Node::get_multiplayer() const { @@ -630,7 +663,7 @@ void Node::set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { multiplayer = p_multiplayer; } -Vector<MultiplayerAPI::RPCConfig> Node::get_node_rpc_methods() const { +Vector<Multiplayer::RPCConfig> Node::get_node_rpc_methods() const { return data.rpc_methods; } @@ -1058,6 +1091,10 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) { p_child->data.pos = data.children.size(); data.children.push_back(p_child); p_child->data.parent = this; + + if (data.internal_children_back > 0) { + _move_child(p_child, data.children.size() - data.internal_children_back - 1); + } p_child->notification(NOTIFICATION_PARENTED); if (data.tree) { @@ -1070,7 +1107,7 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) { add_child_notify(p_child); } -void Node::add_child(Node *p_child, bool p_legible_unique_name) { +void Node::add_child(Node *p_child, bool p_legible_unique_name, InternalMode p_internal) { ERR_FAIL_NULL(p_child); ERR_FAIL_COND_MSG(p_child == this, vformat("Can't add child '%s' to itself.", p_child->get_name())); // adding to itself! ERR_FAIL_COND_MSG(p_child->data.parent, vformat("Can't add child '%s' to '%s', already has a parent '%s'.", p_child->get_name(), get_name(), p_child->data.parent->get_name())); //Fail if node has a parent @@ -1079,19 +1116,35 @@ void Node::add_child(Node *p_child, bool p_legible_unique_name) { #endif ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, add_node() failed. Consider using call_deferred(\"add_child\", child) instead."); - /* Validate name */ _validate_child_name(p_child, p_legible_unique_name); - _add_child_nocheck(p_child, p_child->data.name); + + if (p_internal == INTERNAL_MODE_FRONT) { + _move_child(p_child, data.internal_children_front); + data.internal_children_front++; + } else if (p_internal == INTERNAL_MODE_BACK) { + if (data.internal_children_back > 0) { + _move_child(p_child, data.children.size() - 1, true); + } + data.internal_children_back++; + } } void Node::add_sibling(Node *p_sibling, bool p_legible_unique_name) { ERR_FAIL_NULL(p_sibling); + ERR_FAIL_NULL(data.parent); ERR_FAIL_COND_MSG(p_sibling == this, vformat("Can't add sibling '%s' to itself.", p_sibling->get_name())); // adding to itself! ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, add_sibling() failed. Consider using call_deferred(\"add_sibling\", sibling) instead."); - get_parent()->add_child(p_sibling, p_legible_unique_name); - get_parent()->move_child(p_sibling, this->get_index() + 1); + InternalMode internal = INTERNAL_MODE_DISABLED; + if (_is_internal_front()) { // The sibling will have the same internal status. + internal = INTERNAL_MODE_FRONT; + } else if (_is_internal_back()) { + internal = INTERNAL_MODE_BACK; + } + + data.parent->add_child(p_sibling, p_legible_unique_name, internal); + data.parent->_move_child(p_sibling, get_index() + 1); } void Node::_propagate_validate_owner() { @@ -1145,7 +1198,12 @@ void Node::remove_child(Node *p_child) { ERR_FAIL_COND_MSG(idx == -1, vformat("Cannot remove child node '%s' as it is not a child of this node.", p_child->get_name())); //ERR_FAIL_COND( p_child->data.blocked > 0 ); - //if (data.scene) { does not matter + // If internal child, update the counter. + if (p_child->_is_internal_front()) { + data.internal_children_front--; + } else if (p_child->_is_internal_back()) { + data.internal_children_back--; + } p_child->_set_tree(nullptr); //} @@ -1175,17 +1233,29 @@ void Node::remove_child(Node *p_child) { } } -int Node::get_child_count() const { - return data.children.size(); +int Node::get_child_count(bool p_include_internal) const { + if (p_include_internal) { + return data.children.size(); + } else { + return data.children.size() - data.internal_children_front - data.internal_children_back; + } } -Node *Node::get_child(int p_index) const { - if (p_index < 0) { - p_index += data.children.size(); +Node *Node::get_child(int p_index, bool p_include_internal) const { + if (p_include_internal) { + if (p_index < 0) { + p_index += data.children.size(); + } + ERR_FAIL_INDEX_V(p_index, data.children.size(), nullptr); + return data.children[p_index]; + } else { + if (p_index < 0) { + p_index += data.children.size() - data.internal_children_front - data.internal_children_back; + } + ERR_FAIL_INDEX_V(p_index, data.children.size() - data.internal_children_front - data.internal_children_back, nullptr); + p_index += data.internal_children_front; + return data.children[p_index]; } - ERR_FAIL_INDEX_V(p_index, data.children.size(), nullptr); - - return data.children[p_index]; } Node *Node::_get_child_by_name(const StringName &p_name) const { @@ -1717,7 +1787,13 @@ void Node::_propagate_replace_owner(Node *p_owner, Node *p_by_owner) { data.blocked--; } -int Node::get_index() const { +int Node::get_index(bool p_include_internal) const { + // p_include_internal = false doesn't make sense if the node is internal. + ERR_FAIL_COND_V_MSG(!p_include_internal && (_is_internal_front() || _is_internal_back()), -1, "Node is internal. Can't get index with 'include_internal' being false."); + + if (data.parent && !p_include_internal) { + return data.pos - data.parent->data.internal_children_front; + } return data.pos; } @@ -2429,12 +2505,12 @@ void Node::queue_delete() { } } -TypedArray<Node> Node::_get_children() const { +TypedArray<Node> Node::_get_children(bool p_include_internal) const { TypedArray<Node> arr; - int cc = get_child_count(); + int cc = get_child_count(p_include_internal); arr.resize(cc); for (int i = 0; i < cc; i++) { - arr[i] = get_child(i); + arr[i] = get_child(i, p_include_internal); } return arr; @@ -2487,9 +2563,14 @@ void Node::clear_internal_tree_resource_paths() { } TypedArray<String> Node::get_configuration_warnings() const { - if (get_script_instance() && get_script_instance()->get_script().is_valid() && - get_script_instance()->get_script()->is_tool() && get_script_instance()->has_method("_get_configuration_warnings")) { - return get_script_instance()->call("_get_configuration_warnings"); + Vector<String> warnings; + if (GDVIRTUAL_CALL(_get_configuration_warnings, warnings)) { + TypedArray<String> ret; + ret.resize(warnings.size()); + for (int i = 0; i < warnings.size(); i++) { + ret[i] = warnings[i]; + } + return ret; } return Array(); } @@ -2535,6 +2616,37 @@ void Node::request_ready() { data.ready_first = true; } +void Node::_call_input(const Ref<InputEvent> &p_event) { + GDVIRTUAL_CALL(_input, p_event); + if (!is_inside_tree() || !get_viewport() || get_viewport()->is_input_handled()) { + return; + } + input(p_event); +} +void Node::_call_unhandled_input(const Ref<InputEvent> &p_event) { + GDVIRTUAL_CALL(_unhandled_input, p_event); + if (!is_inside_tree() || !get_viewport() || get_viewport()->is_input_handled()) { + return; + } + unhandled_input(p_event); +} +void Node::_call_unhandled_key_input(const Ref<InputEvent> &p_event) { + GDVIRTUAL_CALL(_unhandled_key_input, p_event); + if (!is_inside_tree() || !get_viewport() || get_viewport()->is_input_handled()) { + return; + } + unhandled_key_input(p_event); +} + +void Node::input(const Ref<InputEvent> &p_event) { +} + +void Node::unhandled_input(const Ref<InputEvent> &p_event) { +} + +void Node::unhandled_key_input(const Ref<InputEvent> &p_key_event) { +} + void Node::_bind_methods() { GLOBAL_DEF("editor/node_naming/name_num_separator", 0); ProjectSettings::get_singleton()->set_custom_property_info("editor/node_naming/name_num_separator", PropertyInfo(Variant::INT, "editor/node_naming/name_num_separator", PROPERTY_HINT_ENUM, "None,Space,Underscore,Dash")); @@ -2545,11 +2657,11 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("set_name", "name"), &Node::set_name); ClassDB::bind_method(D_METHOD("get_name"), &Node::get_name); - ClassDB::bind_method(D_METHOD("add_child", "node", "legible_unique_name"), &Node::add_child, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("add_child", "node", "legible_unique_name", "internal"), &Node::add_child, DEFVAL(false), DEFVAL(0)); ClassDB::bind_method(D_METHOD("remove_child", "node"), &Node::remove_child); - ClassDB::bind_method(D_METHOD("get_child_count"), &Node::get_child_count); - ClassDB::bind_method(D_METHOD("get_children"), &Node::_get_children); - ClassDB::bind_method(D_METHOD("get_child", "idx"), &Node::get_child); + ClassDB::bind_method(D_METHOD("get_child_count", "include_internal"), &Node::get_child_count, DEFVAL(false)); // Note that the default value bound for include_internal is false, while the method is declared with true. This is because internal nodes are irrelevant for GDSCript. + ClassDB::bind_method(D_METHOD("get_children", "include_internal"), &Node::_get_children, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_child", "idx", "include_internal"), &Node::get_child, DEFVAL(false)); ClassDB::bind_method(D_METHOD("has_node", "path"), &Node::has_node); ClassDB::bind_method(D_METHOD("get_node", "path"), &Node::get_node); ClassDB::bind_method(D_METHOD("get_node_or_null", "path"), &Node::get_node_or_null); @@ -2573,7 +2685,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("set_owner", "owner"), &Node::set_owner); ClassDB::bind_method(D_METHOD("get_owner"), &Node::get_owner); ClassDB::bind_method(D_METHOD("remove_and_skip"), &Node::remove_and_skip); - ClassDB::bind_method(D_METHOD("get_index"), &Node::get_index); + ClassDB::bind_method(D_METHOD("get_index", "include_internal"), &Node::get_index, DEFVAL(false)); ClassDB::bind_method(D_METHOD("print_tree"), &Node::print_tree); ClassDB::bind_method(D_METHOD("print_tree_pretty"), &Node::print_tree_pretty); ClassDB::bind_method(D_METHOD("set_filename", "filename"), &Node::set_filename); @@ -2616,6 +2728,8 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("set_scene_instance_load_placeholder", "load_placeholder"), &Node::set_scene_instance_load_placeholder); ClassDB::bind_method(D_METHOD("get_scene_instance_load_placeholder"), &Node::get_scene_instance_load_placeholder); + ClassDB::bind_method(D_METHOD("set_editable_instance", "node", "is_editable"), &Node::set_editable_instance); + ClassDB::bind_method(D_METHOD("is_editable_instance", "node"), &Node::is_editable_instance); ClassDB::bind_method(D_METHOD("get_viewport"), &Node::get_viewport); @@ -2623,15 +2737,15 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("request_ready"), &Node::request_ready); - ClassDB::bind_method(D_METHOD("set_network_master", "id", "recursive"), &Node::set_network_master, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("get_network_master"), &Node::get_network_master); + ClassDB::bind_method(D_METHOD("set_multiplayer_authority", "id", "recursive"), &Node::set_multiplayer_authority, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_multiplayer_authority"), &Node::get_multiplayer_authority); - ClassDB::bind_method(D_METHOD("is_network_master"), &Node::is_network_master); + ClassDB::bind_method(D_METHOD("is_multiplayer_authority"), &Node::is_multiplayer_authority); ClassDB::bind_method(D_METHOD("get_multiplayer"), &Node::get_multiplayer); ClassDB::bind_method(D_METHOD("get_custom_multiplayer"), &Node::get_custom_multiplayer); ClassDB::bind_method(D_METHOD("set_custom_multiplayer", "api"), &Node::set_custom_multiplayer); - ClassDB::bind_method(D_METHOD("rpc_config", "method", "rpc_mode", "transfer_mode", "channel"), &Node::rpc_config, DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("rpc_config", "method", "rpc_mode", "transfer_mode", "channel"), &Node::rpc_config, DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_editor_description", "editor_description"), &Node::set_editor_description); ClassDB::bind_method(D_METHOD("get_editor_description"), &Node::get_editor_description); @@ -2709,6 +2823,10 @@ void Node::_bind_methods() { BIND_ENUM_CONSTANT(DUPLICATE_SCRIPTS); BIND_ENUM_CONSTANT(DUPLICATE_USE_INSTANCING); + BIND_ENUM_CONSTANT(INTERNAL_MODE_DISABLED); + BIND_ENUM_CONSTANT(INTERNAL_MODE_FRONT); + BIND_ENUM_CONSTANT(INTERNAL_MODE_BACK); + ADD_SIGNAL(MethodInfo("ready")); ADD_SIGNAL(MethodInfo("renamed")); ADD_SIGNAL(MethodInfo("tree_entered")); @@ -2734,11 +2852,9 @@ void Node::_bind_methods() { GDVIRTUAL_BIND(_exit_tree); GDVIRTUAL_BIND(_ready); GDVIRTUAL_BIND(_get_configuration_warnings); - - BIND_VMETHOD(MethodInfo("_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); - BIND_VMETHOD(MethodInfo("_unhandled_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); - BIND_VMETHOD(MethodInfo("_unhandled_key_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEventKey"))); - BIND_VMETHOD(MethodInfo(PropertyInfo(Variant::ARRAY, "", PROPERTY_HINT_ARRAY_TYPE, "String"), "_get_configuration_warnings")); + GDVIRTUAL_BIND(_input, "event"); + GDVIRTUAL_BIND(_unhandled_input, "event"); + GDVIRTUAL_BIND(_unhandled_key_input, "event"); } String Node::_get_name_num_separator() { diff --git a/scene/main/node.h b/scene/main/node.h index 8aa56aa97f..198501eeac 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -73,6 +73,12 @@ public: NAME_CASING_SNAKE_CASE }; + enum InternalMode { + INTERNAL_MODE_DISABLED, + INTERNAL_MODE_FRONT, + INTERNAL_MODE_BACK, + }; + struct Comparator { bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); } }; @@ -97,6 +103,8 @@ private: Node *parent = nullptr; Node *owner = nullptr; Vector<Node *> children; + int internal_children_front = 0; + int internal_children_back = 0; int pos = -1; int depth = -1; int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed. @@ -119,8 +127,8 @@ private: ProcessMode process_mode = PROCESS_MODE_INHERIT; Node *process_owner = nullptr; - int network_master = 1; // Server by default. - Vector<MultiplayerAPI::RPCConfig> rpc_methods; + int multiplayer_authority = 1; // Server by default. + Vector<Multiplayer::RPCConfig> rpc_methods; // Variables used to properly sort the node when processing, ignored otherwise. // TODO: Should move all the stuff below to bits. @@ -172,12 +180,15 @@ private: void _duplicate_signals(const Node *p_original, Node *p_copy) const; Node *_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap = nullptr) const; - TypedArray<Node> _get_children() const; + TypedArray<Node> _get_children(bool p_include_internal = true) const; Array _get_groups() const; Variant _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); Variant _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + _FORCE_INLINE_ bool _is_internal_front() const { return data.parent && data.pos < data.parent->data.internal_children_front; } + _FORCE_INLINE_ bool _is_internal_back() const { return data.parent && data.pos >= data.parent->data.children.size() - data.parent->data.internal_children_back; } + friend class SceneTree; void _set_tree(SceneTree *p_tree); @@ -208,12 +219,27 @@ protected: void _set_owner_nocheck(Node *p_owner); void _set_name_nocheck(const StringName &p_name); + //call from SceneTree + void _call_input(const Ref<InputEvent> &p_event); + void _call_unhandled_input(const Ref<InputEvent> &p_event); + void _call_unhandled_key_input(const Ref<InputEvent> &p_event); + +protected: + virtual void input(const Ref<InputEvent> &p_event); + virtual void unhandled_input(const Ref<InputEvent> &p_event); + virtual void unhandled_key_input(const Ref<InputEvent> &p_key_event); + GDVIRTUAL1(_process, double) GDVIRTUAL1(_physics_process, double) GDVIRTUAL0(_enter_tree) GDVIRTUAL0(_exit_tree) GDVIRTUAL0(_ready) GDVIRTUAL0RC(Vector<String>, _get_configuration_warnings) + + GDVIRTUAL1(_input, Ref<InputEvent>) + GDVIRTUAL1(_unhandled_input, Ref<InputEvent>) + GDVIRTUAL1(_unhandled_key_input, Ref<InputEvent>) + public: enum { // you can make your own, but don't use the same numbers as other notifications in other nodes @@ -269,12 +295,12 @@ public: StringName get_name() const; void set_name(const String &p_name); - void add_child(Node *p_child, bool p_legible_unique_name = false); + void add_child(Node *p_child, bool p_legible_unique_name = false, InternalMode p_internal = INTERNAL_MODE_DISABLED); void add_sibling(Node *p_sibling, bool p_legible_unique_name = false); void remove_child(Node *p_child); - int get_child_count() const; - Node *get_child(int p_index) const; + int get_child_count(bool p_include_internal = true) const; + Node *get_child(int p_index, bool p_include_internal = true) const; bool has_node(const NodePath &p_path) const; Node *get_node(const NodePath &p_path) const; Node *get_node_or_null(const NodePath &p_path) const; @@ -312,6 +338,7 @@ public: int get_persistent_group_count() const; void move_child(Node *p_child, int p_pos); + void _move_child(Node *p_child, int p_pos, bool p_ignore_end = false); void raise(); void set_owner(Node *p_owner); @@ -319,7 +346,7 @@ public: void get_owned_by(Node *p_by, List<Node *> *p_owned); void remove_and_skip(); - int get_index() const; + int get_index(bool p_include_internal = true) const; Ref<Tween> create_tween(); @@ -435,12 +462,12 @@ public: bool is_displayed_folded() const; /* NETWORK */ - void set_network_master(int p_peer_id, bool p_recursive = true); - int get_network_master() const; - bool is_network_master() const; + void set_multiplayer_authority(int p_peer_id, bool p_recursive = true); + int get_multiplayer_authority() const; + bool is_multiplayer_authority() const; - uint16_t rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_rpc_mode, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel = 0); // config a local method for RPC - Vector<MultiplayerAPI::RPCConfig> get_node_rpc_methods() const; + uint16_t rpc_config(const StringName &p_method, Multiplayer::RPCMode p_rpc_mode, Multiplayer::TransferMode p_transfer_mode, int p_channel = 0); // config a local method for RPC + Vector<Multiplayer::RPCConfig> get_node_rpc_methods() const; void rpc(const StringName &p_method, VARIANT_ARG_LIST); // RPC, honors RPCMode, TransferMode, channel void rpc_id(int p_peer_id, const StringName &p_method, VARIANT_ARG_LIST); // RPC to specific peer(s), honors RPCMode, TransferMode, channel diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 5b707498a7..bef7ecb462 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -858,16 +858,7 @@ void SceneTree::_notify_group_pause(const StringName &p_group, int p_notificatio } } -/* -void SceneMainLoop::_update_listener_2d() { - if (listener_2d.is_valid()) { - SpatialSound2DServer::get_singleton()->listener_set_space( listener_2d, world_2d->get_sound_space() ); - } -} - -*/ - -void SceneTree::_call_input_pause(const StringName &p_group, const StringName &p_method, const Ref<InputEvent> &p_input, Viewport *p_viewport) { +void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_call_type, const Ref<InputEvent> &p_input, Viewport *p_viewport) { Map<StringName, Group>::Element *E = group_map.find(p_group); if (!E) { return; @@ -886,9 +877,6 @@ void SceneTree::_call_input_pause(const StringName &p_group, const StringName &p int node_count = nodes_copy.size(); Node **nodes = nodes_copy.ptrw(); - Variant arg = p_input; - const Variant *v[1] = { &arg }; - call_lock++; for (int i = node_count - 1; i >= 0; i--) { @@ -905,14 +893,16 @@ void SceneTree::_call_input_pause(const StringName &p_group, const StringName &p continue; } - Callable::CallError err; - // Call both script and native method. - if (n->get_script_instance()) { - n->get_script_instance()->call(p_method, (const Variant **)v, 1, err); - } - MethodBind *method = ClassDB::get_method(n->get_class_name(), p_method); - if (method) { - method->call(n, (const Variant **)v, 1, err); + switch (p_call_type) { + case CALL_INPUT_TYPE_INPUT: + n->_call_input(p_input); + break; + case CALL_INPUT_TYPE_UNHANDLED_INPUT: + n->_call_unhandled_input(p_input); + break; + case CALL_INPUT_TYPE_UNHANDLED_KEY_INPUT: + n->_call_unhandled_key_input(p_input); + break; } } diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index cfb95bd6b5..19331c1906 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -31,7 +31,7 @@ #ifndef SCENE_TREE_H #define SCENE_TREE_H -#include "core/io/multiplayer_api.h" +#include "core/multiplayer/multiplayer_api.h" #include "core/os/main_loop.h" #include "core/os/thread_safe.h" #include "core/templates/self_list.h" @@ -204,8 +204,14 @@ private: void _main_window_close(); void _main_window_go_back(); + enum CallInputType { + CALL_INPUT_TYPE_INPUT, + CALL_INPUT_TYPE_UNHANDLED_INPUT, + CALL_INPUT_TYPE_UNHANDLED_KEY_INPUT, + }; + //used by viewport - void _call_input_pause(const StringName &p_group, const StringName &p_method, const Ref<InputEvent> &p_input, Viewport *p_viewport); + void _call_input_pause(const StringName &p_group, CallInputType p_call_type, const Ref<InputEvent> &p_input, Viewport *p_viewport); protected: void _notification(int p_notification); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 78fa0985a9..eea5ca9895 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -32,10 +32,12 @@ #include "core/core_string_names.h" #include "core/debugger/engine_debugger.h" +#include "core/object/message_queue.h" #include "core/string/translation.h" #include "core/templates/pair.h" #include "scene/2d/camera_2d.h" #include "scene/2d/collision_object_2d.h" +#include "scene/2d/listener_2d.h" #ifndef _3D_DISABLED #include "scene/3d/camera_3d.h" #include "scene/3d/collision_object_3d.h" @@ -45,12 +47,14 @@ #include "scene/gui/control.h" #include "scene/gui/label.h" #include "scene/gui/popup.h" +#include "scene/gui/popup_menu.h" #include "scene/main/canvas_layer.h" #include "scene/main/window.h" #include "scene/resources/mesh.h" #include "scene/resources/text_line.h" #include "scene/resources/world_2d.h" #include "scene/scene_string_names.h" +#include "servers/audio_server.h" void ViewportTexture::setup_local_to_scene() { if (vp) { @@ -77,7 +81,7 @@ void ViewportTexture::setup_local_to_scene() { RS::get_singleton()->texture_proxy_update(proxy, vp->texture_rid); RS::get_singleton()->free(proxy_ph); } else { - ERR_FAIL_COND(proxy.is_valid()); //should be invalid + ERR_FAIL_COND(proxy.is_valid()); // Should be invalid. proxy = RS::get_singleton()->texture_proxy_create(vp->texture_rid); } } @@ -114,7 +118,6 @@ Size2 ViewportTexture::get_size() const { } RID ViewportTexture::get_rid() const { - //ERR_FAIL_COND_V_MSG(!vp, RID(), "Viewport Texture must be set to use it."); if (proxy.is_null()) { proxy_ph = RS::get_singleton()->texture_2d_placeholder_create(); proxy = RS::get_singleton()->texture_proxy_create(proxy_ph); @@ -259,7 +262,7 @@ void Viewport::_sub_window_update(Window *p_window) { void Viewport::_sub_window_grab_focus(Window *p_window) { if (p_window == nullptr) { - //release current focus + // Release current focus. if (gui.subwindow_focused) { gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_OUT); gui.subwindow_focused = nullptr; @@ -285,18 +288,18 @@ void Viewport::_sub_window_grab_focus(Window *p_window) { ERR_FAIL_COND(index == -1); if (p_window->get_flag(Window::FLAG_NO_FOCUS)) { - //can only move to foreground, but no focus granted + // Can only move to foreground, but no focus granted. SubWindow sw = gui.sub_windows[index]; gui.sub_windows.remove(index); gui.sub_windows.push_back(sw); index = gui.sub_windows.size() - 1; _sub_window_update_order(); - return; //i guess not... + return; } if (gui.subwindow_focused) { if (gui.subwindow_focused == p_window) { - return; //nothing to do + return; // Nothing to do. } gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_OUT); gui.subwindow_drag = SUB_WINDOW_DRAG_DISABLED; @@ -313,7 +316,7 @@ void Viewport::_sub_window_grab_focus(Window *p_window) { gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_IN); - { //move to foreground + { // Move to foreground. SubWindow sw = gui.sub_windows[index]; gui.sub_windows.remove(index); gui.sub_windows.push_back(sw); @@ -419,7 +422,7 @@ void Viewport::_notification(int p_what) { } if (camera_3d_set.size() && !camera_3d) { - //there are cameras but no current camera, pick first in tree and make it current + // There are cameras but no current camera, pick first in tree and make it current. Camera3D *first = nullptr; for (Set<Camera3D *>::Element *E = camera_3d_set.front(); E; E = E->next()) { if (first == nullptr || first->is_greater_than(E->get())) { @@ -518,8 +521,9 @@ void Viewport::_process_picking() { PhysicsDirectSpaceState2D *ss2d = PhysicsServer2D::get_singleton()->space_get_direct_state(find_world_2d()->get_space()); if (physics_has_last_mousepos) { - // if no mouse event exists, create a motion one. This is necessary because objects or camera may have moved. - // while this extra event is sent, it is checked if both camera and last object and last ID did not move. If nothing changed, the event is discarded to avoid flooding with unnecessary motion events every frame + // If no mouse event exists, create a motion one. This is necessary because objects or camera may have moved. + // While this extra event is sent, it is checked if both camera and last object and last ID did not move. + // If nothing changed, the event is discarded to avoid flooding with unnecessary motion events every frame. bool has_mouse_event = false; for (const Ref<InputEvent> &m : physics_picking_events) { if (m.is_valid()) { @@ -593,7 +597,7 @@ void Viewport::_process_picking() { Ref<InputEventKey> k = ev; if (k.is_valid()) { - //only for mask + // Only for mask. physics_last_mouse_state.alt = k->is_alt_pressed(); physics_last_mouse_state.shift = k->is_shift_pressed(); physics_last_mouse_state.control = k->is_ctrl_pressed(); @@ -614,7 +618,7 @@ void Viewport::_process_picking() { } if (ss2d) { - //send to 2D + // Send to 2D. uint64_t frame = get_tree()->get_frame(); @@ -623,11 +627,11 @@ void Viewport::_process_picking() { Transform2D canvas_transform; ObjectID canvas_layer_id; if (E->get()) { - // A descendant CanvasLayer + // A descendant CanvasLayer. canvas_transform = E->get()->get_transform(); canvas_layer_id = E->get()->get_instance_id(); } else { - // This Viewport's builtin canvas + // This Viewport's builtin canvas. canvas_transform = get_canvas_transform(); canvas_layer_id = ObjectID(); } @@ -647,7 +651,7 @@ void Viewport::_process_picking() { co->_mouse_enter(); } else { F->get() = frame; - // It was already hovered, so don't send the event if it's faked + // It was already hovered, so don't send the event if it's faked. if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) { send_event = false; } @@ -662,7 +666,7 @@ void Viewport::_process_picking() { } if (send_event) { - co->_input_event(this, ev, res[i].shape); + co->_input_event_call(this, ev, res[i].shape); } } } @@ -696,11 +700,11 @@ void Viewport::_process_picking() { } if (captured) { - //none + // None. } else if (pos == last_pos) { if (last_id.is_valid()) { if (ObjectDB::get_instance(last_id) && last_object) { - //good, exists + // Good, exists. _collision_object_3d_input_event(last_object, camera_3d, ev, result.position, result.normal, result.shape); if (last_object->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) { physics_object_capture = last_id; @@ -819,12 +823,7 @@ Rect2 Viewport::get_visible_rect() const { } void Viewport::_update_listener_2d() { - /* - if (is_inside_tree() && audio_listener_3d && (!get_parent() || (Object::cast_to<Control>(get_parent()) && Object::cast_to<Control>(get_parent())->is_visible_in_tree()))) - SpatialSound2DServer::get_singleton()->listener_set_space(internal_listener_2d, find_world_2d()->get_sound_space()); - else - SpatialSound2DServer::get_singleton()->listener_set_space(internal_listener_2d, RID()); -*/ + AudioServer::get_singleton()->notify_listener_changed(); } void Viewport::set_as_audio_listener_2d(bool p_enable) { @@ -833,7 +832,6 @@ void Viewport::set_as_audio_listener_2d(bool p_enable) { } audio_listener_2d = p_enable; - _update_listener_2d(); } @@ -841,6 +839,10 @@ bool Viewport::is_audio_listener_2d() const { return audio_listener_2d; } +Listener2D *Viewport::get_listener_2d() const { + return listener_2d; +} + void Viewport::enable_canvas_transform_override(bool p_enable) { if (override_canvas_transform == p_enable) { return; @@ -905,6 +907,21 @@ void Viewport::_camera_2d_set(Camera2D *p_camera_2d) { camera_2d = p_camera_2d; } +void Viewport::_listener_2d_set(Listener2D *p_listener) { + if (listener_2d == p_listener) { + return; + } else if (listener_2d) { + listener_2d->clear_current(); + } + listener_2d = p_listener; +} + +void Viewport::_listener_2d_remove(Listener2D *p_listener) { + if (listener_2d == p_listener) { + listener_2d = nullptr; + } +} + void Viewport::_canvas_layer_add(CanvasLayer *p_canvas_layer) { canvas_layers.insert(p_canvas_layer); } @@ -1104,6 +1121,12 @@ String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Cont while (p_control) { tooltip = p_control->get_tooltip(pos); + // Temporary solution for PopupMenus. + PopupMenu *menu = Object::cast_to<PopupMenu>(this); + if (menu) { + tooltip = menu->get_tooltip(pos); + } + if (r_tooltip_owner) { *r_tooltip_owner = p_control; } @@ -1212,11 +1235,9 @@ void Viewport::_gui_show_tooltip() { } void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_input) { - //_block(); - Ref<InputEvent> ev = p_input; - //mouse wheel events can't be stopped + // Mouse wheel events can't be stopped. Ref<InputEventMouseButton> mb = p_input; bool cant_stop_me_now = (mb.is_valid() && @@ -1234,27 +1255,7 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu Control *control = Object::cast_to<Control>(ci); if (control) { if (control->data.mouse_filter != Control::MOUSE_FILTER_IGNORE) { - control->emit_signal(SceneStringNames::get_singleton()->gui_input, ev); //signal should be first, so it's possible to override an event (and then accept it) - } - if (gui.key_event_accepted) { - break; - } - if (!control->is_inside_tree()) { - break; - } - - if (control->data.mouse_filter != Control::MOUSE_FILTER_IGNORE) { - // Call both script and native methods. - Callable::CallError error; - Variant event = ev; - const Variant *args[1] = { &event }; - if (control->get_script_instance()) { - control->get_script_instance()->call(SceneStringNames::get_singleton()->_gui_input, args, 1, error); - } - MethodBind *method = ClassDB::get_method(control->get_class_name(), SceneStringNames::get_singleton()->_gui_input); - if (method) { - method->call(control, args, 1, error); - } + control->_call_gui_input(ev); } if (!control->is_inside_tree() || control->is_set_as_top_level()) { @@ -1272,11 +1273,9 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu break; } - ev = ev->xformed_by(ci->get_transform()); //transform event upwards + ev = ev->xformed_by(ci->get_transform()); // Transform event upwards. ci = ci->get_parent_item(); } - - //_unblock(); } void Viewport::_gui_call_notification(Control *p_control, int p_what) { @@ -1306,12 +1305,10 @@ void Viewport::_gui_call_notification(Control *p_control, int p_what) { ci = ci->get_parent_item(); } - - //_unblock(); } Control *Viewport::gui_find_control(const Point2 &p_global) { - //aca va subwindows + // Handle subwindows. _gui_sort_roots(); for (List<Control *>::Element *E = gui.roots.back(); E; E = E->prev()) { @@ -1343,8 +1340,7 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ } if (!p_node->is_visible()) { - //return _find_next_visible_control_at_pos(p_node,p_global,r_inv_xform); - return nullptr; //canvas item hidden, discard + return nullptr; // Canvas item hidden, discard. } Transform2D matrix = p_xform * p_node->get_transform(); @@ -1388,32 +1384,31 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ } bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check) { - { //attempt grab, try parent controls too - CanvasItem *ci = p_at_control; - while (ci) { - Control *control = Object::cast_to<Control>(ci); - if (control) { - if (control->can_drop_data(p_at_pos, gui.drag_data)) { - if (!p_just_check) { - control->drop_data(p_at_pos, gui.drag_data); - } - - return true; + // Attempt grab, try parent controls too. + CanvasItem *ci = p_at_control; + while (ci) { + Control *control = Object::cast_to<Control>(ci); + if (control) { + if (control->can_drop_data(p_at_pos, gui.drag_data)) { + if (!p_just_check) { + control->drop_data(p_at_pos, gui.drag_data); } - if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { - break; - } + return true; } - p_at_pos = ci->get_transform().xform(p_at_pos); - - if (ci->is_set_as_top_level()) { + if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { break; } + } + + p_at_pos = ci->get_transform().xform(p_at_pos); - ci = ci->get_parent_item(); + if (ci->is_set_as_top_level()) { + break; } + + ci = ci->get_parent_item(); } return false; @@ -1433,23 +1428,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (mb->is_pressed()) { Size2 pos = mpos; if (gui.mouse_focus_mask) { - //do not steal mouse focus and stuff while a focus mask exists - gui.mouse_focus_mask |= 1 << (mb->get_button_index() - 1); //add the button to the mask + // Do not steal mouse focus and stuff while a focus mask exists. + gui.mouse_focus_mask |= 1 << (mb->get_button_index() - 1); // Add the button to the mask. } else { - bool is_handled = false; - - if (is_handled) { - set_input_as_handled(); - return; - } - - //Matrix32 parent_xform; - - /* - if (data.parent_canvas_item) - parent_xform=data.parent_canvas_item->get_global_transform(); - */ - gui.mouse_focus = gui_find_control(pos); gui.last_mouse_focus = gui.mouse_focus; @@ -1466,7 +1447,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - mb = mb->xformed_by(Transform2D()); // make a copy of the event + mb = mb->xformed_by(Transform2D()); // Make a copy of the event. mb->set_global_position(pos); @@ -1483,7 +1464,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } #endif - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { //assign focus + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { // Assign focus. CanvasItem *ci = gui.mouse_focus; while (ci) { Control *control = Object::cast_to<Control>(ci); @@ -1515,7 +1496,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { set_input_as_handled(); if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - //alternate drop use (when using force_drag(), as proposed by #5342 + // Alternate drop use (when using force_drag(), as proposed by #5342). if (gui.mouse_focus) { _gui_drop(gui.mouse_focus, pos, false); } @@ -1529,7 +1510,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.drag_preview_id = ObjectID(); } _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); - //change mouse accordingly + // Change mouse accordingly. } _gui_cancel_tooltip(); @@ -1549,26 +1530,28 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.dragging = false; gui.drag_mouse_over = nullptr; _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); - //change mouse accordingly + // Change mouse accordingly. } - gui.mouse_focus_mask &= ~(1 << (mb->get_button_index() - 1)); //remove from mask + gui.mouse_focus_mask &= ~(1 << (mb->get_button_index() - 1)); // Remove from mask. if (!gui.mouse_focus) { - //release event is only sent if a mouse focus (previously pressed button) exists + // Release event is only sent if a mouse focus (previously pressed button) exists. return; } Size2 pos = mpos; - mb = mb->xformed_by(Transform2D()); //make a copy + mb = mb->xformed_by(Transform2D()); // Make a copy. mb->set_global_position(pos); pos = gui.focus_inv_xform.xform(pos); mb->set_position(pos); Control *mouse_focus = gui.mouse_focus; - //disable mouse focus if needed before calling input, this makes popups on mouse press event work better, as the release will never be received otherwise + // Disable mouse focus if needed before calling input, + // this makes popups on mouse press event work better, + // as the release will never be received otherwise. if (gui.mouse_focus_mask == 0) { gui.mouse_focus = nullptr; gui.forced_mouse_focus = false; @@ -1578,11 +1561,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { _gui_call_input(mouse_focus, mb); } - /*if (gui.drag_data.get_type()!=Variant::NIL && mb->get_button_index()==MOUSE_BUTTON_LEFT) { - _propagate_viewport_notification(this,NOTIFICATION_DRAG_END); - gui.drag_data=Variant(); //always clear - }*/ - // In case the mouse was released after for example dragging a scrollbar, // check whether the current control is different from the stored one. If // it is different, rather than wait for it to be updated the next time the @@ -1621,12 +1599,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *over = nullptr; - // D&D + // Drag & drop. if (!gui.drag_attempted && gui.mouse_focus && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { gui.drag_accum += mm->get_relative(); float len = gui.drag_accum.length(); if (len > 10) { - { //attempt grab, try parent controls too + { // Attempt grab, try parent controls too. CanvasItem *ci = gui.mouse_focus; while (ci) { Control *control = Object::cast_to<Control>(ci); @@ -1699,14 +1677,14 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Vector2 speed = localizer.basis_xform(mm->get_speed()); Vector2 rel = localizer.basis_xform(mm->get_relative()); - mm = mm->xformed_by(Transform2D()); //make a copy + mm = mm->xformed_by(Transform2D()); // Make a copy. mm->set_global_position(mpos); mm->set_speed(speed); mm->set_relative(rel); if (mm->get_button_mask() == 0) { - //nothing pressed + // Nothing pressed. bool can_tooltip = true; @@ -1730,7 +1708,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { is_tooltip_shown = true; } } else { - is_tooltip_shown = true; //well, nothing to compare against, likely using custom control, so if it changes there is nothing we can do + is_tooltip_shown = true; // Nothing to compare against, likely using custom control, so if it changes there is nothing we can do. } } } else { @@ -1787,7 +1765,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (gui.drag_data.get_type() != Variant::NIL) { - //handle dragandrop + // Handle drag & drop. Control *drag_preview = _gui_get_drag_preview(); if (drag_preview) { @@ -1797,9 +1775,8 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.drag_mouse_over = over; gui.drag_mouse_over_pos = Vector2(); - //find the window this is above of - - //see if there is an embedder + // Find the window this is above of. + // See if there is an embedder. Viewport *embedder = nullptr; Vector2 viewport_pos; @@ -1807,7 +1784,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { embedder = this; viewport_pos = mpos; } else { - //not an embedder, but may be a subwindow of an embedder + // Not an embedder, but may be a subwindow of an embedder. Window *w = Object::cast_to<Window>(this); if (w) { if (w->is_embedded()) { @@ -1815,7 +1792,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Transform2D ai = (get_final_transform().affine_inverse() * _get_input_pre_xform()).affine_inverse(); - viewport_pos = ai.xform(mpos) + w->get_position(); //to parent coords + viewport_pos = ai.xform(mpos) + w->get_position(); // To parent coords. } } } @@ -1823,7 +1800,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Viewport *viewport_under = nullptr; if (embedder) { - //use embedder logic + // Use embedder logic. for (int i = embedder->gui.sub_windows.size() - 1; i >= 0; i--) { Window *sw = embedder->gui.sub_windows[i].window; @@ -1841,11 +1818,11 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (!viewport_under) { - //not in a subwindow, likely in embedder + // Not in a subwindow, likely in embedder. viewport_under = embedder; } } else { - //use displayserver logic + // Use DisplayServer logic. Vector2i screen_mouse_pos = DisplayServer::get_singleton()->mouse_get_position(); DisplayServer::WindowID window_id = DisplayServer::get_singleton()->get_window_at_screen_position(screen_mouse_pos); @@ -1853,7 +1830,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (window_id != DisplayServer::INVALID_WINDOW_ID) { ObjectID object_under = DisplayServer::get_singleton()->window_get_attached_instance_id(window_id); - if (object_under != ObjectID()) { //fetch window + if (object_under != ObjectID()) { // Fetch window. Window *w = Object::cast_to<Window>(ObjectDB::get_instance(object_under)); if (w) { viewport_under = w; @@ -1866,7 +1843,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (viewport_under) { Transform2D ai = (viewport_under->get_final_transform().affine_inverse() * viewport_under->_get_input_pre_xform()); viewport_pos = ai.xform(viewport_pos); - //find control under at pos + // Find control under at position. gui.drag_mouse_over = viewport_under->gui_find_control(viewport_pos); if (gui.drag_mouse_over) { Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse(); @@ -1898,7 +1875,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *over = gui_find_control(pos); if (over) { if (over->can_process()) { - touch_event = touch_event->xformed_by(Transform2D()); //make a copy + touch_event = touch_event->xformed_by(Transform2D()); // Make a copy. if (over == gui.mouse_focus) { pos = gui.focus_inv_xform.xform(pos); } else { @@ -1912,7 +1889,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } else if (touch_event->get_index() == 0 && gui.last_mouse_focus) { if (gui.last_mouse_focus->can_process()) { - touch_event = touch_event->xformed_by(Transform2D()); //make a copy + touch_event = touch_event->xformed_by(Transform2D()); // Make a copy. touch_event->set_position(gui.focus_inv_xform.xform(pos)); _gui_call_input(gui.last_mouse_focus, touch_event); @@ -1933,7 +1910,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *over = gui_find_control(pos); if (over) { if (over->can_process()) { - gesture_event = gesture_event->xformed_by(Transform2D()); //make a copy + gesture_event = gesture_event->xformed_by(Transform2D()); // Make a copy. if (over == gui.mouse_focus) { pos = gui.focus_inv_xform.xform(pos); } else { @@ -1960,7 +1937,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Vector2 speed = localizer.basis_xform(drag_event->get_speed()); Vector2 rel = localizer.basis_xform(drag_event->get_relative()); - drag_event = drag_event->xformed_by(Transform2D()); //make a copy + drag_event = drag_event->xformed_by(Transform2D()); // Make a copy. drag_event->set_speed(speed); drag_event->set_relative(rel); @@ -1982,10 +1959,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.key_focus) { gui.key_event_accepted = false; if (gui.key_focus->can_process()) { - gui.key_focus->call(SceneStringNames::get_singleton()->_gui_input, p_event); - if (gui.key_focus) { //maybe lost it - gui.key_focus->emit_signal(SceneStringNames::get_singleton()->gui_input, p_event); - } + gui.key_focus->_call_gui_input(p_event); } if (gui.key_event_accepted) { @@ -1994,12 +1968,11 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - Control *from = gui.key_focus ? gui.key_focus : nullptr; //hmm + Control *from = gui.key_focus ? gui.key_focus : nullptr; - //keyboard focus - //if (from && p_event->is_pressed() && !p_event->is_alt_pressed() && !p_event->is_meta_pressed() && !p_event->key->is_command_pressed()) { + // Keyboard focus. Ref<InputEventKey> k = p_event; - //need to check for mods, otherwise any combination of alt/ctrl/shift+<up/down/left/right/etc> is handled here when it shouldn't be. + // Need to check for mods, otherwise any combination of alt/ctrl/shift+<up/down/left/right/etc> is handled here when it shouldn't be. bool mods = k.is_valid() && (k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_shift_pressed() || k->is_meta_pressed()); if (from && p_event->is_pressed()) { @@ -2070,7 +2043,7 @@ void Viewport::_gui_set_drag_preview(Control *p_base, Control *p_control) { } p_control->set_as_top_level(true); p_control->set_position(gui.last_mouse_pos); - p_base->get_root_parent_control()->add_child(p_control); //add as child of viewport + p_base->get_root_parent_control()->add_child(p_control); // Add as child of viewport. p_control->raise(); gui.drag_preview_id = p_control->get_instance_id(); @@ -2172,8 +2145,8 @@ bool Viewport::_gui_control_has_focus(const Control *p_control) { } void Viewport::_gui_control_grab_focus(Control *p_control) { - //no need for change if (gui.key_focus && gui.key_focus == p_control) { + // No need for change. return; } get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, "_viewports", "_gui_remove_focus_for_window", (Node *)get_base_window()); @@ -2205,7 +2178,7 @@ void Viewport::_drop_mouse_focus() { mb->set_global_position(c->get_local_mouse_position()); mb->set_button_index(MouseButton(i + 1)); mb->set_pressed(false); - c->call(SceneStringNames::get_singleton()->_gui_input, mb); + c->_call_gui_input(mb); } } } @@ -2255,7 +2228,7 @@ void Viewport::_cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paus to_erase.pop_front(); } - // Per-shape + // Per-shape. List<Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *> shapes_to_erase; for (Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *E = physics_2d_shape_mouseover.front(); E; E = E->next()) { @@ -2294,7 +2267,7 @@ void Viewport::_gui_grab_click_focus(Control *p_control) { void Viewport::_post_gui_grab_click_focus() { Control *focus_grabber = gui.mouse_click_grabber; if (!focus_grabber) { - // Redundant grab requests were made + // Redundant grab requests were made. return; } gui.mouse_click_grabber = nullptr; @@ -2312,12 +2285,12 @@ void Viewport::_post_gui_grab_click_focus() { Ref<InputEventMouseButton> mb; mb.instantiate(); - //send unclick + // Send unclick. mb->set_position(click); mb->set_button_index(MouseButton(i + 1)); mb->set_pressed(false); - gui.mouse_focus->call(SceneStringNames::get_singleton()->_gui_input, mb); + gui.mouse_focus->_call_gui_input(mb); } } @@ -2330,12 +2303,12 @@ void Viewport::_post_gui_grab_click_focus() { Ref<InputEventMouseButton> mb; mb.instantiate(); - //send click + // Send click. mb->set_position(click); mb->set_button_index(MouseButton(i + 1)); mb->set_pressed(true); - gui.mouse_focus->call_deferred(SceneStringNames::get_singleton()->_gui_input, mb); + MessageQueue::get_singleton()->push_callable(callable_mp(gui.mouse_focus, &Control::_call_gui_input), mb); } } } @@ -2343,9 +2316,9 @@ void Viewport::_post_gui_grab_click_focus() { /////////////////////////////// -void Viewport::input_text(const String &p_text) { +void Viewport::push_text_input(const String &p_text) { if (gui.subwindow_focused) { - gui.subwindow_focused->input_text(p_text); + gui.subwindow_focused->push_text_input(p_text); return; } @@ -2367,7 +2340,7 @@ Viewport::SubWindowResize Viewport::_sub_window_get_resize_margin(Window *p_subw r.size.y += title_height; if (r.has_point(p_point)) { - return SUB_WINDOW_RESIZE_DISABLED; //it's inside, so no resize + return SUB_WINDOW_RESIZE_DISABLED; // It's inside, so no resize. } int dist_x = p_point.x < r.position.x ? (p_point.x - r.position.x) : (p_point.x > (r.position.x + r.size.x) ? (p_point.x - (r.position.x + r.size.x)) : 0); @@ -2426,12 +2399,12 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (gui.subwindow_drag == SUB_WINDOW_DRAG_CLOSE) { if (gui.subwindow_drag_close_rect.has_point(mb->get_position())) { - //close window + // Close window. gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST); } } gui.subwindow_drag = SUB_WINDOW_DRAG_DISABLED; - if (gui.subwindow_focused != nullptr) { //may have been erased + if (gui.subwindow_focused != nullptr) { // May have been erased. _sub_window_update(gui.subwindow_focused); } } @@ -2538,27 +2511,27 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { gui.subwindow_focused->_rect_changed_callback(r); } - if (gui.subwindow_focused) { //may have been erased + if (gui.subwindow_focused) { // May have been erased. _sub_window_update(gui.subwindow_focused); } } - return true; //handled + return true; // Handled. } Ref<InputEventMouseButton> mb = p_event; - //if the event is a mouse button, we need to check whether another window was clicked + // If the event is a mouse button, we need to check whether another window was clicked. if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { bool click_on_window = false; for (int i = gui.sub_windows.size() - 1; i >= 0; i--) { SubWindow &sw = gui.sub_windows.write[i]; - //clicked inside window? + // Clicked inside window? Rect2i r = Rect2i(sw.window->get_position(), sw.window->get_size()); if (!sw.window->get_flag(Window::FLAG_BORDERLESS)) { - //check top bar + // Check top bar. int title_height = sw.window->get_theme_constant(SNAME("title_height")); Rect2i title_bar = r; title_bar.position.y -= title_height; @@ -2576,13 +2549,13 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { close_rect.size = close_icon->get_size(); if (gui.subwindow_focused != sw.window) { - //refocus + // Refocus. _sub_window_grab_focus(sw.window); } if (close_rect.has_point(mb->get_position())) { gui.subwindow_drag = SUB_WINDOW_DRAG_CLOSE; - gui.subwindow_drag_close_inside = true; //starts inside + gui.subwindow_drag_close_inside = true; // Starts inside. gui.subwindow_drag_close_rect = close_rect; } else { gui.subwindow_drag = SUB_WINDOW_DRAG_MOVE; @@ -2603,9 +2576,9 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { } } if (!click_on_window && r.has_point(mb->get_position())) { - //clicked, see if it needs to fetch focus + // Clicked, see if it needs to fetch focus. if (gui.subwindow_focused != sw.window) { - //refocus + // Refocus. _sub_window_grab_focus(sw.window); } @@ -2618,7 +2591,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { } if (!click_on_window && gui.subwindow_focused) { - //no window found and clicked, remove focus + // No window found and clicked, remove focus. _sub_window_grab_focus(nullptr); } } @@ -2642,13 +2615,13 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { DisplayServer::get_singleton()->cursor_set_shape(shapes[resize]); - return true; //reserved for showing the resize cursor + return true; // Reserved for showing the resize cursor. } } } if (gui.subwindow_drag != SUB_WINDOW_DRAG_DISABLED) { - return true; // dragging, don't pass the event + return true; // Dragging, don't pass the event. } if (!gui.subwindow_focused) { @@ -2665,7 +2638,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { return true; } -void Viewport::input(const Ref<InputEvent> &p_event, bool p_local_coords) { +void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) { ERR_FAIL_COND(!is_inside_tree()); if (disable_input) { @@ -2695,7 +2668,7 @@ void Viewport::input(const Ref<InputEvent> &p_event, bool p_local_coords) { } if (!is_input_handled()) { - get_tree()->_call_input_pause(input_group, "_input", ev, this); //not a bug, must happen before GUI, order is _input -> gui input -> _unhandled input + get_tree()->_call_input_pause(input_group, SceneTree::CALL_INPUT_TYPE_INPUT, ev, this); //not a bug, must happen before GUI, order is _input -> gui input -> _unhandled input } if (!is_input_handled()) { @@ -2703,10 +2676,9 @@ void Viewport::input(const Ref<InputEvent> &p_event, bool p_local_coords) { } event_count++; - //get_tree()->call_group(SceneTree::GROUP_CALL_REVERSE|SceneTree::GROUP_CALL_REALTIME|SceneTree::GROUP_CALL_MULIILEVEL,gui_input_group,"_gui_input",ev); //special one for GUI, as controls use their own process check } -void Viewport::unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coords) { +void Viewport::push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coords) { ERR_FAIL_COND(p_event.is_null()); ERR_FAIL_COND(!is_inside_tree()); local_input_handled = false; @@ -2726,12 +2698,12 @@ void Viewport::unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coor ev = p_event; } - // Unhandled Input - get_tree()->_call_input_pause(unhandled_input_group, "_unhandled_input", ev, this); + // Unhandled Input. + get_tree()->_call_input_pause(unhandled_input_group, SceneTree::CALL_INPUT_TYPE_UNHANDLED_INPUT, ev, this); - // Unhandled key Input - used for performance reasons - This is called a lot less than _unhandled_input since it ignores MouseMotion, etc + // Unhandled key Input - used for performance reasons - This is called a lot less than _unhandled_input since it ignores MouseMotion, etc. if (!is_input_handled() && (Object::cast_to<InputEventKey>(*ev) != nullptr || Object::cast_to<InputEventShortcut>(*ev) != nullptr)) { - get_tree()->_call_input_pause(unhandled_key_input_group, "_unhandled_key_input", ev, this); + get_tree()->_call_input_pause(unhandled_key_input_group, SceneTree::CALL_INPUT_TYPE_UNHANDLED_KEY_INPUT, ev, this); } if (physics_object_picking && !is_input_handled()) { @@ -2740,7 +2712,7 @@ void Viewport::unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coor Object::cast_to<InputEventMouseMotion>(*ev) || Object::cast_to<InputEventScreenDrag>(*ev) || Object::cast_to<InputEventScreenTouch>(*ev) || - Object::cast_to<InputEventKey>(*ev) //to remember state + Object::cast_to<InputEventKey>(*ev) // To remember state. )) { physics_picking_events.push_back(ev); @@ -2786,10 +2758,6 @@ Variant Viewport::gui_get_drag_data() const { } TypedArray<String> Viewport::get_configuration_warnings() const { - /*if (get_parent() && !Object::cast_to<Control>(get_parent()) && !render_target) { - return TTR("This viewport is not set as render target. If you intend for it to display its contents directly to the screen, make it a child of a Control so it can obtain a size. Otherwise, make it a RenderTarget and assign its internal texture to some node for display."); - }*/ - TypedArray<String> warnings = Node::get_configuration_warnings(); if (size.x == 0 || size.y == 0) { @@ -3095,6 +3063,7 @@ bool Viewport::is_audio_listener_3d() const { } void Viewport::_update_listener_3d() { + AudioServer::get_singleton()->notify_listener_changed(); } void Viewport::_listener_transform_3d_changed_notify() { @@ -3139,7 +3108,7 @@ void Viewport::_listener_3d_make_next_current(Listener3D *p_exclude) { E->get()->make_current(); } } else { - // Attempt to reset listener to the camera position + // Attempt to reset listener to the camera position. if (camera_3d != nullptr) { _update_listener_3d(); _camera_3d_transform_changed_notify(); @@ -3152,14 +3121,14 @@ void Viewport::_collision_object_3d_input_event(CollisionObject3D *p_object, Cam Transform3D camera_transform = p_camera->get_global_transform(); ObjectID id = p_object->get_instance_id(); - //avoid sending the fake event unnecessarily if nothing really changed in the context + // Avoid sending the fake event unnecessarily if nothing really changed in the context. if (object_transform == physics_last_object_transform && camera_transform == physics_last_camera_transform && physics_last_id == id) { Ref<InputEventMouseMotion> mm = p_input_event; if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) { - return; //discarded + return; // Discarded. } } - p_object->_input_event(camera_3d, p_input_event, p_pos, p_normal, p_shape); + p_object->_input_event_call(camera_3d, p_input_event, p_pos, p_normal, p_shape); physics_last_object_transform = object_transform; physics_last_camera_transform = camera_transform; physics_last_id = id; @@ -3474,6 +3443,17 @@ void Viewport::set_use_xr(bool p_use_xr) { bool Viewport::is_using_xr() { return use_xr; } + +void Viewport::set_scale_3d(const Scale3D p_scale_3d) { + scale_3d = p_scale_3d; + + RS::get_singleton()->viewport_set_scale_3d(viewport, RS::ViewportScale3D(scale_3d)); +} + +Viewport::Scale3D Viewport::get_scale_3d() const { + return scale_3d; +} + #endif // _3D_DISABLED void Viewport::_bind_methods() { @@ -3515,9 +3495,9 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("get_physics_object_picking"), &Viewport::get_physics_object_picking); ClassDB::bind_method(D_METHOD("get_viewport_rid"), &Viewport::get_viewport_rid); - ClassDB::bind_method(D_METHOD("input_text", "text"), &Viewport::input_text); - ClassDB::bind_method(D_METHOD("input", "event", "in_local_coords"), &Viewport::input, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("unhandled_input", "event", "in_local_coords"), &Viewport::unhandled_input, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("push_text_input", "text"), &Viewport::push_text_input); + ClassDB::bind_method(D_METHOD("push_input", "event", "in_local_coords"), &Viewport::push_input, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("push_unhandled_input", "event", "in_local_coords"), &Viewport::push_unhandled_input, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_camera_2d"), &Viewport::get_camera_2d); ClassDB::bind_method(D_METHOD("set_as_audio_listener_2d", "enable"), &Viewport::set_as_audio_listener_2d); @@ -3598,8 +3578,12 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_xr", "use"), &Viewport::set_use_xr); ClassDB::bind_method(D_METHOD("is_using_xr"), &Viewport::is_using_xr); + ClassDB::bind_method(D_METHOD("set_scale_3d", "scale"), &Viewport::set_scale_3d); + ClassDB::bind_method(D_METHOD("get_scale_3d"), &Viewport::get_scale_3d); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_3d"), "set_disable_3d", "is_3d_disabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_xr"), "set_use_xr", "is_using_xr"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "scale_3d", PROPERTY_HINT_ENUM, String::utf8("Disabled,75%,50%,33%,25%")), "set_scale_3d", "get_scale_3d"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_listener_enable_3d"), "set_as_audio_listener_3d", "is_audio_listener_3d"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "own_world_3d"), "set_use_own_world_3d", "is_using_own_world_3d"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "world_3d", PROPERTY_HINT_RESOURCE_TYPE, "World3D"), "set_world_3d", "get_world_3d"); @@ -3643,6 +3627,12 @@ void Viewport::_bind_methods() { ADD_SIGNAL(MethodInfo("size_changed")); ADD_SIGNAL(MethodInfo("gui_focus_changed", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); + BIND_ENUM_CONSTANT(SCALE_3D_DISABLED); + BIND_ENUM_CONSTANT(SCALE_3D_75_PERCENT); + BIND_ENUM_CONSTANT(SCALE_3D_50_PERCENT); + BIND_ENUM_CONSTANT(SCALE_3D_33_PERCENT); + BIND_ENUM_CONSTANT(SCALE_3D_25_PERCENT); + BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED); BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_1); BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_4); @@ -3731,10 +3721,8 @@ Viewport::Viewport() { viewport_textures.insert(default_texture.ptr()); default_texture->proxy = RS::get_singleton()->texture_proxy_create(texture_rid); - //internal_listener_2d = SpatialSound2DServer::get_singleton()->listener_create(); - canvas_layers.insert(nullptr); // This eases picking code (interpreted as the canvas of the Viewport) + canvas_layers.insert(nullptr); // This eases picking code (interpreted as the canvas of the Viewport). - //clear=true; set_shadow_atlas_size(shadow_atlas_size); for (int i = 0; i < 4; i++) { @@ -3757,11 +3745,16 @@ Viewport::Viewport() { gui.tooltip_delay = GLOBAL_DEF("gui/timers/tooltip_delay_sec", 0.5); ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/tooltip_delay_sec", PropertyInfo(Variant::FLOAT, "gui/timers/tooltip_delay_sec", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater")); // No negative numbers - set_sdf_oversize(sdf_oversize); //set to server +#ifndef _3D_DISABLED + int scale = GLOBAL_GET("rendering/3d/viewport/scale"); + set_scale_3d((Scale3D)scale); +#endif // _3D_DISABLED + + set_sdf_oversize(sdf_oversize); // Set to server. } Viewport::~Viewport() { - //erase itself from viewport textures + // Erase itself from viewport textures. for (Set<ViewportTexture *>::Element *E = viewport_textures.front(); E; E = E->next()) { E->get()->vp = nullptr; } diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 9c51f404d7..06efd27073 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -41,6 +41,7 @@ class Listener3D; class World3D; #endif // _3D_DISABLED +class Listener2D; class Camera2D; class CanvasItem; class CanvasLayer; @@ -88,6 +89,14 @@ class Viewport : public Node { GDCLASS(Viewport, Node); public: + enum Scale3D { + SCALE_3D_DISABLED, + SCALE_3D_75_PERCENT, + SCALE_3D_50_PERCENT, + SCALE_3D_33_PERCENT, + SCALE_3D_25_PERCENT + }; + enum ShadowAtlasQuadrantSubdiv { SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED, SHADOW_ATLAS_QUADRANT_SUBDIV_1, @@ -193,6 +202,7 @@ private: Viewport *parent = nullptr; + Listener2D *listener_2d = nullptr; Camera2D *camera_2d = nullptr; Set<CanvasLayer *> canvas_layers; @@ -408,6 +418,10 @@ private: bool _gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check); + friend class Listener2D; + void _listener_2d_set(Listener2D *p_listener); + void _listener_2d_remove(Listener2D *p_listener); + friend class Camera2D; void _camera_2d_set(Camera2D *p_camera_2d); @@ -449,6 +463,7 @@ protected: public: uint64_t get_processed_events_count() const { return event_count; } + Listener2D *get_listener_2d() const; Camera2D *get_camera_2d() const; void set_as_audio_listener_2d(bool p_enable); bool is_audio_listener_2d() const; @@ -508,9 +523,9 @@ public: Vector2 get_camera_coords(const Vector2 &p_viewport_coords) const; Vector2 get_camera_rect_size() const; - void input_text(const String &p_text); - void input(const Ref<InputEvent> &p_event, bool p_local_coords = false); - void unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coords = false); + void push_text_input(const String &p_text); + void push_input(const Ref<InputEvent> &p_event, bool p_local_coords = false); + void push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coords = false); void set_disable_input(bool p_disable); bool is_input_disabled() const; @@ -577,6 +592,7 @@ public: #ifndef _3D_DISABLED bool use_xr = false; + Scale3D scale_3d = SCALE_3D_DISABLED; friend class Listener3D; Listener3D *listener_3d = nullptr; Set<Listener3D *> listener_3d_set; @@ -647,6 +663,9 @@ public: void set_use_xr(bool p_use_xr); bool is_using_xr(); + + void set_scale_3d(const Scale3D p_scale_3d); + Scale3D get_scale_3d() const; #endif // _3D_DISABLED Viewport(); @@ -705,6 +724,7 @@ VARIANT_ENUM_CAST(SubViewport::UpdateMode); VARIANT_ENUM_CAST(Viewport::ShadowAtlasQuadrantSubdiv); VARIANT_ENUM_CAST(Viewport::MSAA); VARIANT_ENUM_CAST(Viewport::ScreenSpaceAA); +VARIANT_ENUM_CAST(Viewport::Scale3D); VARIANT_ENUM_CAST(Viewport::DebugDraw); VARIANT_ENUM_CAST(Viewport::SDFScale); VARIANT_ENUM_CAST(Viewport::SDFOversize); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 73eec73d75..ca5a3915d0 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -659,8 +659,8 @@ void Window::_update_viewport_size() { if (!use_font_oversampling) { font_oversampling = 1.0; } - if (TS->font_get_oversampling() != font_oversampling) { - TS->font_set_oversampling(font_oversampling); + if (TS->font_get_global_oversampling() != font_oversampling) { + TS->font_set_global_oversampling(font_oversampling); } } @@ -916,14 +916,14 @@ void Window::_window_input(const Ref<InputEvent> &p_ev) { emit_signal(SceneStringNames::get_singleton()->window_input, p_ev); - input(p_ev); + push_input(p_ev); if (!is_input_handled()) { - unhandled_input(p_ev); + push_unhandled_input(p_ev); } } void Window::_window_input_text(const String &p_text) { - input_text(p_text); + push_text_input(p_text); } void Window::_window_drop_files(const Vector<String> &p_files) { @@ -1497,7 +1497,7 @@ void Window::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "position"), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size"), "set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Windowed,Minimized,Maximized,Fullscreen"), "set_mode", "get_mode"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_screen"), "set_current_screen", "get_current_screen"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current_screen"), "set_current_screen", "get_current_screen"); ADD_GROUP("Flags", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 6f8091c03f..015a4d5dba 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -49,6 +49,7 @@ #include "scene/2d/light_2d.h" #include "scene/2d/light_occluder_2d.h" #include "scene/2d/line_2d.h" +#include "scene/2d/listener_2d.h" #include "scene/2d/mesh_instance_2d.h" #include "scene/2d/multimesh_instance_2d.h" #include "scene/2d/navigation_agent_2d.h" @@ -160,6 +161,8 @@ #include "scene/resources/rectangle_shape_2d.h" #include "scene/resources/resource_format_text.h" #include "scene/resources/segment_shape_2d.h" +#include "scene/resources/separation_ray_shape_2d.h" +#include "scene/resources/separation_ray_shape_3d.h" #include "scene/resources/skeleton_modification_2d.h" #include "scene/resources/skeleton_modification_2d_ccdik.h" #include "scene/resources/skeleton_modification_2d_fabrik.h" @@ -247,12 +250,6 @@ static Ref<ResourceFormatSaverText> resource_saver_text; static Ref<ResourceFormatLoaderText> resource_loader_text; -static Ref<ResourceFormatLoaderFont> resource_loader_font; - -#ifndef DISABLE_DEPRECATED -static Ref<ResourceFormatLoaderCompatFont> resource_loader_compat_font; -#endif /* DISABLE_DEPRECATED */ - static Ref<ResourceFormatLoaderStreamTexture2D> resource_loader_stream_texture; static Ref<ResourceFormatLoaderStreamTextureLayered> resource_loader_texture_layered; static Ref<ResourceFormatLoaderStreamTexture3D> resource_loader_texture_3d; @@ -267,14 +264,6 @@ void register_scene_types() { Node::init_node_hrcr(); - resource_loader_font.instantiate(); - ResourceLoader::add_resource_format_loader(resource_loader_font); - -#ifndef DISABLE_DEPRECATED - resource_loader_compat_font.instantiate(); - ResourceLoader::add_resource_format_loader(resource_loader_compat_font); -#endif /* DISABLE_DEPRECATED */ - resource_loader_stream_texture.instantiate(); ResourceLoader::add_resource_format_loader(resource_loader_stream_texture); @@ -320,7 +309,6 @@ void register_scene_types() { OS::get_singleton()->yield(); //may take time to init - GDREGISTER_CLASS(Shortcut); GDREGISTER_CLASS(Control); GDREGISTER_CLASS(Button); GDREGISTER_CLASS(Label); @@ -501,6 +489,7 @@ void register_scene_types() { GDREGISTER_VIRTUAL_CLASS(CollisionObject3D); GDREGISTER_VIRTUAL_CLASS(PhysicsBody3D); GDREGISTER_CLASS(StaticBody3D); + GDREGISTER_CLASS(AnimatableBody3D); GDREGISTER_CLASS(RigidBody3D); GDREGISTER_CLASS(KinematicCollision3D); GDREGISTER_CLASS(CharacterBody3D); @@ -660,6 +649,7 @@ void register_scene_types() { GDREGISTER_VIRTUAL_CLASS(CollisionObject2D); GDREGISTER_VIRTUAL_CLASS(PhysicsBody2D); GDREGISTER_CLASS(StaticBody2D); + GDREGISTER_CLASS(AnimatableBody2D); GDREGISTER_CLASS(RigidBody2D); GDREGISTER_CLASS(CharacterBody2D); GDREGISTER_CLASS(KinematicCollision2D); @@ -682,6 +672,7 @@ void register_scene_types() { OS::get_singleton()->yield(); //may take time to init GDREGISTER_CLASS(Camera2D); + GDREGISTER_CLASS(Listener2D); GDREGISTER_VIRTUAL_CLASS(Joint2D); GDREGISTER_CLASS(PinJoint2D); GDREGISTER_CLASS(GrooveJoint2D); @@ -753,6 +744,7 @@ void register_scene_types() { OS::get_singleton()->yield(); //may take time to init GDREGISTER_VIRTUAL_CLASS(Shape3D); + GDREGISTER_CLASS(SeparationRayShape3D); GDREGISTER_CLASS(SphereShape3D); GDREGISTER_CLASS(BoxShape3D); GDREGISTER_CLASS(CapsuleShape3D); @@ -841,6 +833,7 @@ void register_scene_types() { GDREGISTER_VIRTUAL_CLASS(Shape2D); GDREGISTER_CLASS(WorldMarginShape2D); GDREGISTER_CLASS(SegmentShape2D); + GDREGISTER_CLASS(SeparationRayShape2D); GDREGISTER_CLASS(CircleShape2D); GDREGISTER_CLASS(RectangleShape2D); GDREGISTER_CLASS(CapsuleShape2D); @@ -961,6 +954,8 @@ void register_scene_types() { ClassDB::add_compatibility_class("ProceduralSky", "Sky"); ClassDB::add_compatibility_class("ProximityGroup", "ProximityGroup3D"); ClassDB::add_compatibility_class("RayCast", "RayCast3D"); + ClassDB::add_compatibility_class("RayShape", "SeparationRayShape3D"); + ClassDB::add_compatibility_class("RayShape2D", "SeparationRayShape2D"); ClassDB::add_compatibility_class("RemoteTransform", "RemoteTransform3D"); ClassDB::add_compatibility_class("RigidBody", "RigidBody3D"); ClassDB::add_compatibility_class("Shape", "Shape3D"); @@ -1066,14 +1061,6 @@ void unregister_scene_types() { SceneDebugger::deinitialize(); clear_default_theme(); - ResourceLoader::remove_resource_format_loader(resource_loader_font); - resource_loader_font.unref(); - -#ifndef DISABLE_DEPRECATED - ResourceLoader::remove_resource_format_loader(resource_loader_compat_font); - resource_loader_compat_font.unref(); -#endif /* DISABLE_DEPRECATED */ - ResourceLoader::remove_resource_format_loader(resource_loader_texture_layered); resource_loader_texture_layered.unref(); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index e6a74e7685..b4eec2530b 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -29,9 +29,9 @@ /*************************************************************************/ #include "animation.h" -#include "scene/scene_string_names.h" #include "core/math/geometry_3d.h" +#include "scene/scene_string_names.h" bool Animation::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; @@ -79,15 +79,16 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { TransformTrack *tt = static_cast<TransformTrack *>(tracks[track]); Vector<real_t> values = p_value; int vcount = values.size(); - ERR_FAIL_COND_V(vcount % 12, false); // should be multiple of 12 + ERR_FAIL_COND_V(vcount % TRANSFORM_TRACK_SIZE, false); const real_t *r = values.ptr(); - tt->transforms.resize(vcount / 12); + int64_t count = vcount / TRANSFORM_TRACK_SIZE; + tt->transforms.resize(count); - for (int i = 0; i < (vcount / 12); i++) { + for (int i = 0; i < count; i++) { TKey<TransformKey> &tk = tt->transforms.write[i]; - const real_t *ofs = &r[i * 12]; + const real_t *ofs = &r[i * TRANSFORM_TRACK_SIZE]; tk.time = ofs[0]; tk.transition = ofs[1]; @@ -354,7 +355,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { if (track_get_type(track) == TYPE_TRANSFORM3D) { Vector<real_t> keys; int kk = track_get_key_count(track); - keys.resize(kk * sizeof(Transform3D)); + keys.resize(kk * TRANSFORM_TRACK_SIZE); real_t *w = keys.ptrw(); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 6227f6967f..9a410bd566 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -92,6 +92,9 @@ private: Vector3 scale; }; + // Not necessarily the same size as Transform3D. The amount of numbers in Animation::Key and TransformKey. + const int32_t TRANSFORM_TRACK_SIZE = 12; + /* TRANSFORM TRACK */ struct TransformTrack : public Track { diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp index ef070589e4..d018103e64 100644 --- a/scene/resources/audio_stream_sample.cpp +++ b/scene/resources/audio_stream_sample.cpp @@ -221,12 +221,12 @@ void AudioStreamPlaybackSample::do_resample(const Depth *p_src, AudioFrame *p_ds } } -void AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { +int AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { if (!base->data || !active) { for (int i = 0; i < p_frames; i++) { p_buffer[i] = AudioFrame(0, 0); } - return; + return 0; } int len = base->data_bytes; @@ -395,12 +395,15 @@ void AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, in } if (todo) { + int mixed_frames = p_frames - todo; //bit was missing from mix int todo_ofs = p_frames - todo; for (int i = todo_ofs; i < p_frames; i++) { p_buffer[i] = AudioFrame(0, 0); } + return mixed_frames; } + return p_frames; } AudioStreamPlaybackSample::AudioStreamPlaybackSample() {} @@ -477,6 +480,10 @@ float AudioStreamSample::get_length() const { return float(len) / mix_rate; } +bool AudioStreamSample::is_monophonic() const { + return false; +} + void AudioStreamSample::set_data(const Vector<uint8_t> &p_data) { AudioServer::get_singleton()->lock(); if (data) { diff --git a/scene/resources/audio_stream_sample.h b/scene/resources/audio_stream_sample.h index 70b8ba79ad..24198e3c98 100644 --- a/scene/resources/audio_stream_sample.h +++ b/scene/resources/audio_stream_sample.h @@ -73,7 +73,7 @@ public: virtual float get_playback_position() const override; virtual void seek(float p_time) override; - virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; + virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; AudioStreamPlaybackSample(); }; @@ -136,6 +136,8 @@ public: virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; + void set_data(const Vector<uint8_t> &p_data); Vector<uint8_t> get_data() const; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 4e139adabb..4845c556c6 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -212,26 +212,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("outline_size", "LinkButton", 0); theme->set_constant("underline_spacing", "LinkButton", 2 * scale); - // ColorPickerButton - - theme->set_stylebox("normal", "ColorPickerButton", sb_button_normal); - theme->set_stylebox("pressed", "ColorPickerButton", sb_button_pressed); - theme->set_stylebox("hover", "ColorPickerButton", sb_button_hover); - theme->set_stylebox("disabled", "ColorPickerButton", sb_button_disabled); - theme->set_stylebox("focus", "ColorPickerButton", sb_button_focus); - - theme->set_font("font", "ColorPickerButton", Ref<Font>()); - theme->set_font_size("font_size", "ColorPickerButton", -1); - - theme->set_color("font_color", "ColorPickerButton", Color(1, 1, 1, 1)); - theme->set_color("font_pressed_color", "ColorPickerButton", Color(0.8, 0.8, 0.8, 1)); - theme->set_color("font_hover_color", "ColorPickerButton", Color(1, 1, 1, 1)); - theme->set_color("font_disabled_color", "ColorPickerButton", Color(0.9, 0.9, 0.9, 0.3)); - theme->set_color("font_outline_color", "ColorPickerButton", Color(1, 1, 1)); - - theme->set_constant("hseparation", "ColorPickerButton", 2 * scale); - theme->set_constant("outline_size", "ColorPickerButton", 0); - // OptionButton Ref<StyleBox> sb_optbutton_focus = sb_expand(make_stylebox(button_focus_png, 4, 4, 4, 4, 6, 2, 6, 2), 2, 2, 2, 2); @@ -859,7 +839,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("add_preset", "ColorPicker", make_icon(icon_add_png)); theme->set_icon("color_hue", "ColorPicker", make_icon(color_picker_hue_png)); theme->set_icon("color_sample", "ColorPicker", make_icon(color_picker_sample_png)); - theme->set_icon("preset_bg", "ColorPicker", make_icon(mini_checkerboard_png)); + theme->set_icon("sample_bg", "ColorPicker", make_icon(mini_checkerboard_png)); theme->set_icon("overbright_indicator", "ColorPicker", make_icon(overbright_indicator_png)); theme->set_icon("bar_arrow", "ColorPicker", make_icon(bar_arrow_png)); theme->set_icon("picker_cursor", "ColorPicker", make_icon(picker_cursor_png)); @@ -867,6 +847,34 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // ColorPickerButton theme->set_icon("bg", "ColorPickerButton", make_icon(mini_checkerboard_png)); + theme->set_stylebox("normal", "ColorPickerButton", sb_button_normal); + theme->set_stylebox("pressed", "ColorPickerButton", sb_button_pressed); + theme->set_stylebox("hover", "ColorPickerButton", sb_button_hover); + theme->set_stylebox("disabled", "ColorPickerButton", sb_button_disabled); + theme->set_stylebox("focus", "ColorPickerButton", sb_button_focus); + + theme->set_font("font", "ColorPickerButton", Ref<Font>()); + theme->set_font_size("font_size", "ColorPickerButton", -1); + + theme->set_color("font_color", "ColorPickerButton", Color(1, 1, 1, 1)); + theme->set_color("font_pressed_color", "ColorPickerButton", Color(0.8, 0.8, 0.8, 1)); + theme->set_color("font_hover_color", "ColorPickerButton", Color(1, 1, 1, 1)); + theme->set_color("font_disabled_color", "ColorPickerButton", Color(0.9, 0.9, 0.9, 0.3)); + theme->set_color("font_outline_color", "ColorPickerButton", Color(1, 1, 1)); + + theme->set_constant("hseparation", "ColorPickerButton", 2 * scale); + theme->set_constant("outline_size", "ColorPickerButton", 0); + + // ColorPresetButton + + Ref<StyleBoxFlat> preset_sb = make_flat_stylebox(Color(1, 1, 1), 2, 2, 2, 2); + preset_sb->set_corner_radius_all(2); + preset_sb->set_corner_detail(2); + preset_sb->set_anti_aliased(false); + + theme->set_stylebox("preset_fg", "ColorPresetButton", preset_sb); + theme->set_icon("preset_bg", "ColorPresetButton", make_icon(mini_checkerboard_png)); + theme->set_icon("overbright_indicator", "ColorPresetButton", make_icon(overbright_indicator_png)); // TooltipPanel @@ -1012,8 +1020,9 @@ void make_default_theme(bool p_hidpi, Ref<Font> p_font) { Ref<FontData> dynamic_font_data; dynamic_font_data.instantiate(); - dynamic_font_data->load_memory(_font_OpenSans_SemiBold, _font_OpenSans_SemiBold_size, "ttf", default_font_size); + dynamic_font_data->set_data_ptr(_font_OpenSans_SemiBold, _font_OpenSans_SemiBold_size); dynamic_font->add_data(dynamic_font_data); + dynamic_font->set_base_size(default_font_size); default_font = dynamic_font; } diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 032171847d..7af8e900fd 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -36,54 +36,129 @@ #include "scene/resources/text_line.h" #include "scene/resources/text_paragraph.h" -void FontData::_bind_methods() { - ClassDB::bind_method(D_METHOD("load_resource", "filename", "base_size"), &FontData::load_resource, DEFVAL(16)); - ClassDB::bind_method(D_METHOD("load_memory", "data", "type", "base_size"), &FontData::_load_memory, DEFVAL(16)); - ClassDB::bind_method(D_METHOD("new_bitmap", "height", "ascent", "base_size"), &FontData::new_bitmap); - - ClassDB::bind_method(D_METHOD("bitmap_add_texture", "texture"), &FontData::bitmap_add_texture); - ClassDB::bind_method(D_METHOD("bitmap_add_char", "char", "texture_idx", "rect", "align", "advance"), &FontData::bitmap_add_char); - ClassDB::bind_method(D_METHOD("bitmap_add_kerning_pair", "A", "B", "kerning"), &FontData::bitmap_add_kerning_pair); +_FORCE_INLINE_ void FontData::_clear_cache() { + for (int i = 0; i < cache.size(); i++) { + if (cache[i].is_valid()) { + TS->free(cache[i]); + cache.write[i] = RID(); + } + } +} - ClassDB::bind_method(D_METHOD("set_data_path", "path"), &FontData::set_data_path); - ClassDB::bind_method(D_METHOD("get_data_path"), &FontData::get_data_path); +_FORCE_INLINE_ void FontData::_ensure_rid(int p_cache_index) const { + if (unlikely(p_cache_index >= cache.size())) { + cache.resize(p_cache_index + 1); + } + if (unlikely(!cache[p_cache_index].is_valid())) { + cache.write[p_cache_index] = TS->create_font(); + TS->font_set_data_ptr(cache[p_cache_index], data_ptr, data_size); + TS->font_set_antialiased(cache[p_cache_index], antialiased); + TS->font_set_multichannel_signed_distance_field(cache[p_cache_index], msdf); + TS->font_set_msdf_pixel_range(cache[p_cache_index], msdf_pixel_range); + TS->font_set_msdf_size(cache[p_cache_index], msdf_size); + TS->font_set_fixed_size(cache[p_cache_index], fixed_size); + TS->font_set_force_autohinter(cache[p_cache_index], force_autohinter); + TS->font_set_hinting(cache[p_cache_index], hinting); + TS->font_set_oversampling(cache[p_cache_index], oversampling); + } +} - ClassDB::bind_method(D_METHOD("get_height", "size"), &FontData::get_height); - ClassDB::bind_method(D_METHOD("get_ascent", "size"), &FontData::get_ascent); - ClassDB::bind_method(D_METHOD("get_descent", "size"), &FontData::get_descent); +void FontData::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_data", "data"), &FontData::set_data); + ClassDB::bind_method(D_METHOD("get_data"), &FontData::get_data); - ClassDB::bind_method(D_METHOD("get_underline_position", "size"), &FontData::get_underline_position); - ClassDB::bind_method(D_METHOD("get_underline_thickness", "size"), &FontData::get_underline_thickness); + ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &FontData::set_antialiased); + ClassDB::bind_method(D_METHOD("is_antialiased"), &FontData::is_antialiased); - ClassDB::bind_method(D_METHOD("get_spacing", "type"), &FontData::get_spacing); - ClassDB::bind_method(D_METHOD("set_spacing", "type", "value"), &FontData::set_spacing); + ClassDB::bind_method(D_METHOD("set_multichannel_signed_distance_field", "msdf"), &FontData::set_multichannel_signed_distance_field); + ClassDB::bind_method(D_METHOD("is_multichannel_signed_distance_field"), &FontData::is_multichannel_signed_distance_field); - ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &FontData::set_antialiased); - ClassDB::bind_method(D_METHOD("get_antialiased"), &FontData::get_antialiased); + ClassDB::bind_method(D_METHOD("set_msdf_pixel_range", "msdf_pixel_range"), &FontData::set_msdf_pixel_range); + ClassDB::bind_method(D_METHOD("get_msdf_pixel_range"), &FontData::get_msdf_pixel_range); - ClassDB::bind_method(D_METHOD("get_variation_list"), &FontData::get_variation_list); + ClassDB::bind_method(D_METHOD("set_msdf_size", "msdf_size"), &FontData::set_msdf_size); + ClassDB::bind_method(D_METHOD("get_msdf_size"), &FontData::get_msdf_size); - ClassDB::bind_method(D_METHOD("set_variation", "tag", "value"), &FontData::set_variation); - ClassDB::bind_method(D_METHOD("get_variation", "tag"), &FontData::get_variation); + ClassDB::bind_method(D_METHOD("set_force_autohinter", "force_autohinter"), &FontData::set_force_autohinter); + ClassDB::bind_method(D_METHOD("is_force_autohinter"), &FontData::is_force_autohinter); ClassDB::bind_method(D_METHOD("set_hinting", "hinting"), &FontData::set_hinting); ClassDB::bind_method(D_METHOD("get_hinting"), &FontData::get_hinting); - ClassDB::bind_method(D_METHOD("set_force_autohinter", "enabled"), &FontData::set_force_autohinter); - ClassDB::bind_method(D_METHOD("get_force_autohinter"), &FontData::get_force_autohinter); + ClassDB::bind_method(D_METHOD("set_oversampling", "oversampling"), &FontData::set_oversampling); + ClassDB::bind_method(D_METHOD("get_oversampling"), &FontData::get_oversampling); - ClassDB::bind_method(D_METHOD("set_distance_field_hint", "distance_field"), &FontData::set_distance_field_hint); - ClassDB::bind_method(D_METHOD("get_distance_field_hint"), &FontData::get_distance_field_hint); + ClassDB::bind_method(D_METHOD("find_cache", "variation_coordinates"), &FontData::find_cache); - ClassDB::bind_method(D_METHOD("has_char", "char"), &FontData::has_char); - ClassDB::bind_method(D_METHOD("get_supported_chars"), &FontData::get_supported_chars); + ClassDB::bind_method(D_METHOD("get_cache_count"), &FontData::get_cache_count); + ClassDB::bind_method(D_METHOD("clear_cache"), &FontData::clear_cache); + ClassDB::bind_method(D_METHOD("remove_cache", "cache_index"), &FontData::remove_cache); + + ClassDB::bind_method(D_METHOD("get_size_cache_list", "cache_index"), &FontData::get_size_cache_list); + ClassDB::bind_method(D_METHOD("clear_size_cache", "cache_index"), &FontData::clear_size_cache); + ClassDB::bind_method(D_METHOD("remove_size_cache", "cache_index", "size"), &FontData::remove_size_cache); + + ClassDB::bind_method(D_METHOD("set_variation_coordinates", "cache_index", "variation_coordinates"), &FontData::set_variation_coordinates); + ClassDB::bind_method(D_METHOD("get_variation_coordinates", "cache_index"), &FontData::get_variation_coordinates); + + ClassDB::bind_method(D_METHOD("set_ascent", "cache_index", "size", "ascent"), &FontData::set_ascent); + ClassDB::bind_method(D_METHOD("get_ascent", "cache_index", "size"), &FontData::get_ascent); + + ClassDB::bind_method(D_METHOD("set_descent", "cache_index", "size", "descent"), &FontData::set_descent); + ClassDB::bind_method(D_METHOD("get_descent", "cache_index", "size"), &FontData::get_descent); + + ClassDB::bind_method(D_METHOD("set_underline_position", "cache_index", "size", "underline_position"), &FontData::set_underline_position); + ClassDB::bind_method(D_METHOD("get_underline_position", "cache_index", "size"), &FontData::get_underline_position); + + ClassDB::bind_method(D_METHOD("set_underline_thickness", "cache_index", "size", "underline_thickness"), &FontData::set_underline_thickness); + ClassDB::bind_method(D_METHOD("get_underline_thickness", "cache_index", "size"), &FontData::get_underline_thickness); + + ClassDB::bind_method(D_METHOD("set_scale", "cache_index", "size", "scale"), &FontData::set_scale); + ClassDB::bind_method(D_METHOD("get_scale", "cache_index", "size"), &FontData::get_scale); + + ClassDB::bind_method(D_METHOD("set_spacing", "cache_index", "size", "spacing"), &FontData::set_spacing); + ClassDB::bind_method(D_METHOD("get_spacing", "cache_index", "size"), &FontData::get_spacing); + + ClassDB::bind_method(D_METHOD("get_texture_count", "cache_index", "size"), &FontData::get_texture_count); + ClassDB::bind_method(D_METHOD("clear_textures", "cache_index", "size"), &FontData::clear_textures); + ClassDB::bind_method(D_METHOD("remove_texture", "cache_index", "size", "texture_index"), &FontData::remove_texture); + + ClassDB::bind_method(D_METHOD("set_texture_image", "cache_index", "size", "texture_index", "image"), &FontData::set_texture_image); + ClassDB::bind_method(D_METHOD("get_texture_image", "cache_index", "size", "texture_index"), &FontData::get_texture_image); + + ClassDB::bind_method(D_METHOD("set_texture_offsets", "cache_index", "size", "texture_index", "offset"), &FontData::set_texture_offsets); + ClassDB::bind_method(D_METHOD("get_texture_offsets", "cache_index", "size", "texture_index"), &FontData::get_texture_offsets); + + ClassDB::bind_method(D_METHOD("get_glyph_list", "cache_index", "size"), &FontData::get_glyph_list); + ClassDB::bind_method(D_METHOD("clear_glyphs", "cache_index", "size"), &FontData::clear_glyphs); + ClassDB::bind_method(D_METHOD("remove_glyph", "cache_index", "size", "glyph"), &FontData::remove_glyph); + + ClassDB::bind_method(D_METHOD("set_glyph_advance", "cache_index", "size", "glyph", "advance"), &FontData::set_glyph_advance); + ClassDB::bind_method(D_METHOD("get_glyph_advance", "cache_index", "size", "glyph"), &FontData::get_glyph_advance); + + ClassDB::bind_method(D_METHOD("set_glyph_offset", "cache_index", "size", "glyph", "offset"), &FontData::set_glyph_offset); + ClassDB::bind_method(D_METHOD("get_glyph_offset", "cache_index", "size", "glyph"), &FontData::get_glyph_offset); + + ClassDB::bind_method(D_METHOD("set_glyph_size", "cache_index", "size", "glyph", "gl_size"), &FontData::set_glyph_size); + ClassDB::bind_method(D_METHOD("get_glyph_size", "cache_index", "size", "glyph"), &FontData::get_glyph_size); - ClassDB::bind_method(D_METHOD("get_glyph_advance", "index", "size"), &FontData::get_glyph_advance); - ClassDB::bind_method(D_METHOD("get_glyph_kerning", "index_a", "index_b", "size"), &FontData::get_glyph_kerning); + ClassDB::bind_method(D_METHOD("set_glyph_uv_rect", "cache_index", "size", "glyph", "uv_rect"), &FontData::set_glyph_uv_rect); + ClassDB::bind_method(D_METHOD("get_glyph_uv_rect", "cache_index", "size", "glyph"), &FontData::get_glyph_uv_rect); - ClassDB::bind_method(D_METHOD("get_base_size"), &FontData::get_base_size); + ClassDB::bind_method(D_METHOD("set_glyph_texture_idx", "cache_index", "size", "glyph", "texture_idx"), &FontData::set_glyph_texture_idx); + ClassDB::bind_method(D_METHOD("get_glyph_texture_idx", "cache_index", "size", "glyph"), &FontData::get_glyph_texture_idx); - ClassDB::bind_method(D_METHOD("has_outline"), &FontData::has_outline); + ClassDB::bind_method(D_METHOD("get_kerning_list", "cache_index", "size"), &FontData::get_kerning_list); + ClassDB::bind_method(D_METHOD("clear_kerning_map", "cache_index", "size"), &FontData::clear_kerning_map); + ClassDB::bind_method(D_METHOD("remove_kerning", "cache_index", "size", "glyph_pair"), &FontData::remove_kerning); + + ClassDB::bind_method(D_METHOD("set_kerning", "cache_index", "size", "glyph_pair", "kerning"), &FontData::set_kerning); + ClassDB::bind_method(D_METHOD("get_kerning", "cache_index", "size", "glyph_pair"), &FontData::get_kerning); + + ClassDB::bind_method(D_METHOD("render_range", "cache_index", "size", "start", "end"), &FontData::render_range); + ClassDB::bind_method(D_METHOD("render_glyph", "cache_index", "size", "index"), &FontData::render_glyph); + + ClassDB::bind_method(D_METHOD("get_cache_rid", "cache_index"), &FontData::get_cache_rid); ClassDB::bind_method(D_METHOD("is_language_supported", "language"), &FontData::is_language_supported); ClassDB::bind_method(D_METHOD("set_language_support_override", "language", "supported"), &FontData::set_language_support_override); @@ -97,511 +172,915 @@ void FontData::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_script_support_override", "script"), &FontData::remove_script_support_override); ClassDB::bind_method(D_METHOD("get_script_support_overrides"), &FontData::get_script_support_overrides); - ClassDB::bind_method(D_METHOD("get_glyph_index", "char", "variation_selector"), &FontData::get_glyph_index, DEFVAL(0x0000)); - ClassDB::bind_method(D_METHOD("draw_glyph", "canvas", "size", "pos", "index", "color"), &FontData::draw_glyph, DEFVAL(Color(1, 1, 1))); - ClassDB::bind_method(D_METHOD("draw_glyph_outline", "canvas", "size", "outline_size", "pos", "index", "color"), &FontData::draw_glyph_outline, DEFVAL(Color(1, 1, 1))); - - ADD_PROPERTY(PropertyInfo(Variant::STRING, "data_path", PROPERTY_HINT_FILE, "*.ttf,*.otf,*.woff,*.fnt,*.font"), "set_data_path", "get_data_path"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "antialiased"), "set_antialiased", "get_antialiased"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_autohinter"), "set_force_autohinter", "get_force_autohinter"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distance_field_hint"), "set_distance_field_hint", "get_distance_field_hint"); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), "set_hinting", "get_hinting"); + ClassDB::bind_method(D_METHOD("has_char", "char"), &FontData::has_char); + ClassDB::bind_method(D_METHOD("get_supported_chars"), &FontData::get_supported_chars); - ADD_GROUP("Extra Spacing", "extra_spacing"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "extra_spacing_glyph"), "set_spacing", "get_spacing", SPACING_GLYPH); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "extra_spacing_space"), "set_spacing", "get_spacing", SPACING_SPACE); + ClassDB::bind_method(D_METHOD("get_glyph_index", "char", "variation_selector"), &FontData::get_glyph_index); - BIND_ENUM_CONSTANT(SPACING_GLYPH); - BIND_ENUM_CONSTANT(SPACING_SPACE); + ClassDB::bind_method(D_METHOD("get_supported_feature_list"), &FontData::get_supported_feature_list); + ClassDB::bind_method(D_METHOD("get_supported_variation_list"), &FontData::get_supported_variation_list); } bool FontData::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("language_support_override/")) { - String lang = str.get_slicec('/', 1); - if (lang == "_new") { - return false; + Vector<String> tokens = p_name.operator String().split("/"); + if (tokens.size() == 1) { + if (tokens[0] == "data") { + set_data(p_value); + return true; + } else if (tokens[0] == "antialiased") { + set_antialiased(p_value); + return true; + } else if (tokens[0] == "multichannel_signed_distance_field") { + set_multichannel_signed_distance_field(p_value); + return true; + } else if (tokens[0] == "msdf_pixel_range") { + set_msdf_pixel_range(p_value); + return true; + } else if (tokens[0] == "msdf_size") { + set_msdf_size(p_value); + return true; + } else if (tokens[0] == "fixed_size") { + set_fixed_size(p_value); + return true; + } else if (tokens[0] == "hinting") { + set_hinting((TextServer::Hinting)p_value.operator int()); + return true; + } else if (tokens[0] == "force_autohinter") { + set_force_autohinter(p_value); + return true; + } else if (tokens[0] == "oversampling") { + set_oversampling(p_value); + return true; } + } else if (tokens.size() == 2 && tokens[0] == "language_support_override") { + String lang = tokens[1]; set_language_support_override(lang, p_value); return true; - } - if (str.begins_with("script_support_override/")) { - String scr = str.get_slicec('/', 1); - if (scr == "_new") { - return false; - } - set_script_support_override(scr, p_value); - return true; - } - if (str.begins_with("variation/")) { - String name = str.get_slicec('/', 1); - set_variation(name, p_value); + } else if (tokens.size() == 2 && tokens[0] == "script_support_override") { + String script = tokens[1]; + set_script_support_override(script, p_value); return true; + } else if (tokens.size() >= 3 && tokens[0] == "cache") { + int cache_index = tokens[1].to_int(); + if (tokens.size() == 3 && tokens[2] == "variation_coordinates") { + set_variation_coordinates(cache_index, p_value); + return true; + } + if (tokens.size() >= 5) { + Vector2i sz = Vector2i(tokens[2].to_int(), tokens[3].to_int()); + if (tokens[4] == "ascent") { + set_ascent(cache_index, sz.x, p_value); + return true; + } else if (tokens[4] == "descent") { + set_descent(cache_index, sz.x, p_value); + return true; + } else if (tokens[4] == "underline_position") { + set_underline_position(cache_index, sz.x, p_value); + return true; + } else if (tokens[4] == "underline_thickness") { + set_underline_thickness(cache_index, sz.x, p_value); + return true; + } else if (tokens[4] == "scale") { + set_scale(cache_index, sz.x, p_value); + return true; + } else if (tokens[4] == "spacing_glyph") { + set_spacing(cache_index, sz.x, TextServer::SPACING_GLYPH, p_value); + return true; + } else if (tokens[4] == "spacing_space") { + set_spacing(cache_index, sz.x, TextServer::SPACING_SPACE, p_value); + return true; + } else if (tokens.size() == 7 && tokens[4] == "textures") { + int texture_index = tokens[5].to_int(); + if (tokens[6] == "image") { + set_texture_image(cache_index, sz, texture_index, p_value); + return true; + } else if (tokens[6] == "offsets") { + set_texture_offsets(cache_index, sz, texture_index, p_value); + return true; + } + } else if (tokens.size() == 7 && tokens[4] == "glyphs") { + int32_t glyph_index = tokens[5].to_int(); + if (tokens[6] == "advance") { + set_glyph_advance(cache_index, sz.x, glyph_index, p_value); + return true; + } else if (tokens[6] == "offset") { + set_glyph_offset(cache_index, sz, glyph_index, p_value); + return true; + } else if (tokens[6] == "size") { + set_glyph_size(cache_index, sz, glyph_index, p_value); + return true; + } else if (tokens[6] == "uv_rect") { + set_glyph_uv_rect(cache_index, sz, glyph_index, p_value); + return true; + } else if (tokens[6] == "texture_idx") { + set_glyph_texture_idx(cache_index, sz, glyph_index, p_value); + return true; + } + } else if (tokens.size() == 7 && tokens[4] == "kerning_overrides") { + Vector2i gp = Vector2i(tokens[5].to_int(), tokens[6].to_int()); + set_kerning(cache_index, sz.x, gp, p_value); + return true; + } + } } - return false; } bool FontData::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("language_support_override/")) { - String lang = str.get_slicec('/', 1); - if (lang == "_new") { + Vector<String> tokens = p_name.operator String().split("/"); + if (tokens.size() == 1) { + if (tokens[0] == "data") { + r_ret = get_data(); + return true; + } else if (tokens[0] == "antialiased") { + r_ret = is_antialiased(); + return true; + } else if (tokens[0] == "multichannel_signed_distance_field") { + r_ret = is_multichannel_signed_distance_field(); + return true; + } else if (tokens[0] == "msdf_pixel_range") { + r_ret = get_msdf_pixel_range(); + return true; + } else if (tokens[0] == "msdf_size") { + r_ret = get_msdf_size(); + return true; + } else if (tokens[0] == "fixed_size") { + r_ret = get_fixed_size(); + return true; + } else if (tokens[0] == "hinting") { + r_ret = get_hinting(); + return true; + } else if (tokens[0] == "force_autohinter") { + r_ret = is_force_autohinter(); + return true; + } else if (tokens[0] == "oversampling") { + r_ret = get_oversampling(); return true; } + } else if (tokens.size() == 2 && tokens[0] == "language_support_override") { + String lang = tokens[1]; r_ret = get_language_support_override(lang); return true; - } - if (str.begins_with("script_support_override/")) { - String scr = str.get_slicec('/', 1); - if (scr == "_new") { + } else if (tokens.size() == 2 && tokens[0] == "script_support_override") { + String script = tokens[1]; + r_ret = get_script_support_override(script); + return true; + } else if (tokens.size() >= 3 && tokens[0] == "cache") { + int cache_index = tokens[1].to_int(); + if (tokens.size() == 3 && tokens[2] == "variation_coordinates") { + r_ret = get_variation_coordinates(cache_index); return true; } - r_ret = get_script_support_override(scr); - return true; - } - if (str.begins_with("variation/")) { - String name = str.get_slicec('/', 1); - - r_ret = get_variation(name); - return true; + if (tokens.size() >= 5) { + Vector2i sz = Vector2i(tokens[2].to_int(), tokens[3].to_int()); + if (tokens[4] == "ascent") { + r_ret = get_ascent(cache_index, sz.x); + return true; + } else if (tokens[4] == "descent") { + r_ret = get_descent(cache_index, sz.x); + return true; + } else if (tokens[4] == "underline_position") { + r_ret = get_underline_position(cache_index, sz.x); + return true; + } else if (tokens[4] == "underline_thickness") { + r_ret = get_underline_thickness(cache_index, sz.x); + return true; + } else if (tokens[4] == "scale") { + r_ret = get_scale(cache_index, sz.x); + return true; + } else if (tokens[4] == "spacing_glyph") { + r_ret = get_spacing(cache_index, sz.x, TextServer::SPACING_GLYPH); + return true; + } else if (tokens[4] == "spacing_space") { + r_ret = get_spacing(cache_index, sz.x, TextServer::SPACING_SPACE); + return true; + } else if (tokens.size() == 7 && tokens[4] == "textures") { + int texture_index = tokens[5].to_int(); + if (tokens[6] == "image") { + r_ret = get_texture_image(cache_index, sz, texture_index); + return true; + } else if (tokens[6] == "offsets") { + r_ret = get_texture_offsets(cache_index, sz, texture_index); + return true; + } + } else if (tokens.size() == 7 && tokens[4] == "glyphs") { + int32_t glyph_index = tokens[5].to_int(); + if (tokens[6] == "advance") { + r_ret = get_glyph_advance(cache_index, sz.x, glyph_index); + return true; + } else if (tokens[6] == "offset") { + r_ret = get_glyph_offset(cache_index, sz, glyph_index); + return true; + } else if (tokens[6] == "size") { + r_ret = get_glyph_size(cache_index, sz, glyph_index); + return true; + } else if (tokens[6] == "uv_rect") { + r_ret = get_glyph_uv_rect(cache_index, sz, glyph_index); + return true; + } else if (tokens[6] == "texture_idx") { + r_ret = get_glyph_texture_idx(cache_index, sz, glyph_index); + return true; + } + } else if (tokens.size() == 7 && tokens[4] == "kerning_overrides") { + Vector2i gp = Vector2i(tokens[5].to_int(), tokens[6].to_int()); + r_ret = get_kerning(cache_index, sz.x, gp); + return true; + } + } } - return false; } void FontData::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + + p_list->push_back(PropertyInfo(Variant::BOOL, "antialiased", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, "fixed_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::BOOL, "force_autohinter", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + Vector<String> lang_over = get_language_support_overrides(); for (int i = 0; i < lang_over.size(); i++) { - p_list->push_back(PropertyInfo(Variant::BOOL, "language_support_override/" + lang_over[i], PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::BOOL, "language_support_override/" + lang_over[i], PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); } - p_list->push_back(PropertyInfo(Variant::NIL, "language_support_override/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); - Vector<String> scr_over = get_script_support_overrides(); for (int i = 0; i < scr_over.size(); i++) { - p_list->push_back(PropertyInfo(Variant::BOOL, "script_support_override/" + scr_over[i], PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "script_support_override/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + p_list->push_back(PropertyInfo(Variant::BOOL, "script_support_override/" + scr_over[i], PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + } + for (int i = 0; i < cache.size(); i++) { + String prefix = "cache/" + itos(i) + "/"; + Array sizes = get_size_cache_list(i); + p_list->push_back(PropertyInfo(Variant::DICTIONARY, prefix + "variation_coordinates", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + for (int j = 0; j < sizes.size(); j++) { + Vector2i sz = sizes[j]; + String prefix_sz = prefix + itos(sz.x) + "/" + itos(sz.y) + "/"; + if (sz.y == 0) { + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "ascent", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "descent", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "underline_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "underline_thickness", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::BOOL, prefix_sz + "spacing_glyph", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::BOOL, prefix_sz + "spacing_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + } - Dictionary variations = get_variation_list(); - for (const Variant *ftr = variations.next(nullptr); ftr != nullptr; ftr = variations.next(ftr)) { - Vector3i v = variations[*ftr]; - p_list->push_back(PropertyInfo(Variant::FLOAT, "variation/" + TS->tag_to_name(*ftr), PROPERTY_HINT_RANGE, itos(v.x) + "," + itos(v.y), PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE)); + int tx_cnt = get_texture_count(i, sz); + for (int k = 0; k < tx_cnt; k++) { + p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, prefix_sz + "textures/" + itos(k) + "/offsets", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::OBJECT, prefix_sz + "textures/" + itos(k) + "/image", PROPERTY_HINT_RESOURCE_TYPE, "Image", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT)); + } + Array glyphs = get_glyph_list(i, sz); + for (int k = 0; k < glyphs.size(); k++) { + const int32_t &gl = glyphs[k]; + if (sz.y == 0) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "glyphs/" + itos(gl) + "/advance", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + } + p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "glyphs/" + itos(gl) + "/offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "glyphs/" + itos(gl) + "/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::RECT2, prefix_sz + "glyphs/" + itos(gl) + "/uv_rect", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, prefix_sz + "glyphs/" + itos(gl) + "/texture_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + } + if (sz.y == 0) { + Array kerning_map = get_kerning_list(i, sz.x); + for (int k = 0; k < kerning_map.size(); k++) { + const Vector2i &gl_pair = kerning_map[k]; + p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "kerning_overrides/" + itos(gl_pair.x) + "/" + itos(gl_pair.y), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + } + } + } } } void FontData::reset_state() { - if (rid != RID()) { - TS->free(rid); - } - base_size = 16; - path = String(); -} + _clear_cache(); + data.clear(); + data_ptr = nullptr; + data_size = 0; + cache.clear(); -RID FontData::get_rid() const { - return rid; + antialiased = true; + msdf = false; + force_autohinter = false; + hinting = TextServer::HINTING_LIGHT; + msdf_pixel_range = 14; + msdf_size = 128; + oversampling = 0.f; } -void FontData::load_resource(const String &p_filename, int p_base_size) { - if (rid != RID()) { - TS->free(rid); +/*************************************************************************/ + +void FontData::set_data_ptr(const uint8_t *p_data, size_t p_size) { + data.clear(); + data_ptr = p_data; + data_size = p_size; + + if (data_ptr != nullptr) { + for (int i = 0; i < cache.size(); i++) { + if (cache[i].is_valid()) { + TS->font_set_data_ptr(cache[i], data_ptr, data_size); + } + } } - rid = TS->create_font_resource(p_filename, p_base_size); - path = p_filename; - base_size = TS->font_get_base_size(rid); - emit_changed(); } -void FontData::_load_memory(const PackedByteArray &p_data, const String &p_type, int p_base_size) { - if (rid != RID()) { - TS->free(rid); +void FontData::set_data(const PackedByteArray &p_data) { + data = p_data; + data_ptr = data.ptr(); + data_size = data.size(); + + if (data_ptr != nullptr) { + for (int i = 0; i < cache.size(); i++) { + if (cache[i].is_valid()) { + TS->font_set_data_ptr(cache[i], data_ptr, data_size); + } + } } - rid = TS->create_font_memory(p_data.ptr(), p_data.size(), p_type, p_base_size); - path = TTR("(Memory: " + p_type.to_upper() + " @ 0x" + String::num_int64((uint64_t)p_data.ptr(), 16, true) + ")"); - base_size = TS->font_get_base_size(rid); - emit_changed(); } -void FontData::load_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size) { - if (rid != RID()) { - TS->free(rid); - } - rid = TS->create_font_memory(p_data, p_size, p_type, p_base_size); - path = TTR("(Memory: " + p_type.to_upper() + " @ 0x" + String::num_int64((uint64_t)p_data, 16, true) + ")"); - base_size = TS->font_get_base_size(rid); - emit_changed(); +PackedByteArray FontData::get_data() const { + return data; } -void FontData::new_bitmap(float p_height, float p_ascent, int p_base_size) { - if (rid != RID()) { - TS->free(rid); +void FontData::set_antialiased(bool p_antialiased) { + if (antialiased != p_antialiased) { + antialiased = p_antialiased; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_antialiased(cache[i], antialiased); + } + emit_changed(); } - rid = TS->create_font_bitmap(p_height, p_ascent, p_base_size); - path = TTR("(Bitmap: " + String::num_int64(rid.get_id(), 16, true) + ")"); - base_size = TS->font_get_base_size(rid); - emit_changed(); } -void FontData::bitmap_add_texture(const Ref<Texture> &p_texture) { - if (rid != RID()) { - TS->font_bitmap_add_texture(rid, p_texture); - } +bool FontData::is_antialiased() const { + return antialiased; } -void FontData::bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { - if (rid != RID()) { - TS->font_bitmap_add_char(rid, p_char, p_texture_idx, p_rect, p_align, p_advance); +void FontData::set_multichannel_signed_distance_field(bool p_msdf) { + if (msdf != p_msdf) { + msdf = p_msdf; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_multichannel_signed_distance_field(cache[i], msdf); + } + emit_changed(); } } -void FontData::bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) { - if (rid != RID()) { - TS->font_bitmap_add_kerning_pair(rid, p_A, p_B, p_kerning); - } +bool FontData::is_multichannel_signed_distance_field() const { + return msdf; } -void FontData::set_data_path(const String &p_path) { - load_resource(p_path, base_size); +void FontData::set_msdf_pixel_range(int p_msdf_pixel_range) { + if (msdf_pixel_range != p_msdf_pixel_range) { + msdf_pixel_range = p_msdf_pixel_range; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_msdf_pixel_range(cache[i], msdf_pixel_range); + } + emit_changed(); + } } -String FontData::get_data_path() const { - return path; +int FontData::get_msdf_pixel_range() const { + return msdf_pixel_range; } -float FontData::get_height(int p_size) const { - if (rid == RID()) { - return 0.f; // Do not raise errors in getters, to prevent editor from spamming errors on incomplete (without data_path set) fonts. +void FontData::set_msdf_size(int p_msdf_size) { + if (msdf_size != p_msdf_size) { + msdf_size = p_msdf_size; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_msdf_size(cache[i], msdf_size); + } + emit_changed(); } - return TS->font_get_height(rid, (p_size < 0) ? base_size : p_size); } -float FontData::get_ascent(int p_size) const { - if (rid == RID()) { - return 0.f; - } - return TS->font_get_ascent(rid, (p_size < 0) ? base_size : p_size); +int FontData::get_msdf_size() const { + return msdf_size; } -float FontData::get_descent(int p_size) const { - if (rid == RID()) { - return 0.f; +void FontData::set_fixed_size(int p_fixed_size) { + if (fixed_size != p_fixed_size) { + fixed_size = p_fixed_size; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_fixed_size(cache[i], fixed_size); + } + emit_changed(); } - return TS->font_get_descent(rid, (p_size < 0) ? base_size : p_size); } -float FontData::get_underline_position(int p_size) const { - if (rid == RID()) { - return 0.f; - } - return TS->font_get_underline_position(rid, (p_size < 0) ? base_size : p_size); +int FontData::get_fixed_size() const { + return fixed_size; } -Dictionary FontData::get_feature_list() const { - if (rid == RID()) { - return Dictionary(); +void FontData::set_force_autohinter(bool p_force_autohinter) { + if (force_autohinter != p_force_autohinter) { + force_autohinter = p_force_autohinter; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_force_autohinter(cache[i], force_autohinter); + } + emit_changed(); } - return TS->font_get_feature_list(rid); } -float FontData::get_underline_thickness(int p_size) const { - if (rid == RID()) { - return 0.f; - } - return TS->font_get_underline_thickness(rid, (p_size < 0) ? base_size : p_size); +bool FontData::is_force_autohinter() const { + return force_autohinter; } -Dictionary FontData::get_variation_list() const { - if (rid == RID()) { - return Dictionary(); +void FontData::set_hinting(TextServer::Hinting p_hinting) { + if (hinting != p_hinting) { + hinting = p_hinting; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_hinting(cache[i], hinting); + } + emit_changed(); } - return TS->font_get_variation_list(rid); } -void FontData::set_variation(const String &p_name, double p_value) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_variation(rid, p_name, p_value); - emit_changed(); +TextServer::Hinting FontData::get_hinting() const { + return hinting; } -double FontData::get_variation(const String &p_name) const { - if (rid == RID()) { - return 0; +void FontData::set_oversampling(real_t p_oversampling) { + if (oversampling != p_oversampling) { + oversampling = p_oversampling; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_oversampling(cache[i], oversampling); + } + emit_changed(); + } +} + +real_t FontData::get_oversampling() const { + return oversampling; +} + +RID FontData::find_cache(const Dictionary &p_variation_coordinates) const { + // Find existing variation cache. + const Dictionary &supported_coords = get_supported_variation_list(); + for (int i = 0; i < cache.size(); i++) { + if (cache[i].is_valid()) { + const Dictionary &cache_var = TS->font_get_variation_coordinates(cache[i]); + bool match = true; + for (const Variant *V = supported_coords.next(nullptr); V && match; V = supported_coords.next(V)) { + const Vector3 &def = supported_coords[*V]; + + real_t c_v = def.z; + if (cache_var.has(*V)) { + real_t val = cache_var[*V]; + c_v = CLAMP(val, def.x, def.y); + } + if (cache_var.has(TS->tag_to_name(*V))) { + real_t val = cache_var[TS->tag_to_name(*V)]; + c_v = CLAMP(val, def.x, def.y); + } + + real_t s_v = def.z; + if (p_variation_coordinates.has(*V)) { + real_t val = p_variation_coordinates[*V]; + s_v = CLAMP(val, def.x, def.y); + } + if (p_variation_coordinates.has(TS->tag_to_name(*V))) { + real_t val = p_variation_coordinates[TS->tag_to_name(*V)]; + s_v = CLAMP(val, def.x, def.y); + } + + match = match && (c_v == s_v); + } + if (match) { + return cache[i]; + } + } } - return TS->font_get_variation(rid, p_name); + + // Create new variation cache. + int idx = cache.size(); + _ensure_rid(idx); + TS->font_set_variation_coordinates(cache[idx], p_variation_coordinates); + return cache[idx]; } -int FontData::get_spacing(int p_type) const { - if (rid == RID()) { - return 0; - } - if (p_type == SPACING_GLYPH) { - return TS->font_get_spacing_glyph(rid); - } else { - return TS->font_get_spacing_space(rid); - } +int FontData::get_cache_count() const { + return cache.size(); } -void FontData::set_spacing(int p_type, int p_value) { - ERR_FAIL_COND(rid == RID()); - if (p_type == SPACING_GLYPH) { - TS->font_set_spacing_glyph(rid, p_value); - } else { - TS->font_set_spacing_space(rid, p_value); +void FontData::clear_cache() { + _clear_cache(); + cache.clear(); +} + +void FontData::remove_cache(int p_cache_index) { + ERR_FAIL_INDEX(p_cache_index, cache.size()); + if (cache[p_cache_index].is_valid()) { + TS->free(cache.write[p_cache_index]); } + cache.remove(p_cache_index); emit_changed(); } -void FontData::set_antialiased(bool p_antialiased) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_antialiased(rid, p_antialiased); - emit_changed(); +Array FontData::get_size_cache_list(int p_cache_index) const { + _ensure_rid(p_cache_index); + return TS->font_get_size_cache_list(cache[p_cache_index]); } -bool FontData::get_antialiased() const { - if (rid == RID()) { - return false; - } - return TS->font_get_antialiased(rid); +void FontData::clear_size_cache(int p_cache_index) { + _ensure_rid(p_cache_index); + TS->font_clear_size_cache(cache[p_cache_index]); +} + +void FontData::remove_size_cache(int p_cache_index, const Vector2i &p_size) { + _ensure_rid(p_cache_index); + TS->font_remove_size_cache(cache[p_cache_index], p_size); } -void FontData::set_distance_field_hint(bool p_distance_field) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_distance_field_hint(rid, p_distance_field); +void FontData::set_variation_coordinates(int p_cache_index, const Dictionary &p_variation_coordinates) { + _ensure_rid(p_cache_index); + TS->font_set_variation_coordinates(cache[p_cache_index], p_variation_coordinates); emit_changed(); } -bool FontData::get_distance_field_hint() const { - if (rid == RID()) { - return false; - } - return TS->font_get_distance_field_hint(rid); +Dictionary FontData::get_variation_coordinates(int p_cache_index) const { + _ensure_rid(p_cache_index); + return TS->font_get_variation_coordinates(cache[p_cache_index]); } -void FontData::set_hinting(TextServer::Hinting p_hinting) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_hinting(rid, p_hinting); - emit_changed(); +void FontData::set_ascent(int p_cache_index, int p_size, real_t p_ascent) { + _ensure_rid(p_cache_index); + TS->font_set_ascent(cache[p_cache_index], p_size, p_ascent); } -TextServer::Hinting FontData::get_hinting() const { - if (rid == RID()) { - return TextServer::HINTING_NONE; - } - return TS->font_get_hinting(rid); +real_t FontData::get_ascent(int p_cache_index, int p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_ascent(cache[p_cache_index], p_size); } -void FontData::set_force_autohinter(bool p_enabeld) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_force_autohinter(rid, p_enabeld); - emit_changed(); +void FontData::set_descent(int p_cache_index, int p_size, real_t p_descent) { + _ensure_rid(p_cache_index); + TS->font_set_descent(cache[p_cache_index], p_size, p_descent); } -bool FontData::get_force_autohinter() const { - if (rid == RID()) { - return false; - } - return TS->font_get_force_autohinter(rid); +real_t FontData::get_descent(int p_cache_index, int p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_descent(cache[p_cache_index], p_size); } -bool FontData::has_char(char32_t p_char) const { - if (rid == RID()) { - return false; - } - return TS->font_has_char(rid, p_char); +void FontData::set_underline_position(int p_cache_index, int p_size, real_t p_underline_position) { + _ensure_rid(p_cache_index); + TS->font_set_underline_position(cache[p_cache_index], p_size, p_underline_position); } -String FontData::get_supported_chars() const { - ERR_FAIL_COND_V(rid == RID(), String()); - return TS->font_get_supported_chars(rid); +real_t FontData::get_underline_position(int p_cache_index, int p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_underline_position(cache[p_cache_index], p_size); } -Vector2 FontData::get_glyph_advance(uint32_t p_index, int p_size) const { - ERR_FAIL_COND_V(rid == RID(), Vector2()); - return TS->font_get_glyph_advance(rid, p_index, (p_size < 0) ? base_size : p_size); +void FontData::set_underline_thickness(int p_cache_index, int p_size, real_t p_underline_thickness) { + _ensure_rid(p_cache_index); + TS->font_set_underline_thickness(cache[p_cache_index], p_size, p_underline_thickness); } -Vector2 FontData::get_glyph_kerning(uint32_t p_index_a, uint32_t p_index_b, int p_size) const { - ERR_FAIL_COND_V(rid == RID(), Vector2()); - return TS->font_get_glyph_kerning(rid, p_index_a, p_index_b, (p_size < 0) ? base_size : p_size); +real_t FontData::get_underline_thickness(int p_cache_index, int p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_underline_thickness(cache[p_cache_index], p_size); } -bool FontData::has_outline() const { - if (rid == RID()) { - return false; - } - return TS->font_has_outline(rid); +void FontData::set_scale(int p_cache_index, int p_size, real_t p_scale) { + _ensure_rid(p_cache_index); + TS->font_set_scale(cache[p_cache_index], p_size, p_scale); } -float FontData::get_base_size() const { - return base_size; +real_t FontData::get_scale(int p_cache_index, int p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_scale(cache[p_cache_index], p_size); +} + +void FontData::set_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing, int p_value) { + _ensure_rid(p_cache_index); + TS->font_set_spacing(cache[p_cache_index], p_size, p_spacing, p_value); +} + +int FontData::get_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing) const { + _ensure_rid(p_cache_index); + return TS->font_get_spacing(cache[p_cache_index], p_size, p_spacing); +} + +int FontData::get_texture_count(int p_cache_index, const Vector2i &p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_texture_count(cache[p_cache_index], p_size); +} + +void FontData::clear_textures(int p_cache_index, const Vector2i &p_size) { + _ensure_rid(p_cache_index); + TS->font_clear_textures(cache[p_cache_index], p_size); +} + +void FontData::remove_texture(int p_cache_index, const Vector2i &p_size, int p_texture_index) { + _ensure_rid(p_cache_index); + TS->font_remove_texture(cache[p_cache_index], p_size, p_texture_index); +} + +void FontData::set_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) { + _ensure_rid(p_cache_index); + TS->font_set_texture_image(cache[p_cache_index], p_size, p_texture_index, p_image); +} + +Ref<Image> FontData::get_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index) const { + _ensure_rid(p_cache_index); + return TS->font_get_texture_image(cache[p_cache_index], p_size, p_texture_index); +} + +void FontData::set_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) { + _ensure_rid(p_cache_index); + TS->font_set_texture_offsets(cache[p_cache_index], p_size, p_texture_index, p_offset); +} + +PackedInt32Array FontData::get_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index) const { + _ensure_rid(p_cache_index); + return TS->font_get_texture_offsets(cache[p_cache_index], p_size, p_texture_index); +} + +Array FontData::get_glyph_list(int p_cache_index, const Vector2i &p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_glyph_list(cache[p_cache_index], p_size); +} + +void FontData::clear_glyphs(int p_cache_index, const Vector2i &p_size) { + _ensure_rid(p_cache_index); + TS->font_clear_glyphs(cache[p_cache_index], p_size); +} + +void FontData::remove_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) { + _ensure_rid(p_cache_index); + TS->font_remove_glyph(cache[p_cache_index], p_size, p_glyph); +} + +void FontData::set_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph, const Vector2 &p_advance) { + _ensure_rid(p_cache_index); + TS->font_set_glyph_advance(cache[p_cache_index], p_size, p_glyph, p_advance); +} + +Vector2 FontData::get_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph) const { + _ensure_rid(p_cache_index); + return TS->font_get_glyph_advance(cache[p_cache_index], p_size, p_glyph); +} + +void FontData::set_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) { + _ensure_rid(p_cache_index); + TS->font_set_glyph_offset(cache[p_cache_index], p_size, p_glyph, p_offset); +} + +Vector2 FontData::get_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { + _ensure_rid(p_cache_index); + return TS->font_get_glyph_offset(cache[p_cache_index], p_size, p_glyph); +} + +void FontData::set_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) { + _ensure_rid(p_cache_index); + TS->font_set_glyph_size(cache[p_cache_index], p_size, p_glyph, p_gl_size); +} + +Vector2 FontData::get_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { + _ensure_rid(p_cache_index); + return TS->font_get_glyph_size(cache[p_cache_index], p_size, p_glyph); +} + +void FontData::set_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) { + _ensure_rid(p_cache_index); + TS->font_set_glyph_uv_rect(cache[p_cache_index], p_size, p_glyph, p_uv_rect); +} + +Rect2 FontData::get_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { + _ensure_rid(p_cache_index); + return TS->font_get_glyph_uv_rect(cache[p_cache_index], p_size, p_glyph); +} + +void FontData::set_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) { + _ensure_rid(p_cache_index); + TS->font_set_glyph_texture_idx(cache[p_cache_index], p_size, p_glyph, p_texture_idx); +} + +int FontData::get_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { + _ensure_rid(p_cache_index); + return TS->font_get_glyph_texture_idx(cache[p_cache_index], p_size, p_glyph); +} + +Array FontData::get_kerning_list(int p_cache_index, int p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_kerning_list(cache[p_cache_index], p_size); +} + +void FontData::clear_kerning_map(int p_cache_index, int p_size) { + _ensure_rid(p_cache_index); + TS->font_clear_kerning_map(cache[p_cache_index], p_size); +} + +void FontData::remove_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) { + _ensure_rid(p_cache_index); + TS->font_remove_kerning(cache[p_cache_index], p_size, p_glyph_pair); +} + +void FontData::set_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { + _ensure_rid(p_cache_index); + TS->font_set_kerning(cache[p_cache_index], p_size, p_glyph_pair, p_kerning); +} + +Vector2 FontData::get_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) const { + _ensure_rid(p_cache_index); + return TS->font_get_kerning(cache[p_cache_index], p_size, p_glyph_pair); +} + +void FontData::render_range(int p_cache_index, const Vector2i &p_size, char32_t p_start, char32_t p_end) { + _ensure_rid(p_cache_index); + TS->font_render_range(cache[p_cache_index], p_size, p_start, p_end); +} + +void FontData::render_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_index) { + _ensure_rid(p_cache_index); + TS->font_render_glyph(cache[p_cache_index], p_size, p_index); +} + +RID FontData::get_cache_rid(int p_cache_index) const { + _ensure_rid(p_cache_index); + return cache[p_cache_index]; } bool FontData::is_language_supported(const String &p_language) const { - if (rid == RID()) { - return false; - } - return TS->font_is_language_supported(rid, p_language); + _ensure_rid(0); + return TS->font_is_language_supported(cache[0], p_language); } void FontData::set_language_support_override(const String &p_language, bool p_supported) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_language_support_override(rid, p_language, p_supported); - emit_changed(); + _ensure_rid(0); + TS->font_set_language_support_override(cache[0], p_language, p_supported); } bool FontData::get_language_support_override(const String &p_language) const { - if (rid == RID()) { - return false; - } - return TS->font_get_language_support_override(rid, p_language); + _ensure_rid(0); + return TS->font_get_language_support_override(cache[0], p_language); } void FontData::remove_language_support_override(const String &p_language) { - ERR_FAIL_COND(rid == RID()); - TS->font_remove_language_support_override(rid, p_language); - emit_changed(); + _ensure_rid(0); + TS->font_remove_language_support_override(cache[0], p_language); } Vector<String> FontData::get_language_support_overrides() const { - if (rid == RID()) { - return Vector<String>(); - } - return TS->font_get_language_support_overrides(rid); + _ensure_rid(0); + return TS->font_get_language_support_overrides(cache[0]); } bool FontData::is_script_supported(const String &p_script) const { - if (rid == RID()) { - return false; - } - return TS->font_is_script_supported(rid, p_script); + _ensure_rid(0); + return TS->font_is_script_supported(cache[0], p_script); } void FontData::set_script_support_override(const String &p_script, bool p_supported) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_script_support_override(rid, p_script, p_supported); - emit_changed(); + _ensure_rid(0); + TS->font_set_script_support_override(cache[0], p_script, p_supported); } bool FontData::get_script_support_override(const String &p_script) const { - if (rid == RID()) { - return false; - } - return TS->font_get_script_support_override(rid, p_script); + _ensure_rid(0); + return TS->font_get_script_support_override(cache[0], p_script); } void FontData::remove_script_support_override(const String &p_script) { - ERR_FAIL_COND(rid == RID()); - TS->font_remove_script_support_override(rid, p_script); - emit_changed(); + _ensure_rid(0); + TS->font_remove_script_support_override(cache[0], p_script); } Vector<String> FontData::get_script_support_overrides() const { - if (rid == RID()) { - return Vector<String>(); - } - return TS->font_get_script_support_overrides(rid); + _ensure_rid(0); + return TS->font_get_script_support_overrides(cache[0]); } -uint32_t FontData::get_glyph_index(char32_t p_char, char32_t p_variation_selector) const { - ERR_FAIL_COND_V(rid == RID(), 0); - return TS->font_get_glyph_index(rid, p_char, p_variation_selector); +bool FontData::has_char(char32_t p_char) const { + _ensure_rid(0); + return TS->font_has_char(cache[0], p_char); } -Vector2 FontData::draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - ERR_FAIL_COND_V(rid == RID(), Vector2()); - return TS->font_draw_glyph(rid, p_canvas, (p_size <= 0) ? base_size : p_size, p_pos, p_index, p_color); +String FontData::get_supported_chars() const { + _ensure_rid(0); + return TS->font_get_supported_chars(cache[0]); } -Vector2 FontData::draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - ERR_FAIL_COND_V(rid == RID(), Vector2()); - return TS->font_draw_glyph_outline(rid, p_canvas, (p_size <= 0) ? base_size : p_size, p_outline_size, p_pos, p_index, p_color); +int32_t FontData::get_glyph_index(int p_size, char32_t p_char, char32_t p_variation_selector) const { + _ensure_rid(0); + return TS->font_get_glyph_index(cache[0], p_size, p_char, p_variation_selector); } -FontData::FontData() {} +Dictionary FontData::get_supported_feature_list() const { + _ensure_rid(0); + return TS->font_supported_feature_list(cache[0]); +} -FontData::FontData(const String &p_filename, int p_base_size) { - load_resource(p_filename, p_base_size); +Dictionary FontData::get_supported_variation_list() const { + _ensure_rid(0); + return TS->font_supported_variation_list(cache[0]); } -FontData::FontData(const PackedByteArray &p_data, const String &p_type, int p_base_size) { - _load_memory(p_data, p_type, p_base_size); +FontData::FontData() { + /* NOP */ } FontData::~FontData() { - if (rid != RID()) { - TS->free(rid); - } + _clear_cache(); } /*************************************************************************/ +void Font::_data_changed() { + for (int i = 0; i < rids.size(); i++) { + rids.write[i] = RID(); + } + emit_changed(); +} + +void Font::_ensure_rid(int p_index) const { + // Find or create cache record. + for (int i = 0; i < rids.size(); i++) { + if (!rids[i].is_valid() && data[i].is_valid()) { + rids.write[i] = data[i]->find_cache(variation_coordinates); + } + } +} + void Font::_bind_methods() { ClassDB::bind_method(D_METHOD("add_data", "data"), &Font::add_data); ClassDB::bind_method(D_METHOD("set_data", "idx", "data"), &Font::set_data); ClassDB::bind_method(D_METHOD("get_data_count"), &Font::get_data_count); ClassDB::bind_method(D_METHOD("get_data", "idx"), &Font::get_data); + ClassDB::bind_method(D_METHOD("get_data_rid", "idx"), &Font::get_data_rid); + ClassDB::bind_method(D_METHOD("clear_data"), &Font::clear_data); ClassDB::bind_method(D_METHOD("remove_data", "idx"), &Font::remove_data); + ClassDB::bind_method(D_METHOD("set_base_size", "size"), &Font::set_base_size); + ClassDB::bind_method(D_METHOD("get_base_size"), &Font::get_base_size); + ADD_PROPERTY(PropertyInfo(Variant::INT, "base_size"), "set_base_size", "get_base_size"); + + ClassDB::bind_method(D_METHOD("set_variation_coordinates", "variation_coordinates"), &Font::set_variation_coordinates); + ClassDB::bind_method(D_METHOD("get_variation_coordinates"), &Font::get_variation_coordinates); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "variation_coordinates"), "set_variation_coordinates", "get_variation_coordinates"); + + ClassDB::bind_method(D_METHOD("set_spacing", "spacing", "value"), &Font::set_spacing); + ClassDB::bind_method(D_METHOD("get_spacing", "spacing"), &Font::get_spacing); + + ADD_GROUP("Extra Spacing", "spacing"); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_top"), "set_spacing", "get_spacing", TextServer::SPACING_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_bottom"), "set_spacing", "get_spacing", TextServer::SPACING_BOTTOM); + ClassDB::bind_method(D_METHOD("get_height", "size"), &Font::get_height, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_ascent", "size"), &Font::get_ascent, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_descent", "size"), &Font::get_descent, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_underline_position", "size"), &Font::get_underline_position, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_underline_thickness", "size"), &Font::get_underline_thickness, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_spacing", "type"), &Font::get_spacing); - ClassDB::bind_method(D_METHOD("set_spacing", "type", "value"), &Font::set_spacing); - - ClassDB::bind_method(D_METHOD("get_string_size", "text", "size"), &Font::get_string_size, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_string_size", "text", "size", "align", "width", "flags"), &Font::get_string_size, DEFVAL(-1), DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND)); ClassDB::bind_method(D_METHOD("get_multiline_string_size", "text", "width", "size", "flags"), &Font::get_multiline_string_size, DEFVAL(-1), DEFVAL(-1), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND)); ClassDB::bind_method(D_METHOD("draw_string", "canvas_item", "pos", "text", "align", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND)); ClassDB::bind_method(D_METHOD("draw_multiline_string", "canvas_item", "pos", "text", "align", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_multiline_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND)); - ClassDB::bind_method(D_METHOD("has_char", "char"), &Font::has_char); - ClassDB::bind_method(D_METHOD("get_supported_chars"), &Font::get_supported_chars); - ClassDB::bind_method(D_METHOD("get_char_size", "char", "next", "size"), &Font::get_char_size, DEFVAL(0), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &Font::draw_char, DEFVAL(0), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0))); - ClassDB::bind_method(D_METHOD("update_changes"), &Font::update_changes); - - ADD_GROUP("Extra Spacing", "extra_spacing"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "extra_spacing_top"), "set_spacing", "get_spacing", SPACING_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "extra_spacing_bottom"), "set_spacing", "get_spacing", SPACING_BOTTOM); - - BIND_ENUM_CONSTANT(SPACING_TOP); - BIND_ENUM_CONSTANT(SPACING_BOTTOM); -} - -void Font::_data_changed() { - cache.clear(); - cache_wrap.clear(); + ClassDB::bind_method(D_METHOD("has_char", "char"), &Font::has_char); + ClassDB::bind_method(D_METHOD("get_supported_chars"), &Font::get_supported_chars); - emit_changed(); - notify_property_list_changed(); + ClassDB::bind_method(D_METHOD("update_changes"), &Font::update_changes); } bool Font::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; + Vector<String> tokens = p_name.operator String().split("/"); #ifndef DISABLE_DEPRECATED - if (str == "font_data") { // Compatibility, DynamicFont main data + if (tokens.size() == 1 && tokens[0] == "font_data") { + // Compatibility, DynamicFont main data. Ref<FontData> fd = p_value; if (fd.is_valid()) { add_data(fd); return true; } return false; - } else if (str.begins_with("fallback/")) { // Compatibility, DynamicFont fallback data + } else if (tokens.size() == 2 && tokens[0] == "fallback") { + // Compatibility, DynamicFont fallback data. Ref<FontData> fd = p_value; if (fd.is_valid()) { add_data(fd); return true; } return false; - } else if (str == "fallback") { // Compatibility, BitmapFont fallback + } else if (tokens.size() == 1 && tokens[0] == "fallback") { + // Compatibility, BitmapFont fallback data. Ref<Font> f = p_value; if (f.is_valid()) { for (int i = 0; i < f->get_data_count(); i++) { @@ -612,10 +1091,9 @@ bool Font::_set(const StringName &p_name, const Variant &p_value) { return false; } #endif /* DISABLE_DEPRECATED */ - if (str.begins_with("data/")) { - int idx = str.get_slicec('/', 1).to_int(); + if (tokens.size() == 2 && tokens[0] == "data") { + int idx = tokens[1].to_int(); Ref<FontData> fd = p_value; - if (fd.is_valid()) { if (idx == data.size()) { add_data(fd); @@ -631,14 +1109,13 @@ bool Font::_set(const StringName &p_name, const Variant &p_value) { return true; } } - return false; } bool Font::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("data/")) { - int idx = str.get_slicec('/', 1).to_int(); + Vector<String> tokens = p_name.operator String().split("/"); + if (tokens.size() == 2 && tokens[0] == "data") { + int idx = tokens[1].to_int(); if (idx == data.size()) { r_ret = Ref<FontData>(); @@ -656,24 +1133,44 @@ void Font::_get_property_list(List<PropertyInfo> *p_list) const { for (int i = 0; i < data.size(); i++) { p_list->push_back(PropertyInfo(Variant::OBJECT, "data/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "FontData")); } - p_list->push_back(PropertyInfo(Variant::OBJECT, "data/" + itos(data.size()), PROPERTY_HINT_RESOURCE_TYPE, "FontData")); } void Font::reset_state() { - spacing_top = 0; - spacing_bottom = 0; + for (int i = 0; i < data.size(); i++) { + if (data[i].is_valid()) { + data.write[i]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); + } + } cache.clear(); cache_wrap.clear(); data.clear(); + rids.clear(); + + base_size = 16; + variation_coordinates.clear(); + spacing_bottom = 0; + spacing_top = 0; +} + +Dictionary Font::get_feature_list() const { + Dictionary out; + for (int i = 0; i < data.size(); i++) { + Dictionary data_ftrs = data[i]->get_supported_feature_list(); + for (const Variant *ftr = data_ftrs.next(nullptr); ftr != nullptr; ftr = data_ftrs.next(ftr)) { + out[*ftr] = data_ftrs[*ftr]; + } + } + return out; } void Font::add_data(const Ref<FontData> &p_data) { ERR_FAIL_COND(p_data.is_null()); data.push_back(p_data); + rids.push_back(RID()); if (data[data.size() - 1].is_valid()) { - data.write[data.size() - 1]->connect("changed", callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); + data.write[data.size() - 1]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); } cache.clear(); @@ -688,13 +1185,14 @@ void Font::set_data(int p_idx, const Ref<FontData> &p_data) { ERR_FAIL_INDEX(p_idx, data.size()); if (data[p_idx].is_valid()) { - data.write[p_idx]->disconnect("changed", callable_mp(this, &Font::_data_changed)); + data.write[p_idx]->disconnect(SNAME("changed"), callable_mp(this, &Font::_data_changed)); } data.write[p_idx] = p_data; + rids.write[p_idx] = RID(); if (data[p_idx].is_valid()) { - data.write[p_idx]->connect("changed", callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); + data.write[p_idx]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); } cache.clear(); @@ -713,14 +1211,31 @@ Ref<FontData> Font::get_data(int p_idx) const { return data[p_idx]; } +RID Font::get_data_rid(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, data.size(), RID()); + _ensure_rid(p_idx); + return rids[p_idx]; +} + +void Font::clear_data() { + for (int i = 0; i < data.size(); i++) { + if (data[i].is_valid()) { + data.write[i]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); + } + } + data.clear(); + rids.clear(); +} + void Font::remove_data(int p_idx) { ERR_FAIL_INDEX(p_idx, data.size()); if (data[p_idx].is_valid()) { - data.write[p_idx]->disconnect("changed", callable_mp(this, &Font::_data_changed)); + data.write[p_idx]->disconnect(SNAME("changed"), callable_mp(this, &Font::_data_changed)); } data.remove(p_idx); + rids.remove(p_idx); cache.clear(); cache_wrap.clear(); @@ -729,117 +1244,148 @@ void Font::remove_data(int p_idx) { notify_property_list_changed(); } -Dictionary Font::get_feature_list() const { - Dictionary out; - for (int i = 0; i < data.size(); i++) { - Dictionary data_ftrs = data[i]->get_feature_list(); - for (const Variant *ftr = data_ftrs.next(nullptr); ftr != nullptr; ftr = data_ftrs.next(ftr)) { - out[*ftr] = data_ftrs[*ftr]; - } +void Font::set_base_size(int p_size) { + base_size = p_size; +} + +int Font::get_base_size() const { + return base_size; +} + +void Font::set_variation_coordinates(const Dictionary &p_variation_coordinates) { + _data_changed(); + variation_coordinates = p_variation_coordinates; +} + +Dictionary Font::get_variation_coordinates() const { + return variation_coordinates; +} + +void Font::set_spacing(TextServer::SpacingType p_spacing, int p_value) { + _data_changed(); + switch (p_spacing) { + case TextServer::SPACING_TOP: { + spacing_top = p_value; + } break; + case TextServer::SPACING_BOTTOM: { + spacing_bottom = p_value; + } break; + default: { + ERR_FAIL_MSG("Invalid spacing type: " + itos(p_spacing)); + } break; + } +} + +int Font::get_spacing(TextServer::SpacingType p_spacing) const { + switch (p_spacing) { + case TextServer::SPACING_TOP: { + return spacing_top; + } break; + case TextServer::SPACING_BOTTOM: { + return spacing_bottom; + } break; + default: { + ERR_FAIL_V_MSG(0, "Invalid spacing type: " + itos(p_spacing)); + } break; } - return out; } -float Font::get_height(int p_size) const { - float ret = 0.f; +real_t Font::get_height(int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + real_t ret = 0.f; for (int i = 0; i < data.size(); i++) { - ret = MAX(ret, data[i]->get_height(p_size)); + _ensure_rid(i); + ret = MAX(ret, TS->font_get_ascent(rids[i], size) + TS->font_get_descent(rids[i], size)); } - return ret + spacing_top + spacing_bottom; + return ret + spacing_bottom + spacing_top; } -float Font::get_ascent(int p_size) const { - float ret = 0.f; +real_t Font::get_ascent(int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + real_t ret = 0.f; for (int i = 0; i < data.size(); i++) { - ret = MAX(ret, data[i]->get_ascent(p_size)); + _ensure_rid(i); + ret = MAX(ret, TS->font_get_ascent(rids[i], size)); } return ret + spacing_top; } -float Font::get_descent(int p_size) const { - float ret = 0.f; +real_t Font::get_descent(int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + real_t ret = 0.f; for (int i = 0; i < data.size(); i++) { - ret = MAX(ret, data[i]->get_descent(p_size)); + _ensure_rid(i); + ret = MAX(ret, TS->font_get_descent(rids[i], size)); } return ret + spacing_bottom; } -float Font::get_underline_position(int p_size) const { - float ret = 0.f; +real_t Font::get_underline_position(int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + real_t ret = 0.f; for (int i = 0; i < data.size(); i++) { - ret = MAX(ret, data[i]->get_underline_position(p_size)); + _ensure_rid(i); + ret = MAX(ret, TS->font_get_underline_position(rids[i], size)); } - return ret; + return ret + spacing_top; } -float Font::get_underline_thickness(int p_size) const { - float ret = 0.f; +real_t Font::get_underline_thickness(int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + real_t ret = 0.f; for (int i = 0; i < data.size(); i++) { - ret = MAX(ret, data[i]->get_underline_thickness(p_size)); + _ensure_rid(i); + ret = MAX(ret, TS->font_get_underline_thickness(rids[i], size)); } return ret; } -int Font::get_spacing(int p_type) const { - if (p_type == SPACING_TOP) { - return spacing_top; - } else if (p_type == SPACING_BOTTOM) { - return spacing_bottom; - } +Size2 Font::get_string_size(const String &p_text, int p_size, HAlign p_align, real_t p_width, uint8_t p_flags) const { + ERR_FAIL_COND_V(data.is_empty(), Size2()); - return 0; -} + int size = (p_size <= 0) ? base_size : p_size; -void Font::set_spacing(int p_type, int p_value) { - if (p_type == SPACING_TOP) { - spacing_top = p_value; - } else if (p_type == SPACING_BOTTOM) { - spacing_bottom = p_value; + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); } - emit_changed(); - notify_property_list_changed(); -} - -// Drawing string and string sizes, cached. - -Size2 Font::get_string_size(const String &p_text, int p_size) const { - ERR_FAIL_COND_V(data.is_empty(), Size2()); - uint64_t hash = p_text.hash64(); - hash = hash_djb2_one_64(p_size, hash); + if (p_align == HALIGN_FILL) { + hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash); + hash = hash_djb2_one_64(p_flags, hash); + } + hash = hash_djb2_one_64(size, hash); Ref<TextLine> buffer; if (cache.has(hash)) { buffer = cache.get(hash); } else { buffer.instantiate(); - int size = p_size <= 0 ? data[0]->get_base_size() : p_size; buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); cache.insert(hash, buffer); } - if (buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { - return buffer->get_size() + Vector2(0, spacing_top + spacing_bottom); - } else { - return buffer->get_size() + Vector2(spacing_top + spacing_bottom, 0); - } + return buffer->get_size(); } -Size2 Font::get_multiline_string_size(const String &p_text, float p_width, int p_size, uint8_t p_flags) const { +Size2 Font::get_multiline_string_size(const String &p_text, real_t p_width, int p_size, uint8_t p_flags) const { ERR_FAIL_COND_V(data.is_empty(), Size2()); - uint64_t hash = p_text.hash64(); - hash = hash_djb2_one_64(p_size, hash); + int size = (p_size <= 0) ? base_size : p_size; + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); + } + + uint64_t hash = p_text.hash64(); uint64_t wrp_hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash); wrp_hash = hash_djb2_one_64(p_flags, wrp_hash); + wrp_hash = hash_djb2_one_64(size, wrp_hash); Ref<TextParagraph> lines_buffer; if (cache_wrap.has(wrp_hash)) { lines_buffer = cache_wrap.get(wrp_hash); } else { lines_buffer.instantiate(); - int size = p_size <= 0 ? data[0]->get_base_size() : p_size; lines_buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); lines_buffer->set_width(p_width); lines_buffer->set_flags(p_flags); @@ -851,40 +1397,50 @@ Size2 Font::get_multiline_string_size(const String &p_text, float p_width, int p Size2 line_size = lines_buffer->get_line_size(i); if (lines_buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { ret.x = MAX(ret.x, line_size.x); - ret.y += line_size.y + spacing_top + spacing_bottom; + ret.y += line_size.y; } else { ret.y = MAX(ret.y, line_size.y); - ret.x += line_size.x + spacing_top + spacing_bottom; + ret.x += line_size.x; } } return ret; } -void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align, float p_width, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint8_t p_flags) const { +void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align, real_t p_width, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint8_t p_flags) const { ERR_FAIL_COND(data.is_empty()); + int size = (p_size <= 0) ? base_size : p_size; + + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); + } + uint64_t hash = p_text.hash64(); - hash = hash_djb2_one_64(p_size, hash); + if (p_align == HALIGN_FILL) { + hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash); + hash = hash_djb2_one_64(p_flags, hash); + } + hash = hash_djb2_one_64(size, hash); Ref<TextLine> buffer; if (cache.has(hash)) { buffer = cache.get(hash); } else { buffer.instantiate(); - int size = p_size <= 0 ? data[0]->get_base_size() : p_size; buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); cache.insert(hash, buffer); } Vector2 ofs = p_pos; if (buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { - ofs.y += spacing_top - buffer->get_line_ascent(); + ofs.y -= buffer->get_line_ascent(); } else { - ofs.x += spacing_top - buffer->get_line_ascent(); + ofs.x -= buffer->get_line_ascent(); } buffer->set_width(p_width); buffer->set_align(p_align); + buffer->set_flags(p_flags); if (p_outline_size > 0 && p_outline_modulate.a != 0.0f) { buffer->draw_outline(p_canvas_item, ofs, p_outline_size, p_outline_modulate); @@ -895,18 +1451,22 @@ void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_t void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align, float p_width, int p_max_lines, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint8_t p_flags) const { ERR_FAIL_COND(data.is_empty()); - uint64_t hash = p_text.hash64(); - hash = hash_djb2_one_64(p_size, hash); + int size = (p_size <= 0) ? base_size : p_size; + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); + } + + uint64_t hash = p_text.hash64(); uint64_t wrp_hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash); wrp_hash = hash_djb2_one_64(p_flags, wrp_hash); + wrp_hash = hash_djb2_one_64(size, wrp_hash); Ref<TextParagraph> lines_buffer; if (cache_wrap.has(wrp_hash)) { lines_buffer = cache_wrap.get(wrp_hash); } else { lines_buffer.instantiate(); - int size = p_size <= 0 ? data[0]->get_base_size() : p_size; lines_buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); lines_buffer->set_width(p_width); lines_buffer->set_flags(p_flags); @@ -918,12 +1478,10 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S Vector2 lofs = p_pos; for (int i = 0; i < lines_buffer->get_line_count(); i++) { if (lines_buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { - lofs.y += spacing_top; if (i == 0) { lofs.y -= lines_buffer->get_line_ascent(0); } } else { - lofs.x += spacing_top; if (i == 0) { lofs.x -= lines_buffer->get_line_ascent(0); } @@ -939,9 +1497,9 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S Size2 line_size = lines_buffer->get_line_size(i); if (lines_buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { - lofs.y += line_size.y + spacing_bottom; + lofs.y += line_size.y; } else { - lofs.x += line_size.x + spacing_bottom; + lofs.x += line_size.x; } if ((p_max_lines > 0) && (i >= p_max_lines)) { @@ -950,37 +1508,17 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S } } -bool Font::has_char(char32_t p_char) const { - for (int i = 0; i < data.size(); i++) { - if (data[i]->has_char(p_char)) { - return true; - } - } - return false; -} - -String Font::get_supported_chars() const { - String chars; - for (int i = 0; i < data.size(); i++) { - String data_chars = data[i]->get_supported_chars(); - for (int j = 0; j < data_chars.length(); j++) { - if (chars.find_char(data_chars[j]) == -1) { - chars += data_chars[j]; - } - } - } - return chars; -} - Size2 Font::get_char_size(char32_t p_char, char32_t p_next, int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); if (data[i]->has_char(p_char)) { - int size = p_size <= 0 ? data[i]->get_base_size() : p_size; - uint32_t glyph_a = data[i]->get_glyph_index(p_char); - Size2 ret = Size2(data[i]->get_glyph_advance(glyph_a, size).x, data[i]->get_height(size)); + int32_t glyph_a = TS->font_get_glyph_index(rids[i], size, p_char, 0); + Size2 ret = Size2(TS->font_get_glyph_advance(rids[i], size, glyph_a).x, TS->font_get_ascent(rids[i], size) + TS->font_get_descent(rids[i], size)); if ((p_next != 0) && data[i]->has_char(p_next)) { - uint32_t glyph_b = data[i]->get_glyph_index(p_next); - ret.x -= data[i]->get_glyph_kerning(glyph_a, glyph_b, size).x; + int32_t glyph_b = TS->font_get_glyph_index(rids[i], size, p_next, 0); + ret.x -= TS->font_get_kerning(rids[i], size, Vector2i(glyph_a, glyph_b)).x; } return ret; } @@ -988,35 +1526,55 @@ Size2 Font::get_char_size(char32_t p_char, char32_t p_next, int p_size) const { return Size2(); } -float Font::draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate) const { +real_t Font::draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate) const { + int size = (p_size <= 0) ? base_size : p_size; + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); if (data[i]->has_char(p_char)) { - int size = p_size <= 0 ? data[i]->get_base_size() : p_size; - uint32_t glyph_a = data[i]->get_glyph_index(p_char); - float ret = data[i]->get_glyph_advance(glyph_a, size).x; + int32_t glyph_a = TS->font_get_glyph_index(rids[i], size, p_char, 0); + real_t ret = TS->font_get_glyph_advance(rids[i], size, glyph_a).x; if ((p_next != 0) && data[i]->has_char(p_next)) { - uint32_t glyph_b = data[i]->get_glyph_index(p_next); - ret -= data[i]->get_glyph_kerning(glyph_a, glyph_b, size).x; + int32_t glyph_b = TS->font_get_glyph_index(rids[i], size, p_next, 0); + ret -= TS->font_get_kerning(rids[i], size, Vector2i(glyph_a, glyph_b)).x; } + if (p_outline_size > 0 && p_outline_modulate.a != 0.0f) { - data[i]->draw_glyph_outline(p_canvas_item, size, p_outline_size, p_pos, glyph_a, p_outline_modulate); + TS->font_draw_glyph_outline(rids[i], p_canvas_item, size, p_outline_size, p_pos, glyph_a, p_outline_modulate); } - data[i]->draw_glyph(p_canvas_item, size, p_pos, glyph_a, p_modulate); + TS->font_draw_glyph(rids[i], p_canvas_item, size, p_pos, glyph_a, p_modulate); return ret; } } return 0; } -Vector<RID> Font::get_rids() const { - Vector<RID> ret; +bool Font::has_char(char32_t p_char) const { + for (int i = 0; i < data.size(); i++) { + if (data[i]->has_char(p_char)) + return true; + } + return false; +} + +String Font::get_supported_chars() const { + String chars; for (int i = 0; i < data.size(); i++) { - RID rid = data[i]->get_rid(); - if (rid != RID()) { - ret.push_back(rid); + String data_chars = data[i]->get_supported_chars(); + for (int j = 0; j < data_chars.length(); j++) { + if (chars.find_char(data_chars[j]) == -1) { + chars += data_chars[j]; + } } } - return ret; + return chars; +} + +Vector<RID> Font::get_rids() const { + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); + } + return rids; } void Font::update_changes() { @@ -1029,103 +1587,7 @@ Font::Font() { } Font::~Font() { + clear_data(); cache.clear(); cache_wrap.clear(); } - -/*************************************************************************/ - -RES ResourceFormatLoaderFont::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { - if (r_error) { - *r_error = ERR_FILE_CANT_OPEN; - } - - Ref<FontData> dfont; - dfont.instantiate(); - dfont->load_resource(p_path); - - if (r_error) { - *r_error = OK; - } - - return dfont; -} - -void ResourceFormatLoaderFont::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const { -#ifndef DISABLE_DEPRECATED - if (p_type == "DynamicFontData") { - p_extensions->push_back("ttf"); - p_extensions->push_back("otf"); - p_extensions->push_back("woff"); - return; - } - if (p_type == "BitmapFont") { // BitmapFont (*.font, *fnt) is handled by ResourceFormatLoaderCompatFont - return; - } -#endif /* DISABLE_DEPRECATED */ - if (p_type == "" || handles_type(p_type)) { - get_recognized_extensions(p_extensions); - } -} - -void ResourceFormatLoaderFont::get_recognized_extensions(List<String> *p_extensions) const { - p_extensions->push_back("ttf"); - p_extensions->push_back("otf"); - p_extensions->push_back("woff"); - p_extensions->push_back("font"); - p_extensions->push_back("fnt"); -} - -bool ResourceFormatLoaderFont::handles_type(const String &p_type) const { - return (p_type == "FontData"); -} - -String ResourceFormatLoaderFont::get_resource_type(const String &p_path) const { - String el = p_path.get_extension().to_lower(); - if (el == "ttf" || el == "otf" || el == "woff" || el == "font" || el == "fnt") { - return "FontData"; - } - return ""; -} - -#ifndef DISABLE_DEPRECATED - -RES ResourceFormatLoaderCompatFont::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { - if (r_error) { - *r_error = ERR_FILE_CANT_OPEN; - } - - Ref<FontData> dfont; - dfont.instantiate(); - dfont->load_resource(p_path); - - Ref<Font> font; - font.instantiate(); - font->add_data(dfont); - - if (r_error) { - *r_error = OK; - } - - return font; -} - -void ResourceFormatLoaderCompatFont::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const { - if (p_type == "BitmapFont") { - p_extensions->push_back("font"); - p_extensions->push_back("fnt"); - } -} - -void ResourceFormatLoaderCompatFont::get_recognized_extensions(List<String> *p_extensions) const { -} - -bool ResourceFormatLoaderCompatFont::handles_type(const String &p_type) const { - return (p_type == "Font"); -} - -String ResourceFormatLoaderCompatFont::get_resource_type(const String &p_path) const { - return ""; -} - -#endif /* DISABLE_DEPRECATED */ diff --git a/scene/resources/font.h b/scene/resources/font.h index 200373aa8c..9a34edce64 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -41,17 +41,27 @@ class FontData : public Resource { GDCLASS(FontData, Resource); + RES_BASE_EXTENSION("fontdata"); -public: - enum SpacingType { - SPACING_GLYPH, - SPACING_SPACE, - }; + // Font source data. + const uint8_t *data_ptr = nullptr; + size_t data_size = 0; + PackedByteArray data; -private: - RID rid; - int base_size = 16; - String path; + bool antialiased = true; + bool msdf = false; + int msdf_pixel_range = 16; + int msdf_size = 48; + int fixed_size = 0; + bool force_autohinter = false; + TextServer::Hinting hinting = TextServer::HINTING_LIGHT; + real_t oversampling = 0.f; + + // Cache. + mutable Vector<RID> cache; + + _FORCE_INLINE_ void _clear_cache(); + _FORCE_INLINE_ void _ensure_rid(int p_cache_index) const; protected: static void _bind_methods(); @@ -63,79 +73,132 @@ protected: virtual void reset_state() override; public: - virtual RID get_rid() const override; + // Font source data. + virtual void set_data_ptr(const uint8_t *p_data, size_t p_size); + virtual void set_data(const PackedByteArray &p_data); + virtual PackedByteArray get_data() const; - void load_resource(const String &p_filename, int p_base_size = 16); - void load_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size = 16); - void _load_memory(const PackedByteArray &p_data, const String &p_type, int p_base_size = 16); + // Common properties. + virtual void set_antialiased(bool p_antialiased); + virtual bool is_antialiased() const; - void new_bitmap(float p_height, float p_ascent, int p_base_size = 16); + virtual void set_multichannel_signed_distance_field(bool p_msdf); + virtual bool is_multichannel_signed_distance_field() const; - void bitmap_add_texture(const Ref<Texture> &p_texture); - void bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance); - void bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning); + virtual void set_msdf_pixel_range(int p_msdf_pixel_range); + virtual int get_msdf_pixel_range() const; - void set_data_path(const String &p_path); - String get_data_path() const; + virtual void set_msdf_size(int p_msdf_size); + virtual int get_msdf_size() const; - float get_height(int p_size) const; - float get_ascent(int p_size) const; - float get_descent(int p_size) const; + virtual void set_fixed_size(int p_fixed_size); + virtual int get_fixed_size() const; - Dictionary get_feature_list() const; - Dictionary get_variation_list() const; + virtual void set_force_autohinter(bool p_force_autohinter); + virtual bool is_force_autohinter() const; - void set_variation(const String &p_name, double p_value); - double get_variation(const String &p_name) const; + virtual void set_hinting(TextServer::Hinting p_hinting); + virtual TextServer::Hinting get_hinting() const; - float get_underline_position(int p_size) const; - float get_underline_thickness(int p_size) const; + virtual void set_oversampling(real_t p_oversampling); + virtual real_t get_oversampling() const; - int get_spacing(int p_type) const; - void set_spacing(int p_type, int p_value); + // Cache. + virtual RID find_cache(const Dictionary &p_variation_coordinates) const; - void set_antialiased(bool p_antialiased); - bool get_antialiased() const; + virtual int get_cache_count() const; + virtual void clear_cache(); + virtual void remove_cache(int p_cache_index); - void set_distance_field_hint(bool p_distance_field); - bool get_distance_field_hint() const; + virtual Array get_size_cache_list(int p_cache_index) const; + virtual void clear_size_cache(int p_cache_index); + virtual void remove_size_cache(int p_cache_index, const Vector2i &p_size); - void set_force_autohinter(bool p_enabeld); - bool get_force_autohinter() const; + virtual void set_variation_coordinates(int p_cache_index, const Dictionary &p_variation_coordinates); + virtual Dictionary get_variation_coordinates(int p_cache_index) const; - void set_hinting(TextServer::Hinting p_hinting); - TextServer::Hinting get_hinting() const; + virtual void set_ascent(int p_cache_index, int p_size, real_t p_ascent); + virtual real_t get_ascent(int p_cache_index, int p_size) const; - bool has_char(char32_t p_char) const; - String get_supported_chars() const; + virtual void set_descent(int p_cache_index, int p_size, real_t p_descent); + virtual real_t get_descent(int p_cache_index, int p_size) const; - Vector2 get_glyph_advance(uint32_t p_index, int p_size) const; - Vector2 get_glyph_kerning(uint32_t p_index_a, uint32_t p_index_b, int p_size) const; + virtual void set_underline_position(int p_cache_index, int p_size, real_t p_underline_position); + virtual real_t get_underline_position(int p_cache_index, int p_size) const; - bool has_outline() const; - float get_base_size() const; + virtual void set_underline_thickness(int p_cache_index, int p_size, real_t p_underline_thickness); + virtual real_t get_underline_thickness(int p_cache_index, int p_size) const; - bool is_language_supported(const String &p_language) const; - void set_language_support_override(const String &p_language, bool p_supported); - bool get_language_support_override(const String &p_language) const; - void remove_language_support_override(const String &p_language); - Vector<String> get_language_support_overrides() const; + virtual void set_scale(int p_cache_index, int p_size, real_t p_scale); // Rendering scale for bitmap fonts (e.g. emoji fonts). + virtual real_t get_scale(int p_cache_index, int p_size) const; - bool is_script_supported(const String &p_script) const; - void set_script_support_override(const String &p_script, bool p_supported); - bool get_script_support_override(const String &p_script) const; - void remove_script_support_override(const String &p_script); - Vector<String> get_script_support_overrides() const; + virtual void set_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing, int p_value); + virtual int get_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing) const; - uint32_t get_glyph_index(char32_t p_char, char32_t p_variation_selector = 0x0000) const; + virtual int get_texture_count(int p_cache_index, const Vector2i &p_size) const; + virtual void clear_textures(int p_cache_index, const Vector2i &p_size); + virtual void remove_texture(int p_cache_index, const Vector2i &p_size, int p_texture_index); - Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const; - Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const; + virtual void set_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image); + virtual Ref<Image> get_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index) const; - FontData(); - FontData(const String &p_filename, int p_base_size); - FontData(const PackedByteArray &p_data, const String &p_type, int p_base_size); + virtual void set_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset); + virtual PackedInt32Array get_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index) const; + + virtual Array get_glyph_list(int p_cache_index, const Vector2i &p_size) const; + virtual void clear_glyphs(int p_cache_index, const Vector2i &p_size); + virtual void remove_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_glyph); + + virtual void set_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph, const Vector2 &p_advance); + virtual Vector2 get_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph) const; + + virtual void set_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset); + virtual Vector2 get_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const; + + virtual void set_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size); + virtual Vector2 get_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const; + + virtual void set_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect); + virtual Rect2 get_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const; + + virtual void set_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx); + virtual int get_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const; + virtual Array get_kerning_list(int p_cache_index, int p_size) const; + virtual void clear_kerning_map(int p_cache_index, int p_size); + virtual void remove_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair); + + virtual void set_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning); + virtual Vector2 get_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) const; + + virtual void render_range(int p_cache_index, const Vector2i &p_size, char32_t p_start, char32_t p_end); + virtual void render_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_index); + + virtual RID get_cache_rid(int p_cache_index) const; + + // Language/script support override. + virtual bool is_language_supported(const String &p_language) const; + virtual void set_language_support_override(const String &p_language, bool p_supported); + virtual bool get_language_support_override(const String &p_language) const; + virtual void remove_language_support_override(const String &p_language); + virtual Vector<String> get_language_support_overrides() const; + + virtual bool is_script_supported(const String &p_script) const; + virtual void set_script_support_override(const String &p_script, bool p_supported); + virtual bool get_script_support_override(const String &p_script) const; + virtual void remove_script_support_override(const String &p_script); + virtual Vector<String> get_script_support_overrides() const; + + // Base font properties. + virtual bool has_char(char32_t p_char) const; + virtual String get_supported_chars() const; + + virtual int32_t get_glyph_index(int p_size, char32_t p_char, char32_t p_variation_selector = 0x0000) const; + + virtual Dictionary get_supported_feature_list() const; + virtual Dictionary get_supported_variation_list() const; + + FontData(); ~FontData(); }; @@ -147,20 +210,22 @@ class TextParagraph; class Font : public Resource { GDCLASS(Font, Resource); -public: - enum SpacingType { - SPACING_TOP, - SPACING_BOTTOM, - }; - -private: - int spacing_top = 0; - int spacing_bottom = 0; - + // Shaped string cache. mutable LRUCache<uint64_t, Ref<TextLine>> cache; mutable LRUCache<uint64_t, Ref<TextParagraph>> cache_wrap; + // Font data cache. Vector<Ref<FontData>> data; + mutable Vector<RID> rids; + + // Font config. + int base_size = 16; + Dictionary variation_coordinates; + int spacing_bottom = 0; + int spacing_top = 0; + + _FORCE_INLINE_ void _data_changed(); + _FORCE_INLINE_ void _ensure_rid(int p_index) const; // Find or create cache record. protected: static void _bind_methods(); @@ -171,41 +236,49 @@ protected: virtual void reset_state() override; - void _data_changed(); - public: Dictionary get_feature_list() const; - // Font data control. - void add_data(const Ref<FontData> &p_data); - void set_data(int p_idx, const Ref<FontData> &p_data); - int get_data_count() const; - Ref<FontData> get_data(int p_idx) const; - void remove_data(int p_idx); + // Font data. + virtual void add_data(const Ref<FontData> &p_data); + virtual void set_data(int p_idx, const Ref<FontData> &p_data); + virtual int get_data_count() const; + virtual Ref<FontData> get_data(int p_idx) const; + virtual RID get_data_rid(int p_idx) const; + virtual void clear_data(); + virtual void remove_data(int p_idx); + + // Font configuration. + virtual void set_base_size(int p_size); + virtual int get_base_size() const; - float get_height(int p_size = -1) const; - float get_ascent(int p_size = -1) const; - float get_descent(int p_size = -1) const; + virtual void set_variation_coordinates(const Dictionary &p_variation_coordinates); + virtual Dictionary get_variation_coordinates() const; - float get_underline_position(int p_size = -1) const; - float get_underline_thickness(int p_size = -1) const; + virtual void set_spacing(TextServer::SpacingType p_spacing, int p_value); + virtual int get_spacing(TextServer::SpacingType p_spacing) const; - int get_spacing(int p_type) const; - void set_spacing(int p_type, int p_value); + // Font metrics. + virtual real_t get_height(int p_size = -1) const; + virtual real_t get_ascent(int p_size = -1) const; + virtual real_t get_descent(int p_size = -1) const; + virtual real_t get_underline_position(int p_size = -1) const; + virtual real_t get_underline_thickness(int p_size = -1) const; // Drawing string. - Size2 get_string_size(const String &p_text, int p_size = -1) const; - Size2 get_multiline_string_size(const String &p_text, float p_width = -1, int p_size = -1, uint8_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) const; + virtual Size2 get_string_size(const String &p_text, int p_size = -1, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, uint8_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; + virtual Size2 get_multiline_string_size(const String &p_text, real_t p_width = -1, int p_size = -1, uint8_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) const; - void draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, float p_width = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint8_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; - void draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, float p_width = -1, int p_max_lines = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint8_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; + virtual void draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint8_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; + virtual void draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_max_lines = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint8_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; // Helper functions. - bool has_char(char32_t p_char) const; - String get_supported_chars() const; + virtual bool has_char(char32_t p_char) const; + virtual String get_supported_chars() const; - Size2 get_char_size(char32_t p_char, char32_t p_next = 0, int p_size = -1) const; - float draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next = 0, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const; + // Drawing char. + virtual Size2 get_char_size(char32_t p_char, char32_t p_next = 0, int p_size = -1) const; + virtual real_t draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next = 0, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const; Vector<RID> get_rids() const; @@ -215,31 +288,4 @@ public: ~Font(); }; -VARIANT_ENUM_CAST(FontData::SpacingType); -VARIANT_ENUM_CAST(Font::SpacingType); - -/*************************************************************************/ - -class ResourceFormatLoaderFont : public ResourceFormatLoader { -public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const; - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; -}; - -#ifndef DISABLE_DEPRECATED - -class ResourceFormatLoaderCompatFont : public ResourceFormatLoader { -public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const; - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; -}; - -#endif /* DISABLE_DEPRECATED */ - #endif /* FONT_H */ diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 1d2a2ef26c..77a68151c4 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -963,7 +963,9 @@ void BaseMaterial3D::_update_shader() { } else { code += " float depth = 1.0 - texture(texture_heightmap, base_uv).r;\n"; } - code += " vec2 ofs = base_uv - view_dir.xy / view_dir.z * (depth * heightmap_scale);\n"; + // Use offset limiting to improve the appearance of non-deep parallax. + // This reduces the impression of depth, but avoids visible warping in the distance. + code += " vec2 ofs = base_uv - view_dir.xy * depth * heightmap_scale;\n"; } code += " base_uv=ofs;\n"; diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index ad589a605e..71d0c69d55 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -543,6 +543,7 @@ void Mesh::_bind_methods() { BIND_ENUM_CONSTANT(ARRAY_FORMAT_BLEND_SHAPE_MASK); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM_BASE); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM_BITS); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM0_SHIFT); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM1_SHIFT); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM2_SHIFT); diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 27b0eb098b..aa4ed1cb13 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -105,6 +105,7 @@ public: ARRAY_FORMAT_BLEND_SHAPE_MASK = RS::ARRAY_FORMAT_BLEND_SHAPE_MASK, ARRAY_FORMAT_CUSTOM_BASE = RS::ARRAY_FORMAT_CUSTOM_BASE, + ARRAY_FORMAT_CUSTOM_BITS = RS::ARRAY_FORMAT_CUSTOM_BITS, ARRAY_FORMAT_CUSTOM0_SHIFT = RS::ARRAY_FORMAT_CUSTOM0_SHIFT, ARRAY_FORMAT_CUSTOM1_SHIFT = RS::ARRAY_FORMAT_CUSTOM1_SHIFT, ARRAY_FORMAT_CUSTOM2_SHIFT = RS::ARRAY_FORMAT_CUSTOM2_SHIFT, diff --git a/scene/resources/mesh_library.cpp b/scene/resources/mesh_library.cpp index 33c9ca6d1e..cfb7c3e037 100644 --- a/scene/resources/mesh_library.cpp +++ b/scene/resources/mesh_library.cpp @@ -43,6 +43,8 @@ bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) { set_item_name(idx, p_value); } else if (what == "mesh") { set_item_mesh(idx, p_value); + } else if (what == "mesh_transform") { + set_item_mesh_transform(idx, p_value); } else if (what == "shape") { Vector<ShapeData> shapes; ShapeData sd; @@ -77,6 +79,8 @@ bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const { r_ret = get_item_name(idx); } else if (what == "mesh") { r_ret = get_item_mesh(idx); + } else if (what == "mesh_transform") { + r_ret = get_item_mesh_transform(idx); } else if (what == "shapes") { r_ret = _get_item_shapes(idx); } else if (what == "navmesh") { @@ -127,6 +131,14 @@ void MeshLibrary::set_item_mesh(int p_item, const Ref<Mesh> &p_mesh) { notify_property_list_changed(); } +void MeshLibrary::set_item_mesh_transform(int p_item, const Transform3D &p_transform) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].mesh_transform = p_transform; + notify_change_to_owners(); + emit_changed(); + notify_property_list_changed(); +} + void MeshLibrary::set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes) { ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); item_map[p_item].shapes = p_shapes; @@ -170,6 +182,11 @@ Ref<Mesh> MeshLibrary::get_item_mesh(int p_item) const { return item_map[p_item].mesh; } +Transform3D MeshLibrary::get_item_mesh_transform(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform3D(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].mesh_transform; +} + Vector<MeshLibrary::ShapeData> MeshLibrary::get_item_shapes(int p_item) const { ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Vector<ShapeData>(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); return item_map[p_item].shapes; @@ -271,12 +288,14 @@ void MeshLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("create_item", "id"), &MeshLibrary::create_item); ClassDB::bind_method(D_METHOD("set_item_name", "id", "name"), &MeshLibrary::set_item_name); ClassDB::bind_method(D_METHOD("set_item_mesh", "id", "mesh"), &MeshLibrary::set_item_mesh); + ClassDB::bind_method(D_METHOD("set_item_mesh_transform", "id", "mesh_transform"), &MeshLibrary::set_item_mesh_transform); ClassDB::bind_method(D_METHOD("set_item_navmesh", "id", "navmesh"), &MeshLibrary::set_item_navmesh); ClassDB::bind_method(D_METHOD("set_item_navmesh_transform", "id", "navmesh"), &MeshLibrary::set_item_navmesh_transform); ClassDB::bind_method(D_METHOD("set_item_shapes", "id", "shapes"), &MeshLibrary::_set_item_shapes); ClassDB::bind_method(D_METHOD("set_item_preview", "id", "texture"), &MeshLibrary::set_item_preview); ClassDB::bind_method(D_METHOD("get_item_name", "id"), &MeshLibrary::get_item_name); ClassDB::bind_method(D_METHOD("get_item_mesh", "id"), &MeshLibrary::get_item_mesh); + ClassDB::bind_method(D_METHOD("get_item_mesh_transform", "id"), &MeshLibrary::get_item_mesh_transform); ClassDB::bind_method(D_METHOD("get_item_navmesh", "id"), &MeshLibrary::get_item_navmesh); ClassDB::bind_method(D_METHOD("get_item_navmesh_transform", "id"), &MeshLibrary::get_item_navmesh_transform); ClassDB::bind_method(D_METHOD("get_item_shapes", "id"), &MeshLibrary::_get_item_shapes); diff --git a/scene/resources/mesh_library.h b/scene/resources/mesh_library.h index 1e8a6bf3ff..c25df757e9 100644 --- a/scene/resources/mesh_library.h +++ b/scene/resources/mesh_library.h @@ -52,6 +52,7 @@ public: Vector<ShapeData> shapes; Ref<Texture2D> preview; Transform3D navmesh_transform; + Transform3D mesh_transform; Ref<NavigationMesh> navmesh; }; @@ -72,12 +73,14 @@ public: void create_item(int p_item); void set_item_name(int p_item, const String &p_name); void set_item_mesh(int p_item, const Ref<Mesh> &p_mesh); + void set_item_mesh_transform(int p_item, const Transform3D &p_transform); void set_item_navmesh(int p_item, const Ref<NavigationMesh> &p_navmesh); void set_item_navmesh_transform(int p_item, const Transform3D &p_transform); void set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes); void set_item_preview(int p_item, const Ref<Texture2D> &p_preview); String get_item_name(int p_item) const; Ref<Mesh> get_item_mesh(int p_item) const; + Transform3D get_item_mesh_transform(int p_item) const; Ref<NavigationMesh> get_item_navmesh(int p_item) const; Transform3D get_item_navmesh_transform(int p_item) const; Vector<ShapeData> get_item_shapes(int p_item) const; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 54bfc427c4..e74f759855 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -379,10 +379,17 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map return OK; } + bool is_editable_instance = false; + // save the child instantiated scenes that are chosen as editable, so they can be restored // upon load back if (p_node != p_owner && p_node->get_filename() != String() && p_owner->is_editable_instance(p_node)) { editable_instances.push_back(p_owner->get_path_to(p_node)); + // Node is the root of an editable instance. + is_editable_instance = true; + } else if (p_node->get_owner() && p_node->get_owner() != p_owner && p_owner->is_editable_instance(p_node->get_owner())) { + // Node is part of an editable instance. + is_editable_instance = true; } NodeData nd; @@ -610,7 +617,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map // Save the right type. If this node was created by an instance // then flag that the node should not be created but reused - if (pack_state_stack.is_empty()) { + if (pack_state_stack.is_empty() && !is_editable_instance) { //this node is not part of an instancing process, so save the type nd.type = _nm_get_string(p_node->get_class(), name_map); } else { diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp index 34f4142a14..0495a9e92c 100644 --- a/scene/resources/particles_material.cpp +++ b/scene/resources/particles_material.cpp @@ -45,31 +45,31 @@ void ParticlesMaterial::init_shaders() { shader_names->direction = "direction"; shader_names->spread = "spread"; shader_names->flatness = "flatness"; - shader_names->initial_linear_velocity = "initial_linear_velocity"; - shader_names->initial_angle = "initial_angle"; - shader_names->angular_velocity = "angular_velocity"; - shader_names->orbit_velocity = "orbit_velocity"; - shader_names->linear_accel = "linear_accel"; - shader_names->radial_accel = "radial_accel"; - shader_names->tangent_accel = "tangent_accel"; - shader_names->damping = "damping"; - shader_names->scale = "scale"; - shader_names->hue_variation = "hue_variation"; - shader_names->anim_speed = "anim_speed"; - shader_names->anim_offset = "anim_offset"; - - shader_names->initial_linear_velocity_random = "initial_linear_velocity_random"; - shader_names->initial_angle_random = "initial_angle_random"; - shader_names->angular_velocity_random = "angular_velocity_random"; - shader_names->orbit_velocity_random = "orbit_velocity_random"; - shader_names->linear_accel_random = "linear_accel_random"; - shader_names->radial_accel_random = "radial_accel_random"; - shader_names->tangent_accel_random = "tangent_accel_random"; - shader_names->damping_random = "damping_random"; - shader_names->scale_random = "scale_random"; - shader_names->hue_variation_random = "hue_variation_random"; - shader_names->anim_speed_random = "anim_speed_random"; - shader_names->anim_offset_random = "anim_offset_random"; + shader_names->initial_linear_velocity_min = "initial_linear_velocity_min"; + shader_names->initial_angle_min = "initial_angle_min"; + shader_names->angular_velocity_min = "angular_velocity_min"; + shader_names->orbit_velocity_min = "orbit_velocity_min"; + shader_names->linear_accel_min = "linear_accel_min"; + shader_names->radial_accel_min = "radial_accel_min"; + shader_names->tangent_accel_min = "tangent_accel_min"; + shader_names->damping_min = "damping_min"; + shader_names->scale_min = "scale_min"; + shader_names->hue_variation_min = "hue_variation_min"; + shader_names->anim_speed_min = "anim_speed_min"; + shader_names->anim_offset_min = "anim_offset_min"; + + shader_names->initial_linear_velocity_max = "initial_linear_velocity_max"; + shader_names->initial_angle_max = "initial_angle_max"; + shader_names->angular_velocity_max = "angular_velocity_max"; + shader_names->orbit_velocity_max = "orbit_velocity_max"; + shader_names->linear_accel_max = "linear_accel_max"; + shader_names->radial_accel_max = "radial_accel_max"; + shader_names->tangent_accel_max = "tangent_accel_max"; + shader_names->damping_max = "damping_max"; + shader_names->scale_max = "scale_max"; + shader_names->hue_variation_max = "hue_variation_max"; + shader_names->anim_speed_max = "anim_speed_max"; + shader_names->anim_offset_max = "anim_offset_max"; shader_names->angle_texture = "angle_texture"; shader_names->angular_velocity_texture = "angular_velocity_texture"; @@ -155,31 +155,31 @@ void ParticlesMaterial::_update_shader() { code += "uniform vec3 direction;\n"; code += "uniform float spread;\n"; code += "uniform float flatness;\n"; - code += "uniform float initial_linear_velocity;\n"; - code += "uniform float initial_angle;\n"; - code += "uniform float angular_velocity;\n"; - code += "uniform float orbit_velocity;\n"; - code += "uniform float linear_accel;\n"; - code += "uniform float radial_accel;\n"; - code += "uniform float tangent_accel;\n"; - code += "uniform float damping;\n"; - code += "uniform float scale;\n"; - code += "uniform float hue_variation;\n"; - code += "uniform float anim_speed;\n"; - code += "uniform float anim_offset;\n"; - - code += "uniform float initial_linear_velocity_random;\n"; - code += "uniform float initial_angle_random;\n"; - code += "uniform float angular_velocity_random;\n"; - code += "uniform float orbit_velocity_random;\n"; - code += "uniform float linear_accel_random;\n"; - code += "uniform float radial_accel_random;\n"; - code += "uniform float tangent_accel_random;\n"; - code += "uniform float damping_random;\n"; - code += "uniform float scale_random;\n"; - code += "uniform float hue_variation_random;\n"; - code += "uniform float anim_speed_random;\n"; - code += "uniform float anim_offset_random;\n"; + code += "uniform float initial_linear_velocity_min;\n"; + code += "uniform float initial_angle_min;\n"; + code += "uniform float angular_velocity_min;\n"; + code += "uniform float orbit_velocity_min;\n"; + code += "uniform float linear_accel_min;\n"; + code += "uniform float radial_accel_min;\n"; + code += "uniform float tangent_accel_min;\n"; + code += "uniform float damping_min;\n"; + code += "uniform float scale_min;\n"; + code += "uniform float hue_variation_min;\n"; + code += "uniform float anim_speed_min;\n"; + code += "uniform float anim_offset_min;\n"; + + code += "uniform float initial_linear_velocity_max;\n"; + code += "uniform float initial_angle_max;\n"; + code += "uniform float angular_velocity_max;\n"; + code += "uniform float orbit_velocity_max;\n"; + code += "uniform float linear_accel_max;\n"; + code += "uniform float radial_accel_max;\n"; + code += "uniform float tangent_accel_max;\n"; + code += "uniform float damping_max;\n"; + code += "uniform float scale_max;\n"; + code += "uniform float hue_variation_max;\n"; + code += "uniform float anim_speed_max;\n"; + code += "uniform float anim_offset_max;\n"; code += "uniform float lifetime_randomness;\n"; switch (emission_shape) { @@ -329,7 +329,7 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) { code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(0.0, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_offset = 0.0;\n"; + code += " float tex_anim_offset = 1.0;\n"; } code += " float spread_rad = spread * degree_to_rad;\n"; @@ -339,7 +339,7 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(0.0, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_velocity = 0.0;\n"; + code += " float tex_linear_velocity = 1.0;\n"; } if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { @@ -347,7 +347,7 @@ void ParticlesMaterial::_update_shader() { code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; code += " angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0);\n"; code += " vec3 rot = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n"; - code += " VELOCITY = rot * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; + code += " VELOCITY = rot * mix(initial_linear_velocity_min,initial_linear_velocity_max, rand_from_seed(alt_seed));\n"; code += " }\n"; } else { @@ -369,16 +369,16 @@ void ParticlesMaterial::_update_shader() { code += " binormal = normalize(binormal);\n"; code += " vec3 normal = cross(binormal, direction_nrm);\n"; code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n"; - code += " VELOCITY = spread_direction * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; + code += " VELOCITY = spread_direction * mix(initial_linear_velocity_min, initial_linear_velocity_max,rand_from_seed(alt_seed));\n"; code += " }\n"; } code += " }\n"; - code += " float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n"; + code += " float base_angle = (tex_angle) * mix(initial_angle_min, initial_angle_max, angle_rand);\n"; code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle code += " CUSTOM.y = 0.0;\n"; // phase code += " CUSTOM.w = (1.0 - lifetime_randomness * rand_from_seed(alt_seed));\n"; - code += " CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random);\n"; // animation offset (0-1) + code += " CUSTOM.z = (tex_anim_offset) * mix(anim_offset_min, anim_offset_max, anim_offset_rand);\n"; // animation offset (0-1) code += " if (RESTART_POSITION) {\n"; @@ -471,63 +471,63 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_velocity = 0.0;\n"; + code += " float tex_linear_velocity = 1.0;\n"; } if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { if (tex_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { code += " float tex_orbit_velocity = textureLod(orbit_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_orbit_velocity = 0.0;\n"; + code += " float tex_orbit_velocity = 1.0;\n"; } } if (tex_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { code += " float tex_angular_velocity = textureLod(angular_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_angular_velocity = 0.0;\n"; + code += " float tex_angular_velocity = 1.0;\n"; } if (tex_parameters[PARAM_LINEAR_ACCEL].is_valid()) { code += " float tex_linear_accel = textureLod(linear_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_accel = 0.0;\n"; + code += " float tex_linear_accel = 1.0;\n"; } if (tex_parameters[PARAM_RADIAL_ACCEL].is_valid()) { code += " float tex_radial_accel = textureLod(radial_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_radial_accel = 0.0;\n"; + code += " float tex_radial_accel = 1.0;\n"; } if (tex_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { code += " float tex_tangent_accel = textureLod(tangent_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_tangent_accel = 0.0;\n"; + code += " float tex_tangent_accel = 1.0;\n"; } if (tex_parameters[PARAM_DAMPING].is_valid()) { code += " float tex_damping = textureLod(damping_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_damping = 0.0;\n"; + code += " float tex_damping = 1.0;\n"; } if (tex_parameters[PARAM_ANGLE].is_valid()) { code += " float tex_angle = textureLod(angle_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_angle = 0.0;\n"; + code += " float tex_angle = 1.0;\n"; } if (tex_parameters[PARAM_ANIM_SPEED].is_valid()) { code += " float tex_anim_speed = textureLod(anim_speed_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_speed = 0.0;\n"; + code += " float tex_anim_speed = 1.0;\n"; } if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) { code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_offset = 0.0;\n"; + code += " float tex_anim_offset = 1.0;\n"; } code += " vec3 force = gravity;\n"; @@ -536,18 +536,19 @@ void ParticlesMaterial::_update_shader() { code += " pos.z = 0.0;\n"; } code += " // apply linear acceleration\n"; - code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * (linear_accel + tex_linear_accel) * mix(1.0, rand_from_seed(alt_seed), linear_accel_random) : vec3(0.0);\n"; + code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * tex_linear_accel * mix(linear_accel_min, linear_accel_max, rand_from_seed(alt_seed)) : vec3(0.0);\n"; code += " // apply radial acceleration\n"; code += " vec3 org = EMISSION_TRANSFORM[3].xyz;\n"; code += " vec3 diff = pos - org;\n"; - code += " force += length(diff) > 0.0 ? normalize(diff) * (radial_accel + tex_radial_accel) * mix(1.0, rand_from_seed(alt_seed), radial_accel_random) : vec3(0.0);\n"; + code += " force += length(diff) > 0.0 ? normalize(diff) * tex_radial_accel * mix(radial_accel_min, radial_accel_max, rand_from_seed(alt_seed)) : vec3(0.0);\n"; code += " // apply tangential acceleration;\n"; + code += " float tangent_accel_val = tex_tangent_accel * mix(tangent_accel_min, tangent_accel_max, rand_from_seed(alt_seed))\n;"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0);\n"; + code += " force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * tangent_accel_val : vec3(0.0);\n"; } else { code += " vec3 crossDiff = cross(normalize(diff), normalize(gravity));\n"; - code += " force += length(crossDiff) > 0.0 ? normalize(crossDiff) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0);\n"; + code += " force += length(crossDiff) > 0.0 ? normalize(crossDiff) * tangent_accel_val : vec3(0.0);\n"; } if (attractor_interaction_enabled) { code += " force += ATTRACTOR_FORCE;\n\n"; @@ -557,7 +558,7 @@ void ParticlesMaterial::_update_shader() { code += " VELOCITY += force * DELTA;\n"; code += " // orbit velocity\n"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random);\n"; + code += " float orbit_amount = tex_orbit_velocity * mix(orbit_velocity_min, orbit_velocity_max, rand_from_seed(alt_seed));\n"; code += " if (orbit_amount != 0.0) {\n"; code += " float ang = orbit_amount * DELTA * pi * 2.0;\n"; code += " mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));\n"; @@ -569,9 +570,10 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { code += " VELOCITY = normalize(VELOCITY) * tex_linear_velocity;\n"; } - code += " if (damping + tex_damping > 0.0) {\n"; + code += " float dmp = mix(damping_min, damping_max, rand_from_seed(alt_seed));\n"; + code += " if (dmp * tex_damping > 0.0) {\n"; code += " float v = length(VELOCITY);\n"; - code += " float damp = (damping + tex_damping) * mix(1.0, rand_from_seed(alt_seed), damping_random);\n"; + code += " float damp = tex_damping * dmp;\n"; code += " v -= damp * DELTA;\n"; code += " if (v < 0.0) {\n"; code += " VELOCITY = vec3(0.0);\n"; @@ -579,26 +581,26 @@ void ParticlesMaterial::_update_shader() { code += " VELOCITY = normalize(VELOCITY) * v;\n"; code += " }\n"; code += " }\n"; - code += " float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n"; - code += " base_angle += CUSTOM.y * LIFETIME * (angular_velocity + tex_angular_velocity) * mix(1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, angular_velocity_random);\n"; + code += " float base_angle = (tex_angle) * mix(initial_angle_min, initial_angle_max, rand_from_seed(alt_seed));\n"; + code += " base_angle += CUSTOM.y * LIFETIME * (tex_angular_velocity) * mix(angular_velocity_min,angular_velocity_max, rand_from_seed(alt_seed));\n"; code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle - code += " CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random) + CUSTOM.y * (anim_speed + tex_anim_speed) * mix(1.0, rand_from_seed(alt_seed), anim_speed_random);\n"; // angle + code += " CUSTOM.z = (tex_anim_offset) * mix(anim_offset_min, anim_offset_max, rand_from_seed(alt_seed)) + CUSTOM.y * tex_anim_speed * mix(anim_speed_min, anim_speed_max, rand_from_seed(alt_seed));\n"; // angle // apply color // apply hue rotation if (tex_parameters[PARAM_SCALE].is_valid()) { - code += " float tex_scale = textureLod(scale_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " vec3 tex_scale = textureLod(scale_texture, vec2(tv, 0.0), 0.0).rgb;\n"; } else { - code += " float tex_scale = 1.0;\n"; + code += " vec3 tex_scale = vec3(1.0);\n"; } if (tex_parameters[PARAM_HUE_VARIATION].is_valid()) { code += " float tex_hue_variation = textureLod(hue_variation_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_hue_variation = 0.0;\n"; + code += " float tex_hue_variation = 1.0;\n"; } - code += " float hue_rot_angle = (hue_variation + tex_hue_variation) * pi * 2.0 * mix(1.0, hue_rot_rand * 2.0 - 1.0, hue_variation_random);\n"; + code += " float hue_rot_angle = (tex_hue_variation) * pi * 2.0 * mix(hue_variation_min, hue_variation_max, rand_from_seed(alt_seed));\n"; code += " float hue_rot_c = cos(hue_rot_angle);\n"; code += " float hue_rot_s = sin(hue_rot_angle);\n"; code += " mat4 hue_rot_mat = mat4(vec4(0.299, 0.587, 0.114, 0.0),\n"; @@ -660,18 +662,18 @@ void ParticlesMaterial::_update_shader() { } // turn particle by rotation in Y if (particle_flags[PARTICLE_FLAG_ROTATE_Y]) { + code += " vec4 origin = TRANSFORM[3];\n"; code += " TRANSFORM = mat4(vec4(cos(CUSTOM.x), 0.0, -sin(CUSTOM.x), 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(sin(CUSTOM.x), 0.0, cos(CUSTOM.x), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += " TRANSFORM[3] = origin;\n"; } } //scale by scale - code += " float base_scale = tex_scale * mix(scale, 1.0, scale_random * scale_rand);\n"; - code += " if (base_scale < 0.000001) {\n"; - code += " base_scale = 0.000001;\n"; - code += " }\n"; + code += " float base_scale = mix(scale_min, scale_max, scale_rand);\n"; + code += " base_scale = sign(base_scale) * max(abs(base_scale), 0.001);\n"; - code += " TRANSFORM[0].xyz *= base_scale;\n"; - code += " TRANSFORM[1].xyz *= base_scale;\n"; - code += " TRANSFORM[2].xyz *= base_scale;\n"; + code += " TRANSFORM[0].xyz *= base_scale * sign(tex_scale.r) * max(abs(tex_scale.r), 0.001);\n"; + code += " TRANSFORM[1].xyz *= base_scale * sign(tex_scale.g) * max(abs(tex_scale.g), 0.001);\n"; + code += " TRANSFORM[2].xyz *= base_scale * sign(tex_scale.b) * max(abs(tex_scale.b), 0.001);\n"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { code += " VELOCITY.z = 0.0;\n"; code += " TRANSFORM[3].z = 0.0;\n"; @@ -777,110 +779,116 @@ float ParticlesMaterial::get_flatness() const { return flatness; } -void ParticlesMaterial::set_param(Parameter p_param, float p_value) { +void ParticlesMaterial::set_param_min(Parameter p_param, float p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - parameters[p_param] = p_value; + params_min[p_param] = p_value; + if (params_min[p_param] > params_max[p_param]) { + set_param_max(p_param, p_value); + } switch (p_param) { case PARAM_INITIAL_LINEAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity_min, p_value); } break; case PARAM_ANGULAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_min, p_value); } break; case PARAM_ORBIT_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_min, p_value); } break; case PARAM_LINEAR_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_min, p_value); } break; case PARAM_RADIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_min, p_value); } break; case PARAM_TANGENTIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_min, p_value); } break; case PARAM_DAMPING: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_min, p_value); } break; case PARAM_ANGLE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle_min, p_value); } break; case PARAM_SCALE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_min, p_value); } break; case PARAM_HUE_VARIATION: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_min, p_value); } break; case PARAM_ANIM_SPEED: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_min, p_value); } break; case PARAM_ANIM_OFFSET: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_min, p_value); } break; case PARAM_MAX: break; // Can't happen, but silences warning } } -float ParticlesMaterial::get_param(Parameter p_param) const { +float ParticlesMaterial::get_param_min(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return parameters[p_param]; + return params_min[p_param]; } -void ParticlesMaterial::set_param_randomness(Parameter p_param, float p_value) { +void ParticlesMaterial::set_param_max(Parameter p_param, float p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - randomness[p_param] = p_value; + params_max[p_param] = p_value; + if (params_min[p_param] > params_max[p_param]) { + set_param_min(p_param, p_value); + } switch (p_param) { case PARAM_INITIAL_LINEAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity_max, p_value); } break; case PARAM_ANGULAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_max, p_value); } break; case PARAM_ORBIT_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_max, p_value); } break; case PARAM_LINEAR_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_max, p_value); } break; case PARAM_RADIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_max, p_value); } break; case PARAM_TANGENTIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_max, p_value); } break; case PARAM_DAMPING: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_max, p_value); } break; case PARAM_ANGLE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle_max, p_value); } break; case PARAM_SCALE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_max, p_value); } break; case PARAM_HUE_VARIATION: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_max, p_value); } break; case PARAM_ANIM_SPEED: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_max, p_value); } break; case PARAM_ANIM_OFFSET: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_max, p_value); } break; case PARAM_MAX: break; // Can't happen, but silences warning } } -float ParticlesMaterial::get_param_randomness(Parameter p_param) const { +float ParticlesMaterial::get_param_max(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return randomness[p_param]; + return params_max[p_param]; } static void _adjust_curve_range(const Ref<Texture2D> &p_texture, float p_min, float p_max) { @@ -1259,11 +1267,11 @@ void ParticlesMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flatness", "amount"), &ParticlesMaterial::set_flatness); ClassDB::bind_method(D_METHOD("get_flatness"), &ParticlesMaterial::get_flatness); - ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &ParticlesMaterial::set_param); - ClassDB::bind_method(D_METHOD("get_param", "param"), &ParticlesMaterial::get_param); + ClassDB::bind_method(D_METHOD("set_param_min", "param", "value"), &ParticlesMaterial::set_param_min); + ClassDB::bind_method(D_METHOD("get_param_min", "param"), &ParticlesMaterial::get_param_min); - ClassDB::bind_method(D_METHOD("set_param_randomness", "param", "randomness"), &ParticlesMaterial::set_param_randomness); - ClassDB::bind_method(D_METHOD("get_param_randomness", "param"), &ParticlesMaterial::get_param_randomness); + ClassDB::bind_method(D_METHOD("set_param_max", "param", "value"), &ParticlesMaterial::set_param_max); + ClassDB::bind_method(D_METHOD("get_param_max", "param"), &ParticlesMaterial::get_param_max); ClassDB::bind_method(D_METHOD("set_param_texture", "param", "texture"), &ParticlesMaterial::set_param_texture); ClassDB::bind_method(D_METHOD("get_param_texture", "param"), &ParticlesMaterial::get_param_texture); @@ -1369,54 +1377,54 @@ void ParticlesMaterial::_bind_methods() { ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_INITIAL_LINEAR_VELOCITY); ADD_GROUP("Angular Velocity", "angular_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_min", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_max", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGULAR_VELOCITY); ADD_GROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ORBIT_VELOCITY); ADD_GROUP("Linear Accel", "linear_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_LINEAR_ACCEL); ADD_GROUP("Radial Accel", "radial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_RADIAL_ACCEL); ADD_GROUP("Tangential Accel", "tangential_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param", "get_param", PARAM_DAMPING); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_min", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_max", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param", "get_param", PARAM_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_min", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_min", "get_param_min", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_max", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_max", "get_param_max", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE); ADD_GROUP("Scale", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture,CurveXYZTexture"), "set_param_texture", "get_param_texture", PARAM_SCALE); ADD_GROUP("Color", ""); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture"), "set_color_ramp", "get_color_ramp"); ADD_GROUP("Hue Variation", "hue_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param", "get_param", PARAM_HUE_VARIATION); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_max", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_max", "get_param_max", PARAM_HUE_VARIATION); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_HUE_VARIATION); ADD_GROUP("Animation", "anim_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_param", "get_param", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_min", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_max", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANIM_OFFSET); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_OFFSET); ADD_GROUP("Sub Emitter", "sub_emitter_"); @@ -1472,18 +1480,30 @@ ParticlesMaterial::ParticlesMaterial() : set_direction(Vector3(1, 0, 0)); set_spread(45); set_flatness(0); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); - set_param(PARAM_ANGULAR_VELOCITY, 0); - set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); - set_param(PARAM_RADIAL_ACCEL, 0); - set_param(PARAM_TANGENTIAL_ACCEL, 0); - set_param(PARAM_DAMPING, 0); - set_param(PARAM_ANGLE, 0); - set_param(PARAM_SCALE, 1); - set_param(PARAM_HUE_VARIATION, 0); - set_param(PARAM_ANIM_SPEED, 0); - set_param(PARAM_ANIM_OFFSET, 0); + set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_min(PARAM_ANGULAR_VELOCITY, 0); + set_param_min(PARAM_ORBIT_VELOCITY, 0); + set_param_min(PARAM_LINEAR_ACCEL, 0); + set_param_min(PARAM_RADIAL_ACCEL, 0); + set_param_min(PARAM_TANGENTIAL_ACCEL, 0); + set_param_min(PARAM_DAMPING, 0); + set_param_min(PARAM_ANGLE, 0); + set_param_min(PARAM_SCALE, 1); + set_param_min(PARAM_HUE_VARIATION, 0); + set_param_min(PARAM_ANIM_SPEED, 0); + set_param_min(PARAM_ANIM_OFFSET, 0); + set_param_max(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_max(PARAM_ANGULAR_VELOCITY, 0); + set_param_max(PARAM_ORBIT_VELOCITY, 0); + set_param_max(PARAM_LINEAR_ACCEL, 0); + set_param_max(PARAM_RADIAL_ACCEL, 0); + set_param_max(PARAM_TANGENTIAL_ACCEL, 0); + set_param_max(PARAM_DAMPING, 0); + set_param_max(PARAM_ANGLE, 0); + set_param_max(PARAM_SCALE, 1); + set_param_max(PARAM_HUE_VARIATION, 0); + set_param_max(PARAM_ANIM_SPEED, 0); + set_param_max(PARAM_ANIM_OFFSET, 0); set_emission_shape(EMISSION_SHAPE_POINT); set_emission_sphere_radius(1); set_emission_box_extents(Vector3(1, 1, 1)); @@ -1505,10 +1525,6 @@ ParticlesMaterial::ParticlesMaterial() : set_collision_friction(0.0); set_collision_use_scale(false); - for (int i = 0; i < PARAM_MAX; i++) { - set_param_randomness(Parameter(i), 0); - } - for (int i = 0; i < PARTICLE_FLAG_MAX; i++) { particle_flags[i] = false; } diff --git a/scene/resources/particles_material.h b/scene/resources/particles_material.h index 1e1821024e..8ab26aff77 100644 --- a/scene/resources/particles_material.h +++ b/scene/resources/particles_material.h @@ -154,31 +154,31 @@ private: StringName direction; StringName spread; StringName flatness; - StringName initial_linear_velocity; - StringName initial_angle; - StringName angular_velocity; - StringName orbit_velocity; - StringName linear_accel; - StringName radial_accel; - StringName tangent_accel; - StringName damping; - StringName scale; - StringName hue_variation; - StringName anim_speed; - StringName anim_offset; - - StringName initial_linear_velocity_random; - StringName initial_angle_random; - StringName angular_velocity_random; - StringName orbit_velocity_random; - StringName linear_accel_random; - StringName radial_accel_random; - StringName tangent_accel_random; - StringName damping_random; - StringName scale_random; - StringName hue_variation_random; - StringName anim_speed_random; - StringName anim_offset_random; + StringName initial_linear_velocity_min; + StringName initial_angle_min; + StringName angular_velocity_min; + StringName orbit_velocity_min; + StringName linear_accel_min; + StringName radial_accel_min; + StringName tangent_accel_min; + StringName damping_min; + StringName scale_min; + StringName hue_variation_min; + StringName anim_speed_min; + StringName anim_offset_min; + + StringName initial_linear_velocity_max; + StringName initial_angle_max; + StringName angular_velocity_max; + StringName orbit_velocity_max; + StringName linear_accel_max; + StringName radial_accel_max; + StringName tangent_accel_max; + StringName damping_max; + StringName scale_max; + StringName hue_variation_max; + StringName anim_speed_max; + StringName anim_offset_max; StringName angle_texture; StringName angular_velocity_texture; @@ -230,8 +230,8 @@ private: float spread; float flatness; - float parameters[PARAM_MAX]; - float randomness[PARAM_MAX]; + float params_min[PARAM_MAX]; + float params_max[PARAM_MAX]; Ref<Texture2D> tex_parameters[PARAM_MAX]; Color color; @@ -283,11 +283,11 @@ public: void set_flatness(float p_flatness); float get_flatness() const; - void set_param(Parameter p_param, float p_value); - float get_param(Parameter p_param) const; + void set_param_min(Parameter p_param, float p_value); + float get_param_min(Parameter p_param) const; - void set_param_randomness(Parameter p_param, float p_value); - float get_param_randomness(Parameter p_param) const; + void set_param_max(Parameter p_param, float p_value); + float get_param_max(Parameter p_param) const; void set_param_texture(Parameter p_param, const Ref<Texture2D> &p_texture); Ref<Texture2D> get_param_texture(Parameter p_param) const; diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index ba85ea4a6c..e7da41db9d 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -1420,6 +1420,8 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const { int i, j, prevrow, thisrow, point; float x, y, z; + float scale = height * (is_hemisphere ? 1.0 : 0.5); + // set our bounding box Vector<Vector3> points; @@ -1443,7 +1445,7 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const { v /= (rings + 1); w = sin(Math_PI * v); - y = height * (is_hemisphere ? 1.0 : 0.5) * cos(Math_PI * v); + y = scale * cos(Math_PI * v); for (i = 0; i <= radial_segments; i++) { float u = i; @@ -1458,7 +1460,8 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const { } else { Vector3 p = Vector3(x * radius * w, y, z * radius * w); points.push_back(p); - normals.push_back(p.normalized()); + Vector3 normal = Vector3(x * radius * w * scale, y / scale, z * radius * w * scale); + normals.push_back(normal.normalized()); }; ADD_TANGENT(z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, v)); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index dbe118a262..341ce22185 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -424,7 +424,7 @@ Error ResourceLoaderText::load() { } } - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(local_path.get_base_dir().plus_file(path)); } @@ -768,7 +768,7 @@ void ResourceLoaderText::get_dependencies(FileAccess *p_f, List<String> *p_depen } } - if (!using_uid && path.find("://") == -1 && path.is_rel_path()) { + if (!using_uid && path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(local_path.get_base_dir().plus_file(path)); } @@ -1849,10 +1849,16 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } if (groups.size()) { + // Write all groups on the same line as they're part of a section header. + // This improves readability while not impacting VCS friendliness too much, + // since it's rare to have more than 5 groups assigned to a single node. groups.sort_custom<StringName::AlphCompare>(); - String sgroups = " groups=[\n"; + String sgroups = " groups=["; for (int j = 0; j < groups.size(); j++) { - sgroups += "\"" + String(groups[j]).c_escape() + "\",\n"; + sgroups += "\"" + String(groups[j]).c_escape() + "\""; + if (j < groups.size() - 1) { + sgroups += ", "; + } } sgroups += "]"; header += sgroups; diff --git a/scene/resources/separation_ray_shape_2d.cpp b/scene/resources/separation_ray_shape_2d.cpp new file mode 100644 index 0000000000..0acd6d268d --- /dev/null +++ b/scene/resources/separation_ray_shape_2d.cpp @@ -0,0 +1,119 @@ +/*************************************************************************/ +/* separation_ray_shape_2d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "separation_ray_shape_2d.h" + +#include "servers/physics_server_2d.h" +#include "servers/rendering_server.h" + +void SeparationRayShape2D::_update_shape() { + Dictionary d; + d["length"] = length; + d["slide_on_slope"] = slide_on_slope; + PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), d); + emit_changed(); +} + +void SeparationRayShape2D::draw(const RID &p_to_rid, const Color &p_color) { + const Vector2 target_position = Vector2(0, get_length()); + + const float max_arrow_size = 6; + const float line_width = 1.4; + bool no_line = target_position.length() < line_width; + float arrow_size = CLAMP(target_position.length() * 2 / 3, line_width, max_arrow_size); + + if (no_line) { + arrow_size = target_position.length(); + } else { + RS::get_singleton()->canvas_item_add_line(p_to_rid, Vector2(), target_position - target_position.normalized() * arrow_size, p_color, line_width); + } + + Transform2D xf; + xf.rotate(target_position.angle()); + xf.translate(Vector2(no_line ? 0 : target_position.length() - arrow_size, 0)); + + Vector<Vector2> pts; + pts.push_back(xf.xform(Vector2(arrow_size, 0))); + pts.push_back(xf.xform(Vector2(0, 0.5 * arrow_size))); + pts.push_back(xf.xform(Vector2(0, -0.5 * arrow_size))); + + Vector<Color> cols; + for (int i = 0; i < 3; i++) { + cols.push_back(p_color); + } + + RS::get_singleton()->canvas_item_add_primitive(p_to_rid, pts, cols, Vector<Point2>(), RID()); +} + +Rect2 SeparationRayShape2D::get_rect() const { + Rect2 rect; + rect.position = Vector2(); + rect.expand_to(Vector2(0, length)); + rect = rect.grow(Math_SQRT12 * 4); + return rect; +} + +real_t SeparationRayShape2D::get_enclosing_radius() const { + return length; +} + +void SeparationRayShape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape2D::set_length); + ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape2D::get_length); + + ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape2D::set_slide_on_slope); + ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape2D::get_slide_on_slope); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length"), "set_length", "get_length"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope"); +} + +void SeparationRayShape2D::set_length(real_t p_length) { + length = p_length; + _update_shape(); +} + +real_t SeparationRayShape2D::get_length() const { + return length; +} + +void SeparationRayShape2D::set_slide_on_slope(bool p_active) { + slide_on_slope = p_active; + _update_shape(); +} + +bool SeparationRayShape2D::get_slide_on_slope() const { + return slide_on_slope; +} + +SeparationRayShape2D::SeparationRayShape2D() : + Shape2D(PhysicsServer2D::get_singleton()->separation_ray_shape_create()) { + _update_shape(); +} diff --git a/scene/resources/separation_ray_shape_2d.h b/scene/resources/separation_ray_shape_2d.h new file mode 100644 index 0000000000..5b74e6c727 --- /dev/null +++ b/scene/resources/separation_ray_shape_2d.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* separation_ray_shape_2d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SEPARATION_RAY_SHAPE_2D_H +#define SEPARATION_RAY_SHAPE_2D_H + +#include "scene/resources/shape_2d.h" + +class SeparationRayShape2D : public Shape2D { + GDCLASS(SeparationRayShape2D, Shape2D); + + real_t length = 20.0; + bool slide_on_slope = false; + + void _update_shape(); + +protected: + static void _bind_methods(); + +public: + void set_length(real_t p_length); + real_t get_length() const; + + void set_slide_on_slope(bool p_active); + bool get_slide_on_slope() const; + + virtual void draw(const RID &p_to_rid, const Color &p_color) override; + virtual Rect2 get_rect() const override; + virtual real_t get_enclosing_radius() const override; + + SeparationRayShape2D(); +}; + +#endif // SEPARATION_RAY_SHAPE_2D_H diff --git a/scene/resources/separation_ray_shape_3d.cpp b/scene/resources/separation_ray_shape_3d.cpp new file mode 100644 index 0000000000..376e04c844 --- /dev/null +++ b/scene/resources/separation_ray_shape_3d.cpp @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* separation_ray_shape_3d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "separation_ray_shape_3d.h" + +#include "servers/physics_server_3d.h" + +Vector<Vector3> SeparationRayShape3D::get_debug_mesh_lines() const { + Vector<Vector3> points; + points.push_back(Vector3()); + points.push_back(Vector3(0, 0, get_length())); + + return points; +} + +real_t SeparationRayShape3D::get_enclosing_radius() const { + return length; +} + +void SeparationRayShape3D::_update_shape() { + Dictionary d; + d["length"] = length; + d["slide_on_slope"] = slide_on_slope; + PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); + Shape3D::_update_shape(); +} + +void SeparationRayShape3D::set_length(float p_length) { + length = p_length; + _update_shape(); + notify_change_to_owners(); +} + +float SeparationRayShape3D::get_length() const { + return length; +} + +void SeparationRayShape3D::set_slide_on_slope(bool p_active) { + slide_on_slope = p_active; + _update_shape(); + notify_change_to_owners(); +} + +bool SeparationRayShape3D::get_slide_on_slope() const { + return slide_on_slope; +} + +void SeparationRayShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape3D::set_length); + ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape3D::get_length); + + ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape3D::set_slide_on_slope); + ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape3D::get_slide_on_slope); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0,4096,0.001"), "set_length", "get_length"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope"); +} + +SeparationRayShape3D::SeparationRayShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_SEPARATION_RAY)) { + /* Code copied from setters to prevent the use of uninitialized variables */ + _update_shape(); + notify_change_to_owners(); +} diff --git a/modules/stb_vorbis/register_types.h b/scene/resources/separation_ray_shape_3d.h index d36d87606c..54058b6095 100644 --- a/modules/stb_vorbis/register_types.h +++ b/scene/resources/separation_ray_shape_3d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* register_types.h */ +/* separation_ray_shape_3d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,10 +28,29 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef STB_VORBIS_REGISTER_TYPES_H -#define STB_VORBIS_REGISTER_TYPES_H +#ifndef SEPARATION_RAY_SHAPE_H +#define SEPARATION_RAY_SHAPE_H +#include "scene/resources/shape_3d.h" -void register_stb_vorbis_types(); -void unregister_stb_vorbis_types(); +class SeparationRayShape3D : public Shape3D { + GDCLASS(SeparationRayShape3D, Shape3D); + float length = 1.0; + bool slide_on_slope = false; -#endif // STB_VORBIS_REGISTER_TYPES_H +protected: + static void _bind_methods(); + virtual void _update_shape() override; + +public: + void set_length(float p_length); + float get_length() const; + + void set_slide_on_slope(bool p_active); + bool get_slide_on_slope() const; + + virtual Vector<Vector3> get_debug_mesh_lines() const override; + virtual real_t get_enclosing_radius() const override; + + SeparationRayShape3D(); +}; +#endif // SEPARATION_RAY_SHAPE_H diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 875aa30824..d5e370568d 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -409,7 +409,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = CLAMP(int32_t(c.r * 255.0), 0, 255); w[idx * 4 + 1] = CLAMP(int32_t(c.g * 255.0), 0, 255); w[idx * 4 + 2] = CLAMP(int32_t(c.b * 255.0), 0, 255); @@ -426,7 +426,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = uint8_t(int8_t(CLAMP(int32_t(c.r * 127.0), -128, 127))); w[idx * 4 + 1] = uint8_t(int8_t(CLAMP(int32_t(c.g * 127.0), -128, 127))); w[idx * 4 + 2] = uint8_t(int8_t(CLAMP(int32_t(c.b * 127.0), -128, 127))); @@ -443,7 +443,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 2 + 0] = Math::make_half_float(c.r); w[idx * 2 + 1] = Math::make_half_float(c.g); } @@ -458,7 +458,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = Math::make_half_float(c.r); w[idx * 4 + 1] = Math::make_half_float(c.g); w[idx * 4 + 2] = Math::make_half_float(c.b); @@ -475,7 +475,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx] = c.r; } @@ -489,7 +489,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 2 + 0] = c.r; w[idx * 2 + 1] = c.g; } @@ -504,7 +504,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 3 + 0] = c.r; w[idx * 3 + 1] = c.g; w[idx * 3 + 2] = c.b; @@ -520,7 +520,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = c.r; w[idx * 4 + 1] = c.g; w[idx * 4 + 2] = c.b; @@ -679,6 +679,9 @@ void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, Local _create_list_from_arrays(arr, r_vertex, r_index, lformat); } +static const uint32_t custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 }; +static const uint32_t custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; + void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint32_t *r_format) { ret.clear(); @@ -733,8 +736,6 @@ void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays if (warr.size()) { lformat |= RS::ARRAY_FORMAT_WEIGHTS; } - static const uint32_t custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 }; - static const uint32_t custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; for (int i = 0; i < RS::ARRAY_CUSTOM_COUNT; i++) { ERR_CONTINUE_MSG(p_arrays[RS::ARRAY_CUSTOM0 + i].get_type() == Variant::PACKED_BYTE_ARRAY, "Extracting Byte/Half formats is not supported"); @@ -832,6 +833,12 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { clear(); primitive = Mesh::PRIMITIVE_TRIANGLES; _create_list_from_arrays(p_arrays, &vertex_array, &index_array, format); + + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (format & custom_mask[j]) { + last_custom_format[j] = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); + } + } } void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { @@ -841,6 +848,12 @@ void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { primitive = p_existing->surface_get_primitive_type(p_surface); _create_list(p_existing, p_surface, &vertex_array, &index_array, format); material = p_existing->surface_get_material(p_surface); + + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (format & custom_mask[j]) { + last_custom_format[j] = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); + } + } } void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name) { @@ -863,6 +876,12 @@ void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_sur Array mesh = arr[shape_idx]; ERR_FAIL_COND(mesh.size() != RS::ARRAY_MAX); _create_list_from_arrays(arr[shape_idx], &vertex_array, &index_array, format); + + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (format & custom_mask[j]) { + last_custom_format[j] = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); + } + } } void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform3D &p_xform) { @@ -878,6 +897,16 @@ void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const LocalVector<int> nindices; _create_list(p_existing, p_surface, &nvertices, &nindices, nformat); format |= nformat; + + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (format & custom_mask[j]) { + CustomFormat new_format = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); + if (last_custom_format[j] != CUSTOM_MAX && last_custom_format[j] != new_format) { + WARN_PRINT(vformat("Custom %d format %d mismatch when appending format %d", j, last_custom_format[j], new_format)); + } + last_custom_format[j] = new_format; + } + } int vfrom = vertex_array.size(); for (uint32_t vi = 0; vi < nvertices.size(); vi++) { diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp index e1c5ebb005..52a3abf74d 100644 --- a/scene/resources/syntax_highlighter.cpp +++ b/scene/resources/syntax_highlighter.cpp @@ -43,7 +43,9 @@ Dictionary SyntaxHighlighter::get_line_syntax_highlighting(int p_line) { return color_map; } - GDVIRTUAL_CALL(_get_line_syntax_highlighting, p_line, color_map); + if (!GDVIRTUAL_CALL(_get_line_syntax_highlighting, p_line, color_map)) { + color_map = _get_line_syntax_highlighting_impl(p_line); + } highlighting_cache[p_line] = color_map; return color_map; diff --git a/scene/resources/text_line.cpp b/scene/resources/text_line.cpp index 0807a062f2..d2f38ba836 100644 --- a/scene/resources/text_line.cpp +++ b/scene/resources/text_line.cpp @@ -211,8 +211,8 @@ void TextLine::set_bidi_override(const Vector<Vector2i> &p_override) { bool TextLine::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) { ERR_FAIL_COND_V(p_fonts.is_null(), false); bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language); - spacing_top = p_fonts->get_spacing(Font::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(Font::SPACING_BOTTOM); + spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP); + spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM); dirty = true; return res; } @@ -409,8 +409,8 @@ int TextLine::hit_test(float p_coords) const { TextLine::TextLine(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, TextServer::Direction p_direction, TextServer::Orientation p_orientation) { rid = TS->create_shaped_text(p_direction, p_orientation); - spacing_top = p_fonts->get_spacing(Font::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(Font::SPACING_BOTTOM); + spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP); + spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM); TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language); } diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 357411ae04..62949b9b98 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -333,8 +333,8 @@ void TextParagraph::clear_dropcap() { bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) { ERR_FAIL_COND_V(p_fonts.is_null(), false); bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language); - spacing_top = p_fonts->get_spacing(Font::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(Font::SPACING_BOTTOM); + spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP); + spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM); lines_dirty = true; return res; } @@ -437,7 +437,8 @@ Size2 TextParagraph::get_non_wraped_size() const { Size2 TextParagraph::get_size() const { const_cast<TextParagraph *>(this)->_shape_lines(); Size2 size; - for (int i = 0; i < lines_rid.size(); i++) { + int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : lines_rid.size(); + for (int i = 0; i < visible_lines; i++) { Size2 lsize = TS->shaped_text_get_size(lines_rid[i]); if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { size.x = MAX(size.x, lsize.x); @@ -587,15 +588,15 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo l_width -= h_offset; } } - float length = TS->shaped_text_get_width(lines_rid[i]); + float line_width = TS->shaped_text_get_width(lines_rid[i]); if (width > 0) { switch (align) { case HALIGN_FILL: if (TS->shaped_text_get_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) { if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.x += l_width - length; + ofs.x += l_width - line_width; } else { - ofs.y += l_width - length; + ofs.y += l_width - line_width; } } break; @@ -603,16 +604,16 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo break; case HALIGN_CENTER: { if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.x += Math::floor((l_width - length) / 2.0); + ofs.x += Math::floor((l_width - line_width) / 2.0); } else { - ofs.y += Math::floor((l_width - length) / 2.0); + ofs.y += Math::floor((l_width - line_width) / 2.0); } } break; case HALIGN_RIGHT: { if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.x += l_width - length; + ofs.x += l_width - line_width; } else { - ofs.y += l_width - length; + ofs.y += l_width - line_width; } } break; } @@ -828,8 +829,8 @@ void TextParagraph::draw_line_outline(RID p_canvas, const Vector2 &p_pos, int p_ TextParagraph::TextParagraph(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, float p_width, TextServer::Direction p_direction, TextServer::Orientation p_orientation) { rid = TS->create_shaped_text(p_direction, p_orientation); TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language); - spacing_top = p_fonts->get_spacing(Font::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(Font::SPACING_BOTTOM); + spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP); + spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM); width = p_width; } diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 4ad5f2a506..063a13efc0 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -1758,11 +1758,16 @@ void GradientTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture::get_gradient); ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture::set_width); + // The `get_width()` method is already exposed by the parent class Texture2D. + + ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture::set_use_hdr); + ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture::is_using_hdr); ClassDB::bind_method(D_METHOD("_update"), &GradientTexture::_update); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_gradient", "get_gradient"); ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr"); } void GradientTexture::set_gradient(Ref<Gradient> p_gradient) { @@ -1800,30 +1805,49 @@ void GradientTexture::_update() { return; } - Vector<uint8_t> data; - data.resize(width * 4); - { - uint8_t *wd8 = data.ptrw(); + if (use_hdr) { + // High dynamic range. + Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBAF)); Gradient &g = **gradient; - + // `create()` isn't available for non-uint8_t data, so fill in the data manually. for (int i = 0; i < width; i++) { float ofs = float(i) / (width - 1); - Color color = g.get_color_at_offset(ofs); + image->set_pixel(i, 0, g.get_color_at_offset(ofs)); + } - wd8[i * 4 + 0] = uint8_t(CLAMP(color.r * 255.0, 0, 255)); - wd8[i * 4 + 1] = uint8_t(CLAMP(color.g * 255.0, 0, 255)); - wd8[i * 4 + 2] = uint8_t(CLAMP(color.b * 255.0, 0, 255)); - wd8[i * 4 + 3] = uint8_t(CLAMP(color.a * 255.0, 0, 255)); + if (texture.is_valid()) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(texture, new_texture); + } else { + texture = RS::get_singleton()->texture_2d_create(image); + } + } else { + // Low dynamic range. "Overbright" colors will be clamped. + Vector<uint8_t> data; + data.resize(width * 4); + { + uint8_t *wd8 = data.ptrw(); + Gradient &g = **gradient; + + for (int i = 0; i < width; i++) { + float ofs = float(i) / (width - 1); + Color color = g.get_color_at_offset(ofs); + + wd8[i * 4 + 0] = uint8_t(CLAMP(color.r * 255.0, 0, 255)); + wd8[i * 4 + 1] = uint8_t(CLAMP(color.g * 255.0, 0, 255)); + wd8[i * 4 + 2] = uint8_t(CLAMP(color.b * 255.0, 0, 255)); + wd8[i * 4 + 3] = uint8_t(CLAMP(color.a * 255.0, 0, 255)); + } } - } - Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBA8, data)); + Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBA8, data)); - if (texture.is_valid()) { - RID new_texture = RS::get_singleton()->texture_2d_create(image); - RS::get_singleton()->texture_replace(texture, new_texture); - } else { - texture = RS::get_singleton()->texture_2d_create(image); + if (texture.is_valid()) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(texture, new_texture); + } else { + texture = RS::get_singleton()->texture_2d_create(image); + } } emit_changed(); @@ -1839,6 +1863,19 @@ int GradientTexture::get_width() const { return width; } +void GradientTexture::set_use_hdr(bool p_enabled) { + if (p_enabled == use_hdr) { + return; + } + + use_hdr = p_enabled; + _queue_update(); +} + +bool GradientTexture::is_using_hdr() const { + return use_hdr; +} + Ref<Image> GradientTexture::get_image() const { if (!texture.is_valid()) { return Ref<Image>(); diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 2e97c2deb1..f6b991c335 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -686,6 +686,7 @@ private: bool update_pending = false; RID texture; int width = 2048; + bool use_hdr = false; void _queue_update(); void _update(); @@ -700,6 +701,9 @@ public: void set_width(int p_width); int get_width() const override; + void set_use_hdr(bool p_enabled); + bool is_using_hdr() const; + virtual RID get_rid() const override { return texture; } virtual int get_height() const override { return 1; } virtual bool has_alpha() const override { return true; } diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index fcd31143a8..e288e18f33 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -205,25 +205,46 @@ bool TileSet::is_uv_clipping() const { return uv_clipping; }; -void TileSet::set_occlusion_layers_count(int p_occlusion_layers_count) { - ERR_FAIL_COND(p_occlusion_layers_count < 0); - if (occlusion_layers.size() == p_occlusion_layers_count) { - return; - } +int TileSet::get_occlusion_layers_count() const { + return occlusion_layers.size(); +}; - occlusion_layers.resize(p_occlusion_layers_count); +void TileSet::add_occlusion_layer(int p_index) { + if (p_index < 0) { + p_index = occlusion_layers.size(); + } + ERR_FAIL_INDEX(p_index, occlusion_layers.size() + 1); + occlusion_layers.insert(p_index, OcclusionLayer()); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_occlusion_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_occlusion_layers_count() const { - return occlusion_layers.size(); -}; +void TileSet::move_occlusion_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, occlusion_layers.size()); + ERR_FAIL_INDEX(p_to_pos, occlusion_layers.size() + 1); + occlusion_layers.insert(p_to_pos, occlusion_layers[p_from_index]); + occlusion_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_occlusion_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_occlusion_layer(int p_index) { + ERR_FAIL_INDEX(p_index, occlusion_layers.size()); + occlusion_layers.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_occlusion_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); +} void TileSet::set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask) { ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); @@ -236,7 +257,7 @@ int TileSet::get_occlusion_layer_light_mask(int p_layer_index) const { return occlusion_layers[p_layer_index].light_mask; } -void TileSet::set_occlusion_layer_sdf_collision(int p_layer_index, int p_sdf_collision) { +void TileSet::set_occlusion_layer_sdf_collision(int p_layer_index, bool p_sdf_collision) { ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); occlusion_layers.write[p_layer_index].sdf_collision = p_sdf_collision; emit_changed(); @@ -247,25 +268,45 @@ bool TileSet::get_occlusion_layer_sdf_collision(int p_layer_index) const { return occlusion_layers[p_layer_index].sdf_collision; } -// Physics -void TileSet::set_physics_layers_count(int p_physics_layers_count) { - ERR_FAIL_COND(p_physics_layers_count < 0); - if (physics_layers.size() == p_physics_layers_count) { - return; - } +int TileSet::get_physics_layers_count() const { + return physics_layers.size(); +} - physics_layers.resize(p_physics_layers_count); +void TileSet::add_physics_layer(int p_index) { + if (p_index < 0) { + p_index = physics_layers.size(); + } + ERR_FAIL_INDEX(p_index, physics_layers.size() + 1); + physics_layers.insert(p_index, PhysicsLayer()); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_physics_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_physics_layers_count() const { - return physics_layers.size(); +void TileSet::move_physics_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, physics_layers.size()); + ERR_FAIL_INDEX(p_to_pos, physics_layers.size() + 1); + physics_layers.insert(p_to_pos, physics_layers[p_from_index]); + physics_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_physics_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_physics_layer(int p_index) { + ERR_FAIL_INDEX(p_index, physics_layers.size()); + physics_layers.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_physics_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer) { @@ -301,17 +342,45 @@ Ref<PhysicsMaterial> TileSet::get_physics_layer_physics_material(int p_layer_ind } // Terrains -void TileSet::set_terrain_sets_count(int p_terrains_sets_count) { - ERR_FAIL_COND(p_terrains_sets_count < 0); +int TileSet::get_terrain_sets_count() const { + return terrain_sets.size(); +} - terrain_sets.resize(p_terrains_sets_count); +void TileSet::add_terrain_set(int p_index) { + if (p_index < 0) { + p_index = terrain_sets.size(); + } + ERR_FAIL_INDEX(p_index, terrain_sets.size() + 1); + terrain_sets.insert(p_index, TerrainSet()); + + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_terrain_set(p_index); + } notify_property_list_changed(); emit_changed(); } -int TileSet::get_terrain_sets_count() const { - return terrain_sets.size(); +void TileSet::move_terrain_set(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, terrain_sets.size()); + ERR_FAIL_INDEX(p_to_pos, terrain_sets.size() + 1); + terrain_sets.insert(p_to_pos, terrain_sets[p_from_index]); + terrain_sets.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_terrain_set(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_terrain_set(int p_index) { + ERR_FAIL_INDEX(p_index, terrain_sets.size()); + terrain_sets.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_terrain_set(p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode) { @@ -330,36 +399,61 @@ TileSet::TerrainMode TileSet::get_terrain_set_mode(int p_terrain_set) const { return terrain_sets[p_terrain_set].mode; } -void TileSet::set_terrains_count(int p_terrain_set, int p_terrains_layers_count) { +int TileSet::get_terrains_count(int p_terrain_set) const { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1); + return terrain_sets[p_terrain_set].terrains.size(); +} + +void TileSet::add_terrain(int p_terrain_set, int p_index) { ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); - ERR_FAIL_COND(p_terrains_layers_count < 0); - if (terrain_sets[p_terrain_set].terrains.size() == p_terrains_layers_count) { - return; + Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains; + if (p_index < 0) { + p_index = terrains.size(); } - - int old_size = terrain_sets[p_terrain_set].terrains.size(); - terrain_sets.write[p_terrain_set].terrains.resize(p_terrains_layers_count); + ERR_FAIL_INDEX(p_index, terrains.size() + 1); + terrains.insert(p_index, Terrain()); // Default name and color - for (int i = old_size; i < terrain_sets.write[p_terrain_set].terrains.size(); i++) { - float hue_rotate = (i * 2 % 16) / 16.0; - Color c; - c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5); - terrain_sets.write[p_terrain_set].terrains.write[i].color = c; - terrain_sets.write[p_terrain_set].terrains.write[i].name = String(vformat("Terrain %d", i)); + float hue_rotate = (terrains.size() % 16) / 16.0; + Color c; + c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5); + terrains.write[p_index].color = c; + terrains.write[p_index].name = String(vformat("Terrain %d", p_index)); + + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_terrain(p_terrain_set, p_index); } - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); - } + notify_property_list_changed(); + emit_changed(); +} +void TileSet::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains; + + ERR_FAIL_INDEX(p_from_index, terrains.size()); + ERR_FAIL_INDEX(p_to_pos, terrains.size() + 1); + terrains.insert(p_to_pos, terrains[p_from_index]); + terrains.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_terrain(p_terrain_set, p_from_index, p_to_pos); + } notify_property_list_changed(); emit_changed(); } -int TileSet::get_terrains_count(int p_terrain_set) const { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1); - return terrain_sets[p_terrain_set].terrains.size(); +void TileSet::remove_terrain(int p_terrain_set, int p_index) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains; + + ERR_FAIL_INDEX(p_index, terrains.size()); + terrains.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_terrain(p_terrain_set, p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name) { @@ -485,24 +579,45 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh } // Navigation -void TileSet::set_navigation_layers_count(int p_navigation_layers_count) { - ERR_FAIL_COND(p_navigation_layers_count < 0); - if (navigation_layers.size() == p_navigation_layers_count) { - return; - } +int TileSet::get_navigation_layers_count() const { + return navigation_layers.size(); +} - navigation_layers.resize(p_navigation_layers_count); +void TileSet::add_navigation_layer(int p_index) { + if (p_index < 0) { + p_index = navigation_layers.size(); + } + ERR_FAIL_INDEX(p_index, navigation_layers.size() + 1); + navigation_layers.insert(p_index, NavigationLayer()); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_navigation_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_navigation_layers_count() const { - return navigation_layers.size(); +void TileSet::move_navigation_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, navigation_layers.size()); + ERR_FAIL_INDEX(p_to_pos, navigation_layers.size() + 1); + navigation_layers.insert(p_to_pos, navigation_layers[p_from_index]); + navigation_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_navigation_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_navigation_layer(int p_index) { + ERR_FAIL_INDEX(p_index, navigation_layers.size()); + navigation_layers.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_navigation_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_navigation_layer_layers(int p_layer_index, uint32_t p_layers) { @@ -517,30 +632,52 @@ uint32_t TileSet::get_navigation_layer_layers(int p_layer_index) const { } // Custom data. -void TileSet::set_custom_data_layers_count(int p_custom_data_layers_count) { - ERR_FAIL_COND(p_custom_data_layers_count < 0); - if (custom_data_layers.size() == p_custom_data_layers_count) { - return; - } - - custom_data_layers.resize(p_custom_data_layers_count); +int TileSet::get_custom_data_layers_count() const { + return custom_data_layers.size(); +} - for (Map<String, int>::Element *E = custom_data_layers_by_name.front(); E; E = E->next()) { - if (E->get() >= custom_data_layers.size()) { - custom_data_layers_by_name.erase(E); - } +void TileSet::add_custom_data_layer(int p_index) { + if (p_index < 0) { + p_index = custom_data_layers.size(); } + ERR_FAIL_INDEX(p_index, custom_data_layers.size() + 1); + custom_data_layers.insert(p_index, CustomDataLayer()); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_custom_data_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_custom_data_layers_count() const { - return custom_data_layers.size(); +void TileSet::move_custom_data_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, custom_data_layers.size()); + ERR_FAIL_INDEX(p_to_pos, custom_data_layers.size() + 1); + custom_data_layers.insert(p_to_pos, custom_data_layers[p_from_index]); + custom_data_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_custom_data_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_custom_data_layer(int p_index) { + ERR_FAIL_INDEX(p_index, custom_data_layers.size()); + custom_data_layers.remove(p_index); + for (KeyValue<String, int> E : custom_data_layers_by_name) { + if (E.value == p_index) { + custom_data_layers_by_name.erase(E.key); + break; + } + } + + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_custom_data_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); } int TileSet::get_custom_data_layer_by_name(String p_value) const { @@ -1110,7 +1247,11 @@ Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) { if (is_valid_peering_bit_terrain(terrain_set, cell_neighbor)) { int terrain = tile_data->get_peering_bit_terrain(cell_neighbor); if (terrain >= 0) { - bit_counts[terrain] += 1; + if (terrain >= (int)bit_counts.size()) { + WARN_PRINT(vformat("Invalid peering bit terrain: %d", terrain)); + } else { + bit_counts[terrain] += 1; + } } } } @@ -1825,19 +1966,19 @@ void TileSet::_compatibility_conversion() { tile_data->set_flip_h(flip_h); tile_data->set_flip_v(flip_v); tile_data->set_transpose(transpose); - tile_data->tile_set_material(ctd->material); + tile_data->set_material(ctd->material); tile_data->set_modulate(ctd->modulate); tile_data->set_z_index(ctd->z_index); if (ctd->occluder.is_valid()) { if (get_occlusion_layers_count() < 1) { - set_occlusion_layers_count(1); + add_occlusion_layer(); } tile_data->set_occluder(0, ctd->occluder); } if (ctd->navigation.is_valid()) { if (get_navigation_layers_count() < 1) { - set_navigation_layers_count(1); + add_navigation_layer(); } tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]); } @@ -1847,7 +1988,7 @@ void TileSet::_compatibility_conversion() { // Add the shapes. if (ctd->shapes.size() > 0) { if (get_physics_layers_count() < 1) { - set_physics_layers_count(1); + add_physics_layer(); } } for (int k = 0; k < ctd->shapes.size(); k++) { @@ -1917,18 +2058,18 @@ void TileSet::_compatibility_conversion() { tile_data->set_flip_h(flip_h); tile_data->set_flip_v(flip_v); tile_data->set_transpose(transpose); - tile_data->tile_set_material(ctd->material); + tile_data->set_material(ctd->material); tile_data->set_modulate(ctd->modulate); tile_data->set_z_index(ctd->z_index); if (ctd->autotile_occluder_map.has(coords)) { if (get_occlusion_layers_count() < 1) { - set_occlusion_layers_count(1); + add_occlusion_layer(); } tile_data->set_occluder(0, ctd->autotile_occluder_map[coords]); } if (ctd->autotile_navpoly_map.has(coords)) { if (get_navigation_layers_count() < 1) { - set_navigation_layers_count(1); + add_navigation_layer(); } tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]); } @@ -1942,7 +2083,7 @@ void TileSet::_compatibility_conversion() { // Add the shapes. if (ctd->shapes.size() > 0) { if (get_physics_layers_count() < 1) { - set_physics_layers_count(1); + add_physics_layer(); } } for (int k = 0; k < ctd->shapes.size(); k++) { @@ -2206,15 +2347,15 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "light_mask") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= occlusion_layers.size()) { - set_occlusion_layers_count(index + 1); + while (index >= occlusion_layers.size()) { + add_occlusion_layer(); } set_occlusion_layer_light_mask(index, p_value); return true; } else if (components[1] == "sdf_collision") { ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false); - if (index >= occlusion_layers.size()) { - set_occlusion_layers_count(index + 1); + while (index >= occlusion_layers.size()) { + add_occlusion_layer(); } set_occlusion_layer_sdf_collision(index, p_value); return true; @@ -2225,23 +2366,22 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "collision_layer") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= physics_layers.size()) { - set_physics_layers_count(index + 1); + while (index >= physics_layers.size()) { + add_physics_layer(); } set_physics_layer_collision_layer(index, p_value); return true; } else if (components[1] == "collision_mask") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= physics_layers.size()) { - set_physics_layers_count(index + 1); + while (index >= physics_layers.size()) { + add_physics_layer(); } set_physics_layer_collision_mask(index, p_value); return true; } else if (components[1] == "physics_material") { Ref<PhysicsMaterial> physics_material = p_value; - ERR_FAIL_COND_V(!physics_material.is_valid(), false); - if (index >= physics_layers.size()) { - set_physics_layers_count(index + 1); + while (index >= physics_layers.size()) { + add_physics_layer(); } set_physics_layer_physics_material(index, physics_material); return true; @@ -2252,37 +2392,30 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(terrain_set_index < 0, false); if (components[1] == "mode") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); } set_terrain_set_mode(terrain_set_index, TerrainMode(int(p_value))); - } else if (components[1] == "terrains_count") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); - } - set_terrains_count(terrain_set_index, p_value); - return true; } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { int terrain_index = components[1].trim_prefix("terrain_").to_int(); ERR_FAIL_COND_V(terrain_index < 0, false); if (components[2] == "name") { ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); } - if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { - set_terrains_count(terrain_set_index, terrain_index + 1); + while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + add_terrain(terrain_set_index); } set_terrain_name(terrain_set_index, terrain_index, p_value); return true; } else if (components[2] == "color") { ERR_FAIL_COND_V(p_value.get_type() != Variant::COLOR, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); } - if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { - set_terrains_count(terrain_set_index, terrain_index + 1); + while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + add_terrain(terrain_set_index); } set_terrain_color(terrain_set_index, terrain_index, p_value); return true; @@ -2294,8 +2427,8 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "layers") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= navigation_layers.size()) { - set_navigation_layers_count(index + 1); + while (index >= navigation_layers.size()) { + add_navigation_layer(); } set_navigation_layer_layers(index, p_value); return true; @@ -2306,15 +2439,15 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "name") { ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); - if (index >= custom_data_layers.size()) { - set_custom_data_layers_count(index + 1); + while (index >= custom_data_layers.size()) { + add_custom_data_layer(); } set_custom_data_name(index, p_value); return true; } else if (components[1] == "type") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= custom_data_layers.size()) { - set_custom_data_layers_count(index + 1); + while (index >= custom_data_layers.size()) { + add_custom_data_layer(); } set_custom_data_type(index, Variant::Type(int(p_value))); return true; @@ -2402,9 +2535,6 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const { if (components[1] == "mode") { r_ret = get_terrain_set_mode(terrain_set_index); return true; - } else if (components[1] == "terrains_count") { - r_ret = get_terrains_count(terrain_set_index); - return true; } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { int terrain_index = components[1].trim_prefix("terrain_").to_int(); if (terrain_index < 0 || terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { @@ -2522,7 +2652,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::NIL, "Terrains", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); for (int terrain_set_index = 0; terrain_set_index < terrain_sets.size(); terrain_set_index++) { p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/mode", terrain_set_index), PROPERTY_HINT_ENUM, "Match corners and sides,Match corners,Match sides")); - p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/terrains_count", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + p_list->push_back(PropertyInfo(Variant::NIL, vformat("terrain_set_%d/terrains", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, vformat("terrain_set_%d/terrain_", terrain_set_index))); for (int terrain_index = 0; terrain_index < terrain_sets[terrain_set_index].terrains.size(); terrain_index++) { p_list->push_back(PropertyInfo(Variant::STRING, vformat("terrain_set_%d/terrain_%d/name", terrain_set_index, terrain_index))); p_list->push_back(PropertyInfo(Variant::COLOR, vformat("terrain_set_%d/terrain_%d/color", terrain_set_index, terrain_index))); @@ -2563,13 +2693,13 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { 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(TileSet::INVALID_SOURCE)); + ClassDB::bind_method(D_METHOD("add_source", "source", "atlas_source_id_override"), &TileSet::add_source, DEFVAL(TileSet::INVALID_SOURCE)); ClassDB::bind_method(D_METHOD("remove_source", "source_id"), &TileSet::remove_source); - ClassDB::bind_method(D_METHOD("set_source_id", "source_id"), &TileSet::set_source_id); + ClassDB::bind_method(D_METHOD("set_source_id", "source_id", "new_source_id"), &TileSet::set_source_id); ClassDB::bind_method(D_METHOD("get_source_count"), &TileSet::get_source_count); ClassDB::bind_method(D_METHOD("get_source_id", "index"), &TileSet::get_source_id); - ClassDB::bind_method(D_METHOD("has_source", "index"), &TileSet::has_source); - ClassDB::bind_method(D_METHOD("get_source", "index"), &TileSet::get_source); + ClassDB::bind_method(D_METHOD("has_source", "source_id"), &TileSet::has_source); + ClassDB::bind_method(D_METHOD("get_source", "source_id"), &TileSet::get_source); // Shape and layout. ClassDB::bind_method(D_METHOD("set_tile_shape", "shape"), &TileSet::set_tile_shape); @@ -2590,16 +2720,20 @@ void TileSet::_bind_methods() { ClassDB::bind_method(D_METHOD("set_uv_clipping", "uv_clipping"), &TileSet::set_uv_clipping); ClassDB::bind_method(D_METHOD("is_uv_clipping"), &TileSet::is_uv_clipping); - ClassDB::bind_method(D_METHOD("set_occlusion_layers_count", "occlusion_layers_count"), &TileSet::set_occlusion_layers_count); ClassDB::bind_method(D_METHOD("get_occlusion_layers_count"), &TileSet::get_occlusion_layers_count); + ClassDB::bind_method(D_METHOD("add_occlusion_layer", "to_position"), &TileSet::add_occlusion_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_occlusion_layer", "layer_index", "to_position"), &TileSet::move_occlusion_layer); + ClassDB::bind_method(D_METHOD("remove_occlusion_layer", "layer_index"), &TileSet::remove_occlusion_layer); ClassDB::bind_method(D_METHOD("set_occlusion_layer_light_mask", "layer_index", "light_mask"), &TileSet::set_occlusion_layer_light_mask); - ClassDB::bind_method(D_METHOD("get_occlusion_layer_light_mask"), &TileSet::get_occlusion_layer_light_mask); + ClassDB::bind_method(D_METHOD("get_occlusion_layer_light_mask", "layer_index"), &TileSet::get_occlusion_layer_light_mask); ClassDB::bind_method(D_METHOD("set_occlusion_layer_sdf_collision", "layer_index", "sdf_collision"), &TileSet::set_occlusion_layer_sdf_collision); - ClassDB::bind_method(D_METHOD("get_occlusion_layer_sdf_collision"), &TileSet::get_occlusion_layer_sdf_collision); + ClassDB::bind_method(D_METHOD("get_occlusion_layer_sdf_collision", "layer_index"), &TileSet::get_occlusion_layer_sdf_collision); // Physics - ClassDB::bind_method(D_METHOD("set_physics_layers_count", "physics_layers_count"), &TileSet::set_physics_layers_count); ClassDB::bind_method(D_METHOD("get_physics_layers_count"), &TileSet::get_physics_layers_count); + ClassDB::bind_method(D_METHOD("add_physics_layer", "to_position"), &TileSet::add_physics_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_physics_layer", "layer_index", "to_position"), &TileSet::move_physics_layer); + ClassDB::bind_method(D_METHOD("remove_physics_layer", "layer_index"), &TileSet::remove_physics_layer); ClassDB::bind_method(D_METHOD("set_physics_layer_collision_layer", "layer_index", "layer"), &TileSet::set_physics_layer_collision_layer); ClassDB::bind_method(D_METHOD("get_physics_layer_collision_layer", "layer_index"), &TileSet::get_physics_layer_collision_layer); ClassDB::bind_method(D_METHOD("set_physics_layer_collision_mask", "layer_index", "mask"), &TileSet::set_physics_layer_collision_mask); @@ -2608,27 +2742,35 @@ void TileSet::_bind_methods() { ClassDB::bind_method(D_METHOD("get_physics_layer_physics_material", "layer_index"), &TileSet::get_physics_layer_physics_material); // Terrains - ClassDB::bind_method(D_METHOD("set_terrain_sets_count", "terrain_sets_count"), &TileSet::set_terrain_sets_count); ClassDB::bind_method(D_METHOD("get_terrain_sets_count"), &TileSet::get_terrain_sets_count); + ClassDB::bind_method(D_METHOD("add_terrain_set", "to_position"), &TileSet::add_terrain_set, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_terrain_set", "terrain_set", "to_position"), &TileSet::move_terrain_set); + ClassDB::bind_method(D_METHOD("remove_terrain_set", "terrain_set"), &TileSet::remove_terrain_set); ClassDB::bind_method(D_METHOD("set_terrain_set_mode", "terrain_set", "mode"), &TileSet::set_terrain_set_mode); ClassDB::bind_method(D_METHOD("get_terrain_set_mode", "terrain_set"), &TileSet::get_terrain_set_mode); - ClassDB::bind_method(D_METHOD("set_terrains_count", "terrain_set", "terrains_count"), &TileSet::set_terrains_count); ClassDB::bind_method(D_METHOD("get_terrains_count", "terrain_set"), &TileSet::get_terrains_count); + ClassDB::bind_method(D_METHOD("add_terrain", "terrain_set", "to_position"), &TileSet::add_terrain, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_terrain", "terrain_set", "terrain_index", "to_position"), &TileSet::move_terrain); + ClassDB::bind_method(D_METHOD("remove_terrain", "terrain_set", "terrain_index"), &TileSet::remove_terrain); ClassDB::bind_method(D_METHOD("set_terrain_name", "terrain_set", "terrain_index", "name"), &TileSet::set_terrain_name); ClassDB::bind_method(D_METHOD("get_terrain_name", "terrain_set", "terrain_index"), &TileSet::get_terrain_name); ClassDB::bind_method(D_METHOD("set_terrain_color", "terrain_set", "terrain_index", "color"), &TileSet::set_terrain_color); ClassDB::bind_method(D_METHOD("get_terrain_color", "terrain_set", "terrain_index"), &TileSet::get_terrain_color); // Navigation - ClassDB::bind_method(D_METHOD("set_navigation_layers_count", "navigation_layers_count"), &TileSet::set_navigation_layers_count); ClassDB::bind_method(D_METHOD("get_navigation_layers_count"), &TileSet::get_navigation_layers_count); + ClassDB::bind_method(D_METHOD("add_navigation_layer", "to_position"), &TileSet::add_navigation_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_navigation_layer", "layer_index", "to_position"), &TileSet::move_navigation_layer); + ClassDB::bind_method(D_METHOD("remove_navigation_layer", "layer_index"), &TileSet::remove_navigation_layer); ClassDB::bind_method(D_METHOD("set_navigation_layer_layers", "layer_index", "layers"), &TileSet::set_navigation_layer_layers); ClassDB::bind_method(D_METHOD("get_navigation_layer_layers", "layer_index"), &TileSet::get_navigation_layer_layers); // Custom data - ClassDB::bind_method(D_METHOD("set_custom_data_layers_count", "custom_data_layers_count"), &TileSet::set_custom_data_layers_count); ClassDB::bind_method(D_METHOD("get_custom_data_layers_count"), &TileSet::get_custom_data_layers_count); + ClassDB::bind_method(D_METHOD("add_custom_data_layer", "to_position"), &TileSet::add_custom_data_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_custom_data_layer", "layer_index", "to_position"), &TileSet::move_custom_data_layer); + ClassDB::bind_method(D_METHOD("remove_custom_data_layer", "layer_index"), &TileSet::remove_custom_data_layer); // Tile proxies ClassDB::bind_method(D_METHOD("set_source_level_tile_proxy", "source_from", "source_to"), &TileSet::set_source_level_tile_proxy); @@ -2653,19 +2795,19 @@ void TileSet::_bind_methods() { ADD_GROUP("Rendering", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "occlusion_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_occlusion_layers_count", "get_occlusion_layers_count"); + ADD_ARRAY("occlusion_layers", "occlusion_layer_"); ADD_GROUP("Physics", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_physics_layers_count", "get_physics_layers_count"); + ADD_ARRAY("physics_layers", "physics_layer_"); ADD_GROUP("Terrains", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "terrains_sets_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_terrain_sets_count", "get_terrain_sets_count"); + ADD_ARRAY("terrain_sets", "terrain_set_"); ADD_GROUP("Navigation", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_navigation_layers_count", "get_navigation_layers_count"); + ADD_ARRAY("navigation_layers", "navigation_layer_"); ADD_GROUP("Custom data", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_data_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_custom_data_layers_count", "get_custom_data_layers_count"); + ADD_ARRAY("custom_data_layers", "custom_data_layer_"); // -- Enum binding -- BIND_ENUM_CONSTANT(TILE_SHAPE_SQUARE); @@ -2728,6 +2870,18 @@ void TileSetSource::set_tile_set(const TileSet *p_tile_set) { tile_set = p_tile_set; } +void TileSetSource::_bind_methods() { + // Base tiles + ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetSource::get_tiles_count); + ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetSource::get_tile_id); + ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetSource::has_tile); + + // Alternative tiles + ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetSource::get_alternative_tiles_count); + ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetSource::get_alternative_tile_id); + ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetSource::has_alternative_tile); +} + /////////////////////////////// TileSetAtlasSource ////////////////////////////////////// void TileSetAtlasSource::set_tile_set(const TileSet *p_tile_set) { @@ -2750,6 +2904,150 @@ void TileSetAtlasSource::notify_tile_data_properties_should_change() { } } +void TileSetAtlasSource::add_occlusion_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_occlusion_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_occlusion_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_occlusion_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_occlusion_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_occlusion_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_physics_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_physics_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_physics_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_physics_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_physics_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_physics_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_terrain_set(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_terrain_set(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_terrain_set(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_terrain_set(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_terrain_set(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_terrain_set(p_index); + } + } +} + +void TileSetAtlasSource::add_terrain(int p_terrain_set, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_terrain(p_terrain_set, p_to_pos); + } + } +} + +void TileSetAtlasSource::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_terrain(p_terrain_set, p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_terrain(int p_terrain_set, int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_terrain(p_terrain_set, p_index); + } + } +} + +void TileSetAtlasSource::add_navigation_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_navigation_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_navigation_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_navigation_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_navigation_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_navigation_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_custom_data_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_custom_data_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_custom_data_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_custom_data_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_custom_data_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_custom_data_layer(p_index); + } + } +} + void TileSetAtlasSource::reset_state() { // Reset all TileData. for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) { @@ -3273,32 +3571,24 @@ void TileSetAtlasSource::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NOEDITOR), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_margins", "get_margins"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_separation", "get_separation"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_texture_region_size", "get_texture_region_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_texture_region_size", "get_texture_region_size"); // Base tiles ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1))); ClassDB::bind_method(D_METHOD("remove_tile", "atlas_coords"), &TileSetAtlasSource::remove_tile); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative - ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetAtlasSource::has_tile); ClassDB::bind_method(D_METHOD("can_move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::can_move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); ClassDB::bind_method(D_METHOD("move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas); - ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetAtlasSource::get_tiles_count); - ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetAtlasSource::get_tile_id); - ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords); // Alternative tiles ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile); ClassDB::bind_method(D_METHOD("set_alternative_tile_id", "atlas_coords", "alternative_tile", "new_id"), &TileSetAtlasSource::set_alternative_tile_id); - ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::has_alternative_tile); ClassDB::bind_method(D_METHOD("get_next_alternative_tile_id", "atlas_coords"), &TileSetAtlasSource::get_next_alternative_tile_id); - ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetAtlasSource::get_alternative_tiles_count); - ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetAtlasSource::get_alternative_tile_id); - - ClassDB::bind_method(D_METHOD("get_tile_data", "atlas_coords", "index"), &TileSetAtlasSource::get_tile_data); + ClassDB::bind_method(D_METHOD("get_tile_data", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::get_tile_data); // Helpers. ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size); @@ -3511,16 +3801,6 @@ void TileSetScenesCollectionSource::_get_property_list(List<PropertyInfo> *p_lis } void TileSetScenesCollectionSource::_bind_methods() { - // Base tiles - ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetScenesCollectionSource::get_tiles_count); - ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetScenesCollectionSource::get_tile_id); - ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetScenesCollectionSource::has_tile); - - // Alternative tiles - ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetScenesCollectionSource::get_alternative_tiles_count); - ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetScenesCollectionSource::get_alternative_tile_id); - ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetScenesCollectionSource::has_alternative_tile); - ClassDB::bind_method(D_METHOD("get_scene_tiles_count"), &TileSetScenesCollectionSource::get_scene_tiles_count); ClassDB::bind_method(D_METHOD("get_scene_tile_id", "index"), &TileSetScenesCollectionSource::get_scene_tile_id); ClassDB::bind_method(D_METHOD("has_scene_tile_id", "id"), &TileSetScenesCollectionSource::has_scene_tile_id); @@ -3575,6 +3855,155 @@ void TileData::notify_tile_data_properties_should_change() { emit_signal(SNAME("changed")); } +void TileData::add_occlusion_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = occluders.size(); + } + ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1); + occluders.insert(p_to_pos, Ref<OccluderPolygon2D>()); +} + +void TileData::move_occlusion_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, occluders.size()); + ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1); + occluders.insert(p_to_pos, occluders[p_from_index]); + occluders.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_occlusion_layer(int p_index) { + ERR_FAIL_INDEX(p_index, occluders.size()); + occluders.remove(p_index); +} + +void TileData::add_physics_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = physics.size(); + } + ERR_FAIL_INDEX(p_to_pos, physics.size() + 1); + physics.insert(p_to_pos, PhysicsLayerTileData()); +} + +void TileData::move_physics_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, physics.size()); + ERR_FAIL_INDEX(p_to_pos, physics.size() + 1); + physics.insert(p_to_pos, physics[p_from_index]); + physics.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_physics_layer(int p_index) { + ERR_FAIL_INDEX(p_index, physics.size()); + physics.remove(p_index); +} + +void TileData::add_terrain_set(int p_to_pos) { + if (p_to_pos >= 0 && p_to_pos <= terrain_set) { + terrain_set += 1; + } +} + +void TileData::move_terrain_set(int p_from_index, int p_to_pos) { + if (p_from_index == terrain_set) { + terrain_set = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos; + } else { + if (p_from_index < terrain_set) { + terrain_set -= 1; + } + if (p_to_pos <= terrain_set) { + terrain_set += 1; + } + } +} + +void TileData::remove_terrain_set(int p_index) { + if (p_index == terrain_set) { + terrain_set = -1; + for (int i = 0; i < 16; i++) { + terrain_peering_bits[i] = -1; + } + } else if (terrain_set > p_index) { + terrain_set -= 1; + } +} + +void TileData::add_terrain(int p_terrain_set, int p_to_pos) { + if (terrain_set == p_terrain_set) { + for (int i = 0; i < 16; i++) { + if (p_to_pos >= 0 && p_to_pos <= terrain_peering_bits[i]) { + terrain_peering_bits[i] += 1; + } + } + } +} + +void TileData::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + if (terrain_set == p_terrain_set) { + for (int i = 0; i < 16; i++) { + if (p_from_index == terrain_peering_bits[i]) { + terrain_peering_bits[i] = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos; + } else { + if (p_from_index < terrain_peering_bits[i]) { + terrain_peering_bits[i] -= 1; + } + if (p_to_pos <= terrain_peering_bits[i]) { + terrain_peering_bits[i] += 1; + } + } + } + } +} + +void TileData::remove_terrain(int p_terrain_set, int p_index) { + if (terrain_set == p_terrain_set) { + for (int i = 0; i < 16; i++) { + if (terrain_peering_bits[i] == p_index) { + terrain_peering_bits[i] = -1; + } else if (terrain_peering_bits[i] > p_index) { + terrain_peering_bits[i] -= 1; + } + } + } +} + +void TileData::add_navigation_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = navigation.size(); + } + ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1); + navigation.insert(p_to_pos, Ref<NavigationPolygon>()); +} + +void TileData::move_navigation_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, navigation.size()); + ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1); + navigation.insert(p_to_pos, navigation[p_from_index]); + navigation.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_navigation_layer(int p_index) { + ERR_FAIL_INDEX(p_index, navigation.size()); + navigation.remove(p_index); +} + +void TileData::add_custom_data_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = custom_data.size(); + } + ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); + custom_data.insert(p_to_pos, Variant()); +} + +void TileData::move_custom_data_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, custom_data.size()); + ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); + custom_data.insert(p_to_pos, navigation[p_from_index]); + custom_data.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_custom_data_layer(int p_index) { + ERR_FAIL_INDEX(p_index, custom_data.size()); + custom_data.remove(p_index); +} + void TileData::reset_state() { occluders.clear(); physics.clear(); @@ -3628,11 +4057,11 @@ Vector2i TileData::get_texture_offset() const { return tex_offset; } -void TileData::tile_set_material(Ref<ShaderMaterial> p_material) { +void TileData::set_material(Ref<ShaderMaterial> p_material) { material = p_material; emit_signal(SNAME("changed")); } -Ref<ShaderMaterial> TileData::tile_get_material() const { +Ref<ShaderMaterial> TileData::get_material() const { return material; } @@ -4148,8 +4577,8 @@ void TileData::_bind_methods() { ClassDB::bind_method(D_METHOD("get_flip_v"), &TileData::get_flip_v); ClassDB::bind_method(D_METHOD("set_transpose", "transpose"), &TileData::set_transpose); ClassDB::bind_method(D_METHOD("get_transpose"), &TileData::get_transpose); - ClassDB::bind_method(D_METHOD("tile_set_material", "material"), &TileData::tile_set_material); - ClassDB::bind_method(D_METHOD("tile_get_material"), &TileData::tile_get_material); + ClassDB::bind_method(D_METHOD("set_material", "material"), &TileData::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &TileData::get_material); ClassDB::bind_method(D_METHOD("set_texture_offset", "texture_offset"), &TileData::set_texture_offset); ClassDB::bind_method(D_METHOD("get_texture_offset"), &TileData::get_texture_offset); ClassDB::bind_method(D_METHOD("set_modulate", "modulate"), &TileData::set_modulate); @@ -4200,6 +4629,7 @@ void TileData::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "transpose"), "set_transpose", "get_transpose"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_offset"), "set_texture_offset", "get_texture_offset"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index"), "set_z_index", "get_z_index"); ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin"); diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 35e6999d13..3baf022dc0 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -225,10 +225,10 @@ private: bool terrain_bits_meshes_dirty = true; // Navigation - struct Navigationlayer { + struct NavigationLayer { uint32_t layers = 1; }; - Vector<Navigationlayer> navigation_layers; + Vector<NavigationLayer> navigation_layers; // CustomData struct CustomDataLayer { @@ -298,16 +298,20 @@ public: void set_uv_clipping(bool p_uv_clipping); bool is_uv_clipping() const; - void set_occlusion_layers_count(int p_occlusion_layers_count); int get_occlusion_layers_count() const; + void add_occlusion_layer(int p_index = -1); + void move_occlusion_layer(int p_from_index, int p_to_pos); + void remove_occlusion_layer(int p_index); void set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask); int get_occlusion_layer_light_mask(int p_layer_index) const; - void set_occlusion_layer_sdf_collision(int p_layer_index, int p_sdf_collision); + void set_occlusion_layer_sdf_collision(int p_layer_index, bool p_sdf_collision); bool get_occlusion_layer_sdf_collision(int p_layer_index) const; // Physics - void set_physics_layers_count(int p_physics_layers_count); int get_physics_layers_count() const; + void add_physics_layer(int p_index = -1); + void move_physics_layer(int p_from_index, int p_to_pos); + void remove_physics_layer(int p_index); void set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer); uint32_t get_physics_layer_collision_layer(int p_layer_index) const; void set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask); @@ -315,13 +319,19 @@ public: void set_physics_layer_physics_material(int p_layer_index, Ref<PhysicsMaterial> p_physics_material); Ref<PhysicsMaterial> get_physics_layer_physics_material(int p_layer_index) const; - // Terrains - void set_terrain_sets_count(int p_terrains_sets_count); + // Terrain sets int get_terrain_sets_count() const; + void add_terrain_set(int p_index = -1); + void move_terrain_set(int p_from_index, int p_to_pos); + void remove_terrain_set(int p_index); void set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode); TerrainMode get_terrain_set_mode(int p_terrain_set) const; - void set_terrains_count(int p_terrain_set, int p_terrains_count); + + // Terrains int get_terrains_count(int p_terrain_set) const; + void add_terrain(int p_terrain_set, int p_index = -1); + void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos); + void remove_terrain(int p_terrain_set, int p_index); void set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name); String get_terrain_name(int p_terrain_set, int p_terrain_index) const; void set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color); @@ -330,14 +340,18 @@ public: bool is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const; // Navigation - void set_navigation_layers_count(int p_navigation_layers_count); int get_navigation_layers_count() const; + void add_navigation_layer(int p_index = -1); + void move_navigation_layer(int p_from_index, int p_to_pos); + void remove_navigation_layer(int p_index); void set_navigation_layer_layers(int p_layer_index, uint32_t p_layers); uint32_t get_navigation_layer_layers(int p_layer_index) const; // Custom data - void set_custom_data_layers_count(int p_custom_data_layers_count); int get_custom_data_layers_count() const; + void add_custom_data_layer(int p_index = -1); + void move_custom_data_layer(int p_from_index, int p_to_pos); + void remove_custom_data_layer(int p_index); int get_custom_data_layer_by_name(String p_value) const; void set_custom_data_name(int p_layer_id, String p_value); String get_custom_data_name(int p_layer_id) const; @@ -390,6 +404,8 @@ class TileSetSource : public Resource { protected: const TileSet *tile_set = nullptr; + static void _bind_methods(); + public: static const Vector2i INVALID_ATLAS_COORDS; // Vector2i(-1, -1); static const int INVALID_TILE_ALTERNATIVE; // -1; @@ -397,6 +413,24 @@ public: // Not exposed. virtual void set_tile_set(const TileSet *p_tile_set); virtual void notify_tile_data_properties_should_change(){}; + virtual void add_occlusion_layer(int p_index){}; + virtual void move_occlusion_layer(int p_from_index, int p_to_pos){}; + virtual void remove_occlusion_layer(int p_index){}; + virtual void add_physics_layer(int p_index){}; + virtual void move_physics_layer(int p_from_index, int p_to_pos){}; + virtual void remove_physics_layer(int p_index){}; + virtual void add_terrain_set(int p_index){}; + virtual void move_terrain_set(int p_from_index, int p_to_pos){}; + virtual void remove_terrain_set(int p_index){}; + virtual void add_terrain(int p_terrain_set, int p_index){}; + virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos){}; + virtual void remove_terrain(int p_terrain_set, int p_index){}; + virtual void add_navigation_layer(int p_index){}; + virtual void move_navigation_layer(int p_from_index, int p_to_pos){}; + virtual void remove_navigation_layer(int p_index){}; + virtual void add_custom_data_layer(int p_index){}; + virtual void move_custom_data_layer(int p_from_index, int p_to_pos){}; + virtual void remove_custom_data_layer(int p_index){}; virtual void reset_state() override{}; // Tiles. @@ -448,6 +482,24 @@ public: // Not exposed. virtual void set_tile_set(const TileSet *p_tile_set) override; virtual void notify_tile_data_properties_should_change() override; + virtual void add_occlusion_layer(int p_index) override; + virtual void move_occlusion_layer(int p_from_index, int p_to_pos) override; + virtual void remove_occlusion_layer(int p_index) override; + virtual void add_physics_layer(int p_index) override; + virtual void move_physics_layer(int p_from_index, int p_to_pos) override; + virtual void remove_physics_layer(int p_index) override; + virtual void add_terrain_set(int p_index) override; + virtual void move_terrain_set(int p_from_index, int p_to_pos) override; + virtual void remove_terrain_set(int p_index) override; + virtual void add_terrain(int p_terrain_set, int p_index) override; + virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) override; + virtual void remove_terrain(int p_terrain_set, int p_index) override; + virtual void add_navigation_layer(int p_index) override; + virtual void move_navigation_layer(int p_from_index, int p_to_pos) override; + virtual void remove_navigation_layer(int p_index) override; + virtual void add_custom_data_layer(int p_index) override; + virtual void move_custom_data_layer(int p_from_index, int p_to_pos) override; + virtual void remove_custom_data_layer(int p_index) override; virtual void reset_state() override; // Base properties. @@ -528,7 +580,7 @@ public: int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override; bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override; - // Scenes sccessors. Lot are similar to "Alternative tiles". + // Scenes accessors. Lot are similar to "Alternative tiles". int get_scene_tiles_count() { return get_alternative_tiles_count(Vector2i()); } int get_scene_tile_id(int p_index) { return get_alternative_tile_id(Vector2i(), p_index); }; bool has_scene_tile_id(int p_id) { return has_alternative_tile(Vector2i(), p_id); }; @@ -597,6 +649,24 @@ public: // Not exposed. void set_tile_set(const TileSet *p_tile_set); void notify_tile_data_properties_should_change(); + void add_occlusion_layer(int p_index); + void move_occlusion_layer(int p_from_index, int p_to_pos); + void remove_occlusion_layer(int p_index); + void add_physics_layer(int p_index); + void move_physics_layer(int p_from_index, int p_to_pos); + void remove_physics_layer(int p_index); + void add_terrain_set(int p_index); + void move_terrain_set(int p_from_index, int p_to_pos); + void remove_terrain_set(int p_index); + void add_terrain(int p_terrain_set, int p_index); + void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos); + void remove_terrain(int p_terrain_set, int p_index); + void add_navigation_layer(int p_index); + void move_navigation_layer(int p_from_index, int p_to_pos); + void remove_navigation_layer(int p_index); + void add_custom_data_layer(int p_index); + void move_custom_data_layer(int p_from_index, int p_to_pos); + void remove_custom_data_layer(int p_index); void reset_state(); void set_allow_transform(bool p_allow_transform); bool is_allowing_transform() const; @@ -611,8 +681,8 @@ public: void set_texture_offset(Vector2i p_texture_offset); Vector2i get_texture_offset() const; - void tile_set_material(Ref<ShaderMaterial> p_material); - Ref<ShaderMaterial> tile_get_material() const; + void set_material(Ref<ShaderMaterial> p_material); + Ref<ShaderMaterial> get_material() const; void set_modulate(Color p_modulate); Color get_modulate() const; void set_z_index(int p_z_index); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index a7f99a2113..e8fe3ff3cd 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -335,7 +335,7 @@ String VisualShaderNodeCustom::get_output_port_name(int p_port) const { } String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - ERR_FAIL_COND_V(!GDVIRTUAL_IS_OVERRIDEN(_get_code), ""); + ERR_FAIL_COND_V(!GDVIRTUAL_IS_OVERRIDDEN(_get_code), ""); Vector<String> input_vars; for (int i = 0; i < get_input_port_count(); i++) { input_vars.push_back(p_input_vars[i]); diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index f3fa857682..c098a97906 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -33,6 +33,65 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +void AudioStreamPlayback::start(float p_from_pos) { + if (GDVIRTUAL_CALL(_start, p_from_pos)) { + return; + } + ERR_FAIL_MSG("AudioStreamPlayback::start unimplemented!"); +} +void AudioStreamPlayback::stop() { + if (GDVIRTUAL_CALL(_stop)) { + return; + } + ERR_FAIL_MSG("AudioStreamPlayback::stop unimplemented!"); +} +bool AudioStreamPlayback::is_playing() const { + bool ret; + if (GDVIRTUAL_CALL(_is_playing, ret)) { + return ret; + } + ERR_FAIL_V_MSG(false, "AudioStreamPlayback::is_playing unimplemented!"); +} + +int AudioStreamPlayback::get_loop_count() const { + int ret; + if (GDVIRTUAL_CALL(_get_loop_count, ret)) { + return ret; + } + return 0; +} + +float AudioStreamPlayback::get_playback_position() const { + float ret; + if (GDVIRTUAL_CALL(_get_playback_position, ret)) { + return ret; + } + ERR_FAIL_V_MSG(0, "AudioStreamPlayback::get_playback_position unimplemented!"); +} +void AudioStreamPlayback::seek(float p_time) { + if (GDVIRTUAL_CALL(_seek, p_time)) { + return; + } +} + +int AudioStreamPlayback::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { + int ret; + if (GDVIRTUAL_CALL(_mix, p_buffer, p_rate_scale, p_frames, ret)) { + return ret; + } + WARN_PRINT_ONCE("AudioStreamPlayback::mix unimplemented!"); + return 0; +} + +void AudioStreamPlayback::_bind_methods() { + GDVIRTUAL_BIND(_start, "from_pos") + GDVIRTUAL_BIND(_stop) + GDVIRTUAL_BIND(_is_playing) + GDVIRTUAL_BIND(_get_loop_count) + GDVIRTUAL_BIND(_get_playback_position) + GDVIRTUAL_BIND(_seek, "position") + GDVIRTUAL_BIND(_mix, "buffer", "rate_scale", "frames"); +} ////////////////////////////// void AudioStreamPlaybackResampled::_begin_resample() { @@ -46,12 +105,14 @@ void AudioStreamPlaybackResampled::_begin_resample() { mix_offset = 0; } -void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { +int AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { float target_rate = AudioServer::get_singleton()->get_mix_rate(); float playback_speed_scale = AudioServer::get_singleton()->get_playback_speed_scale(); uint64_t mix_increment = uint64_t(((get_stream_sampling_rate() * p_rate_scale * playback_speed_scale) / double(target_rate)) * double(FP_LEN)); + int mixed_frames_total = p_frames; + for (int i = 0; i < p_frames; i++) { uint32_t idx = CUBIC_INTERP_HISTORY + uint32_t(mix_offset >> FP_BITS); //standard cubic interpolation (great quality/performance ratio) @@ -62,6 +123,11 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, AudioFrame y2 = internal_buffer[idx - 1]; AudioFrame y3 = internal_buffer[idx - 0]; + if (idx <= internal_buffer_end && idx >= internal_buffer_end && mixed_frames_total == p_frames) { + // The internal buffer ends somewhere in this range, and we haven't yet recorded the number of good frames we have. + mixed_frames_total = i; + } + float mu2 = mu * mu; AudioFrame a0 = 3 * y1 - 3 * y2 + y3 - y0; AudioFrame a1 = 2 * y0 - 5 * y1 + 4 * y2 - y3; @@ -78,7 +144,14 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, internal_buffer[2] = internal_buffer[INTERNAL_BUFFER_LEN + 2]; internal_buffer[3] = internal_buffer[INTERNAL_BUFFER_LEN + 3]; if (is_playing()) { - _mix_internal(internal_buffer + 4, INTERNAL_BUFFER_LEN); + int mixed_frames = _mix_internal(internal_buffer + 4, INTERNAL_BUFFER_LEN); + if (mixed_frames != INTERNAL_BUFFER_LEN) { + // internal_buffer[mixed_frames] is the first frame of silence. + internal_buffer_end = mixed_frames; + } else { + // The internal buffer does not contain the first frame of silence. + internal_buffer_end = -1; + } } else { //fill with silence, not playing for (int j = 0; j < INTERNAL_BUFFER_LEN; ++j) { @@ -88,12 +161,49 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, mix_offset -= (INTERNAL_BUFFER_LEN << FP_BITS); } } + return mixed_frames_total; } //////////////////////////////// +Ref<AudioStreamPlayback> AudioStream::instance_playback() { + Ref<AudioStreamPlayback> ret; + if (GDVIRTUAL_CALL(_instance_playback, ret)) { + return ret; + } + ERR_FAIL_V_MSG(Ref<AudioStreamPlayback>(), "Method must be implemented!"); +} +String AudioStream::get_stream_name() const { + String ret; + if (GDVIRTUAL_CALL(_get_stream_name, ret)) { + return ret; + } + return String(); +} + +float AudioStream::get_length() const { + float ret; + if (GDVIRTUAL_CALL(_get_length, ret)) { + return ret; + } + return 0; +} + +bool AudioStream::is_monophonic() const { + bool ret; + if (GDVIRTUAL_CALL(_is_monophonic, ret)) { + return ret; + } + return true; +} + void AudioStream::_bind_methods() { ClassDB::bind_method(D_METHOD("get_length"), &AudioStream::get_length); + ClassDB::bind_method(D_METHOD("is_monophonic"), &AudioStream::is_monophonic); + GDVIRTUAL_BIND(_instance_playback); + GDVIRTUAL_BIND(_get_stream_name); + GDVIRTUAL_BIND(_get_length); + GDVIRTUAL_BIND(_is_monophonic); } //////////////////////////////// @@ -121,13 +231,17 @@ float AudioStreamMicrophone::get_length() const { return 0; } +bool AudioStreamMicrophone::is_monophonic() const { + return true; +} + void AudioStreamMicrophone::_bind_methods() { } AudioStreamMicrophone::AudioStreamMicrophone() { } -void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_frames) { +int AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_frames) { AudioDriver::get_singleton()->lock(); Vector<int32_t> buf = AudioDriver::get_singleton()->get_input_buffer(); @@ -138,6 +252,8 @@ void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_fr unsigned int input_position = AudioDriver::get_singleton()->get_input_position(); #endif + int mixed_frames = p_frames; + if (playback_delay > input_size) { for (int i = 0; i < p_frames; i++) { p_buffer[i] = AudioFrame(0.0f, 0.0f); @@ -157,6 +273,9 @@ void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_fr p_buffer[i] = AudioFrame(l, r); } else { + if (mixed_frames == p_frames) { + mixed_frames = i; + } p_buffer[i] = AudioFrame(0.0f, 0.0f); } } @@ -169,10 +288,12 @@ void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_fr #endif AudioDriver::get_singleton()->unlock(); + + return mixed_frames; } -void AudioStreamPlaybackMicrophone::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { - AudioStreamPlaybackResampled::mix(p_buffer, p_rate_scale, p_frames); +int AudioStreamPlaybackMicrophone::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { + return AudioStreamPlaybackResampled::mix(p_buffer, p_rate_scale, p_frames); } float AudioStreamPlaybackMicrophone::get_stream_sampling_rate() { @@ -281,6 +402,14 @@ float AudioStreamRandomPitch::get_length() const { return 0; } +bool AudioStreamRandomPitch::is_monophonic() const { + if (audio_stream.is_valid()) { + return audio_stream->is_monophonic(); + } + + return true; // It doesn't really matter what we return here, but no sense instancing a many playbacks of a null stream. +} + void AudioStreamRandomPitch::_bind_methods() { ClassDB::bind_method(D_METHOD("set_audio_stream", "stream"), &AudioStreamRandomPitch::set_audio_stream); ClassDB::bind_method(D_METHOD("get_audio_stream"), &AudioStreamRandomPitch::get_audio_stream); @@ -345,16 +474,18 @@ void AudioStreamPlaybackRandomPitch::seek(float p_time) { } } -void AudioStreamPlaybackRandomPitch::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { +int AudioStreamPlaybackRandomPitch::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { if (playing.is_valid()) { - playing->mix(p_buffer, p_rate_scale * pitch_scale, p_frames); + return playing->mix(p_buffer, p_rate_scale * pitch_scale, p_frames); } else { for (int i = 0; i < p_frames; i++) { p_buffer[i] = AudioFrame(0, 0); } + return p_frames; } } AudioStreamPlaybackRandomPitch::~AudioStreamPlaybackRandomPitch() { random_pitch->playbacks.erase(this); } +///////////////////////////////////////////// diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index 0d426f99b2..12d4343f5c 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -36,20 +36,33 @@ #include "servers/audio/audio_filter_sw.h" #include "servers/audio_server.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" +#include "core/variant/native_ptr.h" + class AudioStreamPlayback : public RefCounted { GDCLASS(AudioStreamPlayback, RefCounted); +protected: + static void _bind_methods(); + GDVIRTUAL1(_start, float) + GDVIRTUAL0(_stop) + GDVIRTUAL0RC(bool, _is_playing) + GDVIRTUAL0RC(int, _get_loop_count) + GDVIRTUAL0RC(float, _get_playback_position) + GDVIRTUAL1(_seek, float) + GDVIRTUAL3R(int, _mix, GDNativePtr<AudioFrame>, float, int) public: - virtual void start(float p_from_pos = 0.0) = 0; - virtual void stop() = 0; - virtual bool is_playing() const = 0; + virtual void start(float p_from_pos = 0.0); + virtual void stop(); + virtual bool is_playing() const; - virtual int get_loop_count() const = 0; //times it looped + virtual int get_loop_count() const; //times it looped - virtual float get_playback_position() const = 0; - virtual void seek(float p_time) = 0; + virtual float get_playback_position() const; + virtual void seek(float p_time); - virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) = 0; + virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames); }; class AudioStreamPlaybackResampled : public AudioStreamPlayback { @@ -64,15 +77,17 @@ class AudioStreamPlaybackResampled : public AudioStreamPlayback { }; AudioFrame internal_buffer[INTERNAL_BUFFER_LEN + CUBIC_INTERP_HISTORY]; + unsigned int internal_buffer_end = -1; uint64_t mix_offset; protected: void _begin_resample(); - virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) = 0; + // Returns the number of frames that were mixed. + virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) = 0; virtual float get_stream_sampling_rate() = 0; public: - virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; + virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; AudioStreamPlaybackResampled() { mix_offset = 0; } }; @@ -84,11 +99,17 @@ class AudioStream : public Resource { protected: static void _bind_methods(); + GDVIRTUAL0RC(Ref<AudioStreamPlayback>, _instance_playback) + GDVIRTUAL0RC(String, _get_stream_name) + GDVIRTUAL0RC(float, _get_length) + GDVIRTUAL0RC(bool, _is_monophonic) + public: - virtual Ref<AudioStreamPlayback> instance_playback() = 0; - virtual String get_stream_name() const = 0; + virtual Ref<AudioStreamPlayback> instance_playback(); + virtual String get_stream_name() const; - virtual float get_length() const = 0; //if supported, otherwise return 0 + virtual float get_length() const; + virtual bool is_monophonic() const; }; // Microphone @@ -110,6 +131,8 @@ public: virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; + AudioStreamMicrophone(); }; @@ -123,11 +146,11 @@ class AudioStreamPlaybackMicrophone : public AudioStreamPlaybackResampled { Ref<AudioStreamMicrophone> microphone; protected: - virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) override; + virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override; virtual float get_stream_sampling_rate() override; public: - virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; + virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; virtual void start(float p_from_pos = 0.0) override; virtual void stop() override; @@ -168,6 +191,7 @@ public: virtual String get_stream_name() const override; virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; AudioStreamRandomPitch(); }; @@ -191,7 +215,7 @@ public: virtual float get_playback_position() const override; virtual void seek(float p_time) override; - virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; + virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; ~AudioStreamPlaybackRandomPitch(); }; diff --git a/servers/audio/effects/audio_effect_capture.h b/servers/audio/effects/audio_effect_capture.h index 7f50fc4965..bb1d03be8c 100644 --- a/servers/audio/effects/audio_effect_capture.h +++ b/servers/audio/effects/audio_effect_capture.h @@ -34,6 +34,7 @@ #include "core/config/engine.h" #include "core/math/audio_frame.h" #include "core/object/ref_counted.h" +#include "core/templates/ring_buffer.h" #include "core/templates/vector.h" #include "servers/audio/audio_effect.h" #include "servers/audio_server.h" diff --git a/servers/audio/effects/audio_effect_pitch_shift.cpp b/servers/audio/effects/audio_effect_pitch_shift.cpp index bfbaeee3f3..d6c396e0a5 100644 --- a/servers/audio/effects/audio_effect_pitch_shift.cpp +++ b/servers/audio/effects/audio_effect_pitch_shift.cpp @@ -40,7 +40,7 @@ * * NAME: smbPitchShift.cpp * VERSION: 1.2 -* HOME URL: http://blogs.zynaptiq.com/bernsee +* HOME URL: https://blogs.zynaptiq.com/bernsee * KNOWN BUGS: none * * SYNOPSIS: Routine for doing pitch shifting while maintaining diff --git a/servers/audio/effects/audio_stream_generator.cpp b/servers/audio/effects/audio_stream_generator.cpp index bced2997ce..447acf53a4 100644 --- a/servers/audio/effects/audio_stream_generator.cpp +++ b/servers/audio/effects/audio_stream_generator.cpp @@ -64,6 +64,10 @@ float AudioStreamGenerator::get_length() const { return 0; } +bool AudioStreamGenerator::is_monophonic() const { + return true; +} + void AudioStreamGenerator::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mix_rate", "hz"), &AudioStreamGenerator::set_mix_rate); ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioStreamGenerator::get_mix_rate); @@ -138,7 +142,7 @@ void AudioStreamGeneratorPlayback::clear_buffer() { mixed = 0; } -void AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_frames) { +int AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_frames) { int read_amount = buffer.data_left(); if (p_frames < read_amount) { read_amount = p_frames; @@ -156,6 +160,7 @@ void AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_fra } mixed += p_frames / generator->get_mix_rate(); + return read_amount < p_frames ? read_amount : p_frames; } float AudioStreamGeneratorPlayback::get_stream_sampling_rate() { diff --git a/servers/audio/effects/audio_stream_generator.h b/servers/audio/effects/audio_stream_generator.h index 5d46771f4d..918589f6d0 100644 --- a/servers/audio/effects/audio_stream_generator.h +++ b/servers/audio/effects/audio_stream_generator.h @@ -54,6 +54,7 @@ public: virtual String get_stream_name() const override; virtual float get_length() const override; + virtual bool is_monophonic() const override; AudioStreamGenerator(); }; @@ -67,7 +68,7 @@ class AudioStreamGeneratorPlayback : public AudioStreamPlaybackResampled { AudioStreamGenerator *generator; protected: - virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) override; + virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override; virtual float get_stream_sampling_rate() override; static void _bind_methods(); diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index 4c54188cb2..758ce766c3 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -32,13 +32,19 @@ #include "core/config/project_settings.h" #include "core/debugger/engine_debugger.h" +#include "core/error/error_macros.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" +#include "core/math/audio_frame.h" #include "core/os/os.h" +#include "core/string/string_name.h" +#include "core/templates/pair.h" #include "scene/resources/audio_stream_sample.h" #include "servers/audio/audio_driver_dummy.h" #include "servers/audio/effects/audio_effect_compressor.h" +#include <cstring> + #ifdef TOOLS_ENABLED #define MARK_EDITED set_edited(true); #else @@ -234,6 +240,7 @@ AudioDriver *AudioDriverManager::get_driver(int p_driver) { ////////////////////////////////////////////// void AudioServer::_driver_process(int p_frames, int32_t *p_buffer) { + mix_count++; int todo = p_frames; #ifdef DEBUG_ENABLED @@ -331,10 +338,144 @@ void AudioServer::_mix_step() { bus->soloed = false; } } + for (CallbackItem *ci : mix_callback_list) { + ci->callback(ci->userdata); + } + + for (AudioStreamPlaybackListNode *playback : playback_list) { + // Paused streams are no-ops. Don't even mix audio from the stream playback. + if (playback->state.load() == AudioStreamPlaybackListNode::PAUSED) { + continue; + } + + bool fading_out = playback->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION || playback->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE; - //make callbacks for mixing the audio - for (Set<CallbackItem>::Element *E = callbacks.front(); E; E = E->next()) { - E->get().callback(E->get().userdata); + AudioFrame *buf = mix_buffer.ptrw(); + + // Copy the lookeahead buffer into the mix buffer. + for (int i = 0; i < LOOKAHEAD_BUFFER_SIZE; i++) { + buf[i] = playback->lookahead[i]; + } + + // Mix the audio stream + unsigned int mixed_frames = playback->stream_playback->mix(&buf[LOOKAHEAD_BUFFER_SIZE], playback->pitch_scale.get(), buffer_size); + + if (mixed_frames != buffer_size) { + // We know we have at least the size of our lookahead buffer for fade-out purposes. + + float fadeout_base = 0.94; + float fadeout_coefficient = 1; + static_assert(LOOKAHEAD_BUFFER_SIZE == 64, "Update fadeout_base and comment here if you change LOOKAHEAD_BUFFER_SIZE."); + // 0.94 ^ 64 = 0.01906. There might still be a pop but it'll be way better than if we didn't do this. + for (unsigned int idx = mixed_frames; idx < buffer_size; idx++) { + fadeout_coefficient *= fadeout_base; + buf[idx] *= fadeout_coefficient; + } + AudioStreamPlaybackListNode::PlaybackState new_state; + new_state = AudioStreamPlaybackListNode::AWAITING_DELETION; + playback->state.store(new_state); + } else { + // Move the last little bit of what we just mixed into our lookahead buffer. + for (int i = 0; i < LOOKAHEAD_BUFFER_SIZE; i++) { + playback->lookahead[i] = buf[buffer_size + i]; + } + } + + AudioStreamPlaybackBusDetails *ptr = playback->bus_details.load(); + ERR_FAIL_COND(ptr == nullptr); + // By putting null into the bus details pointers, we're taking ownership of their memory for the duration of this mix. + AudioStreamPlaybackBusDetails bus_details = *ptr; + + // Mix to any active buses. + for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) { + if (!bus_details.bus_active[idx]) { + continue; + } + int bus_idx = thread_find_bus_index(bus_details.bus[idx]); + + int prev_bus_idx = -1; + for (int search_idx = 0; search_idx < MAX_BUSES_PER_PLAYBACK; search_idx++) { + if (!playback->prev_bus_details->bus_active[search_idx]) { + continue; + } + if (playback->prev_bus_details->bus[search_idx].hash() == bus_details.bus[idx].hash()) { + prev_bus_idx = search_idx; + } + } + + for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) { + AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx); + if (fading_out) { + bus_details.volume[idx][channel_idx] = AudioFrame(0, 0); + } + AudioFrame channel_vol = bus_details.volume[idx][channel_idx]; + + AudioFrame prev_channel_vol = AudioFrame(0, 0); + if (prev_bus_idx != -1) { + prev_channel_vol = playback->prev_bus_details->volume[prev_bus_idx][channel_idx]; + } + _mix_step_for_channel(channel_buf, buf, prev_channel_vol, channel_vol, playback->attenuation_filter_cutoff_hz.get(), playback->highshelf_gain.get(), &playback->filter_process[channel_idx * 2], &playback->filter_process[channel_idx * 2 + 1]); + } + } + + // Now go through and fade-out any buses that were being played to previously that we missed by going through current data. + for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) { + if (!playback->prev_bus_details->bus_active[idx]) { + continue; + } + int bus_idx = thread_find_bus_index(playback->prev_bus_details->bus[idx]); + + int current_bus_idx = -1; + for (int search_idx = 0; search_idx < MAX_BUSES_PER_PLAYBACK; search_idx++) { + if (bus_details.bus[search_idx] == playback->prev_bus_details->bus[idx]) { + current_bus_idx = search_idx; + } + } + if (current_bus_idx != -1) { + // If we found a corresponding bus in the current bus assignments, we've already mixed to this bus. + continue; + } + + for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) { + AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx); + AudioFrame prev_channel_vol = playback->prev_bus_details->volume[idx][channel_idx]; + // Fade out to silence + _mix_step_for_channel(channel_buf, buf, prev_channel_vol, AudioFrame(0, 0), playback->attenuation_filter_cutoff_hz.get(), playback->highshelf_gain.get(), &playback->filter_process[channel_idx * 2], &playback->filter_process[channel_idx * 2 + 1]); + } + } + + // Copy the bus details we mixed with to the previous bus details to maintain volume ramps. + std::copy(std::begin(bus_details.bus_active), std::end(bus_details.bus_active), std::begin(playback->prev_bus_details->bus_active)); + std::copy(std::begin(bus_details.bus), std::end(bus_details.bus), std::begin(playback->prev_bus_details->bus)); + for (int bus_idx = 0; bus_idx < MAX_BUSES_PER_PLAYBACK; bus_idx++) { + std::copy(std::begin(bus_details.volume[bus_idx]), std::end(bus_details.volume[bus_idx]), std::begin(playback->prev_bus_details->volume[bus_idx])); + } + + switch (playback->state.load()) { + case AudioStreamPlaybackListNode::AWAITING_DELETION: + case AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION: + playback_list.erase(playback, [](AudioStreamPlaybackListNode *p) { + if (p->prev_bus_details) + delete p->prev_bus_details; + if (p->bus_details) + delete p->bus_details; + p->stream_playback.unref(); + delete p; + }); + break; + case AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE: { + // Pause the stream. + AudioStreamPlaybackListNode::PlaybackState old_state, new_state; + do { + old_state = playback->state.load(); + new_state = AudioStreamPlaybackListNode::PAUSED; + } while (!playback->state.compare_exchange_strong(/* expected= */ old_state, new_state)); + } break; + case AudioStreamPlaybackListNode::PLAYING: + case AudioStreamPlaybackListNode::PAUSED: + // No-op! + break; + } } for (int i = buses.size() - 1; i >= 0; i--) { @@ -464,6 +605,53 @@ void AudioServer::_mix_step() { to_mix = buffer_size; } +void AudioServer::_mix_step_for_channel(AudioFrame *p_out_buf, AudioFrame *p_source_buf, AudioFrame p_vol_start, AudioFrame p_vol_final, float p_attenuation_filter_cutoff_hz, float p_highshelf_gain, AudioFilterSW::Processor *p_processor_l, AudioFilterSW::Processor *p_processor_r) { + if (p_highshelf_gain != 0) { + AudioFilterSW filter; + filter.set_mode(AudioFilterSW::HIGHSHELF); + filter.set_sampling_rate(AudioServer::get_singleton()->get_mix_rate()); + filter.set_cutoff(p_attenuation_filter_cutoff_hz); + filter.set_resonance(1); + filter.set_stages(1); + filter.set_gain(p_highshelf_gain); + + ERR_FAIL_COND(p_processor_l == nullptr); + ERR_FAIL_COND(p_processor_r == nullptr); + + bool is_just_started = p_vol_start.l == 0 && p_vol_start.r == 0; + p_processor_l->set_filter(&filter, /* clear_history= */ is_just_started); + p_processor_l->update_coeffs(buffer_size); + p_processor_r->set_filter(&filter, /* clear_history= */ is_just_started); + p_processor_r->update_coeffs(buffer_size); + + for (unsigned int frame_idx = 0; frame_idx < buffer_size; frame_idx++) { + // Make this buffer size invariant if buffer_size ever becomes a project setting. + float lerp_param = (float)frame_idx / buffer_size; + AudioFrame vol = p_vol_final * lerp_param + (1 - lerp_param) * p_vol_start; + AudioFrame mixed = vol * p_source_buf[frame_idx]; + p_processor_l->process_one_interp(mixed.l); + p_processor_r->process_one_interp(mixed.r); + p_out_buf[frame_idx] += mixed; + } + + } else { + for (unsigned int frame_idx = 0; frame_idx < buffer_size; frame_idx++) { + // Make this buffer size invariant if buffer_size ever becomes a project setting. + float lerp_param = (float)frame_idx / buffer_size; + p_out_buf[frame_idx] += (p_vol_final * lerp_param + (1 - lerp_param) * p_vol_start) * p_source_buf[frame_idx]; + } + } +} + +AudioServer::AudioStreamPlaybackListNode *AudioServer::_find_playback_list_node(Ref<AudioStreamPlayback> p_playback) { + for (AudioStreamPlaybackListNode *playback_list_node : playback_list) { + if (playback_list_node->stream_playback == p_playback) { + return playback_list_node; + } + } + return nullptr; +} + bool AudioServer::thread_has_channel_mix_buffer(int p_bus, int p_buffer) const { if (p_bus < 0 || p_bus >= buses.size()) { return false; @@ -923,9 +1111,223 @@ float AudioServer::get_playback_speed_scale() const { return playback_speed_scale; } +void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time) { + ERR_FAIL_COND(p_playback.is_null()); + + Map<StringName, Vector<AudioFrame>> map; + map[p_bus] = p_volume_db_vector; + + start_playback_stream(p_playback, map, p_start_time); +} + +void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time, float p_highshelf_gain, float p_attenuation_cutoff_hz, float p_pitch_scale) { + ERR_FAIL_COND(p_playback.is_null()); + + AudioStreamPlaybackListNode *playback_node = new AudioStreamPlaybackListNode(); + playback_node->stream_playback = p_playback; + playback_node->stream_playback->start(p_start_time); + + AudioStreamPlaybackBusDetails *new_bus_details = new AudioStreamPlaybackBusDetails(); + int idx = 0; + for (KeyValue<StringName, Vector<AudioFrame>> pair : p_bus_volumes) { + ERR_FAIL_COND(pair.value.size() < channel_count); + ERR_FAIL_COND(pair.value.size() != MAX_CHANNELS_PER_BUS); + + new_bus_details->bus_active[idx] = true; + new_bus_details->bus[idx] = pair.key; + for (int channel_idx = 0; channel_idx < MAX_CHANNELS_PER_BUS; channel_idx++) { + new_bus_details->volume[idx][channel_idx] = pair.value[channel_idx]; + } + } + playback_node->bus_details = new_bus_details; + playback_node->prev_bus_details = new AudioStreamPlaybackBusDetails(); + + playback_node->pitch_scale.set(p_pitch_scale); + playback_node->highshelf_gain.set(p_highshelf_gain); + playback_node->attenuation_filter_cutoff_hz.set(p_attenuation_cutoff_hz); + + memset(playback_node->prev_bus_details->volume, 0, sizeof(playback_node->prev_bus_details->volume)); + + for (AudioFrame &frame : playback_node->lookahead) { + frame = AudioFrame(0, 0); + } + + playback_node->state.store(AudioStreamPlaybackListNode::PLAYING); + + playback_list.insert(playback_node); +} + +void AudioServer::stop_playback_stream(Ref<AudioStreamPlayback> p_playback) { + ERR_FAIL_COND(p_playback.is_null()); + + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); + if (!playback_node) { + return; + } + + AudioStreamPlaybackListNode::PlaybackState new_state, old_state; + do { + old_state = playback_node->state.load(); + if (old_state == AudioStreamPlaybackListNode::AWAITING_DELETION) { + break; // Don't fade out again. + } + new_state = AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION; + + } while (!playback_node->state.compare_exchange_strong(old_state, new_state)); +} + +void AudioServer::set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volumes) { + ERR_FAIL_COND(p_volumes.size() != MAX_CHANNELS_PER_BUS); + + Map<StringName, Vector<AudioFrame>> map; + map[p_bus] = p_volumes; + + set_playback_bus_volumes_linear(p_playback, map); +} + +void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes) { + ERR_FAIL_COND(p_bus_volumes.size() > MAX_BUSES_PER_PLAYBACK); + + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); + if (!playback_node) { + return; + } + AudioStreamPlaybackBusDetails *old_bus_details, *new_bus_details = new AudioStreamPlaybackBusDetails(); + + int idx = 0; + for (KeyValue<StringName, Vector<AudioFrame>> pair : p_bus_volumes) { + if (idx >= MAX_BUSES_PER_PLAYBACK) { + break; + } + ERR_FAIL_COND(pair.value.size() < channel_count); + ERR_FAIL_COND(pair.value.size() != MAX_CHANNELS_PER_BUS); + + new_bus_details->bus_active[idx] = true; + new_bus_details->bus[idx] = pair.key; + for (int channel_idx = 0; channel_idx < MAX_CHANNELS_PER_BUS; channel_idx++) { + new_bus_details->volume[idx][channel_idx] = pair.value[channel_idx]; + } + idx++; + } + + do { + old_bus_details = playback_node->bus_details.load(); + } while (!playback_node->bus_details.compare_exchange_strong(old_bus_details, new_bus_details)); + + bus_details_graveyard.insert(old_bus_details); +} + +void AudioServer::set_playback_all_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Vector<AudioFrame> p_volumes) { + ERR_FAIL_COND(p_playback.is_null()); + ERR_FAIL_COND(p_volumes.size() != MAX_CHANNELS_PER_BUS); + + Map<StringName, Vector<AudioFrame>> map; + + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); + if (!playback_node) { + return; + } + for (int bus_idx = 0; bus_idx < MAX_BUSES_PER_PLAYBACK; bus_idx++) { + if (playback_node->bus_details.load()->bus_active[bus_idx]) { + map[playback_node->bus_details.load()->bus[bus_idx]] = p_volumes; + } + } + + set_playback_bus_volumes_linear(p_playback, map); +} + +void AudioServer::set_playback_pitch_scale(Ref<AudioStreamPlayback> p_playback, float p_pitch_scale) { + ERR_FAIL_COND(p_playback.is_null()); + + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); + if (!playback_node) { + return; + } + + playback_node->pitch_scale.set(p_pitch_scale); +} + +void AudioServer::set_playback_paused(Ref<AudioStreamPlayback> p_playback, bool p_paused) { + ERR_FAIL_COND(p_playback.is_null()); + + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); + if (!playback_node) { + return; + } + + AudioStreamPlaybackListNode::PlaybackState new_state, old_state; + do { + old_state = playback_node->state.load(); + new_state = p_paused ? AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE : AudioStreamPlaybackListNode::PLAYING; + if (!p_paused && old_state == AudioStreamPlaybackListNode::PLAYING) { + return; // No-op. + } + if (p_paused && (old_state == AudioStreamPlaybackListNode::PAUSED || old_state == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE)) { + return; // No-op. + } + + } while (!playback_node->state.compare_exchange_strong(old_state, new_state)); +} + +void AudioServer::set_playback_highshelf_params(Ref<AudioStreamPlayback> p_playback, float p_gain, float p_attenuation_cutoff_hz) { + ERR_FAIL_COND(p_playback.is_null()); + + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); + if (!playback_node) { + return; + } + + playback_node->attenuation_filter_cutoff_hz.set(p_attenuation_cutoff_hz); + playback_node->highshelf_gain.set(p_gain); +} + +bool AudioServer::is_playback_active(Ref<AudioStreamPlayback> p_playback) { + ERR_FAIL_COND_V(p_playback.is_null(), false); + + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); + if (!playback_node) { + return false; + } + + return playback_node->state.load() == AudioStreamPlaybackListNode::PLAYING; +} + +float AudioServer::get_playback_position(Ref<AudioStreamPlayback> p_playback) { + ERR_FAIL_COND_V(p_playback.is_null(), 0); + + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); + if (!playback_node) { + return 0; + } + + return playback_node->stream_playback->get_playback_position(); +} + +bool AudioServer::is_playback_paused(Ref<AudioStreamPlayback> p_playback) { + ERR_FAIL_COND_V(p_playback.is_null(), false); + + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); + if (!playback_node) { + return false; + } + + return playback_node->state.load() == AudioStreamPlaybackListNode::PAUSED || playback_node->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE; +} + +uint64_t AudioServer::get_mix_count() const { + return mix_count; +} + +void AudioServer::notify_listener_changed() { + for (CallbackItem *ci : listener_changed_callback_list) { + ci->callback(ci->userdata); + } +} + void AudioServer::init_channels_and_buffers() { channel_count = get_channel_count(); temp_buffer.resize(channel_count); + mix_buffer.resize(buffer_size + LOOKAHEAD_BUFFER_SIZE); for (int i = 0; i < temp_buffer.size(); i++) { temp_buffer.write[i].resize(buffer_size); @@ -943,7 +1345,7 @@ void AudioServer::init() { channel_disable_threshold_db = GLOBAL_DEF_RST("audio/buses/channel_disable_threshold_db", -60.0); channel_disable_frames = float(GLOBAL_DEF_RST("audio/buses/channel_disable_time", 2.0)) * get_mix_rate(); ProjectSettings::get_singleton()->set_custom_property_info("audio/buses/channel_disable_time", PropertyInfo(Variant::FLOAT, "audio/buses/channel_disable_time", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater")); - buffer_size = 1024; //hardcoded for now + buffer_size = 512; //hardcoded for now init_channels_and_buffers(); @@ -1030,9 +1432,22 @@ void AudioServer::update() { prof_time = 0; #endif - for (Set<CallbackItem>::Element *E = update_callbacks.front(); E; E = E->next()) { - E->get().callback(E->get().userdata); + for (CallbackItem *ci : update_callback_list) { + ci->callback(ci->userdata); + } + mix_callback_list.maybe_cleanup(); + update_callback_list.maybe_cleanup(); + listener_changed_callback_list.maybe_cleanup(); + playback_list.maybe_cleanup(); + for (AudioStreamPlaybackBusDetails *bus_details : bus_details_graveyard_frame_old) { + bus_details_graveyard_frame_old.erase(bus_details, [](AudioStreamPlaybackBusDetails *d) { delete d; }); + } + for (AudioStreamPlaybackBusDetails *bus_details : bus_details_graveyard) { + bus_details_graveyard_frame_old.insert(bus_details); + bus_details_graveyard.erase(bus_details); } + bus_details_graveyard.maybe_cleanup(); + bus_details_graveyard_frame_old.maybe_cleanup(); } void AudioServer::load_default_bus_layout() { @@ -1098,40 +1513,49 @@ double AudioServer::get_time_since_last_mix() const { AudioServer *AudioServer::singleton = nullptr; -void AudioServer::add_callback(AudioCallback p_callback, void *p_userdata) { - lock(); - CallbackItem ci; - ci.callback = p_callback; - ci.userdata = p_userdata; - callbacks.insert(ci); - unlock(); +void AudioServer::add_update_callback(AudioCallback p_callback, void *p_userdata) { + CallbackItem *ci = new CallbackItem(); + ci->callback = p_callback; + ci->userdata = p_userdata; + update_callback_list.insert(ci); } -void AudioServer::remove_callback(AudioCallback p_callback, void *p_userdata) { - lock(); - CallbackItem ci; - ci.callback = p_callback; - ci.userdata = p_userdata; - callbacks.erase(ci); - unlock(); +void AudioServer::remove_update_callback(AudioCallback p_callback, void *p_userdata) { + for (CallbackItem *ci : update_callback_list) { + if (ci->callback == p_callback && ci->userdata == p_userdata) { + update_callback_list.erase(ci, [](CallbackItem *c) { delete c; }); + } + } } -void AudioServer::add_update_callback(AudioCallback p_callback, void *p_userdata) { - lock(); - CallbackItem ci; - ci.callback = p_callback; - ci.userdata = p_userdata; - update_callbacks.insert(ci); - unlock(); +void AudioServer::add_mix_callback(AudioCallback p_callback, void *p_userdata) { + CallbackItem *ci = new CallbackItem(); + ci->callback = p_callback; + ci->userdata = p_userdata; + mix_callback_list.insert(ci); } -void AudioServer::remove_update_callback(AudioCallback p_callback, void *p_userdata) { - lock(); - CallbackItem ci; - ci.callback = p_callback; - ci.userdata = p_userdata; - update_callbacks.erase(ci); - unlock(); +void AudioServer::remove_mix_callback(AudioCallback p_callback, void *p_userdata) { + for (CallbackItem *ci : mix_callback_list) { + if (ci->callback == p_callback && ci->userdata == p_userdata) { + mix_callback_list.erase(ci, [](CallbackItem *c) { delete c; }); + } + } +} + +void AudioServer::add_listener_changed_callback(AudioCallback p_callback, void *p_userdata) { + CallbackItem *ci = new CallbackItem(); + ci->callback = p_callback; + ci->userdata = p_userdata; + listener_changed_callback_list.insert(ci); +} + +void AudioServer::remove_listener_changed_callback(AudioCallback p_callback, void *p_userdata) { + for (CallbackItem *ci : listener_changed_callback_list) { + if (ci->callback == p_callback && ci->userdata == p_userdata) { + listener_changed_callback_list.erase(ci, [](CallbackItem *c) { delete c; }); + } + } } void AudioServer::set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout) { diff --git a/servers/audio_server.h b/servers/audio_server.h index 7974c4a2ad..a60d4ae4c4 100644 --- a/servers/audio_server.h +++ b/servers/audio_server.h @@ -34,12 +34,17 @@ #include "core/math/audio_frame.h" #include "core/object/class_db.h" #include "core/os/os.h" +#include "core/templates/safe_list.h" #include "core/variant/variant.h" #include "servers/audio/audio_effect.h" +#include "servers/audio/audio_filter_sw.h" + +#include <atomic> class AudioDriverDummy; class AudioStream; class AudioStreamSample; +class AudioStreamPlayback; class AudioDriver { static AudioDriver *singleton; @@ -155,7 +160,10 @@ public: }; enum { - AUDIO_DATA_INVALID_ID = -1 + AUDIO_DATA_INVALID_ID = -1, + MAX_CHANNELS_PER_BUS = 4, + MAX_BUSES_PER_PLAYBACK = 6, + LOOKAHEAD_BUFFER_SIZE = 64, }; typedef void (*AudioCallback)(void *p_userdata); @@ -219,7 +227,46 @@ private: int index_cache; }; + struct AudioStreamPlaybackBusDetails { + bool bus_active[MAX_BUSES_PER_PLAYBACK] = { false, false, false, false, false, false }; + StringName bus[MAX_BUSES_PER_PLAYBACK]; + AudioFrame volume[MAX_BUSES_PER_PLAYBACK][MAX_CHANNELS_PER_BUS]; + }; + + struct AudioStreamPlaybackListNode { + enum PlaybackState { + PAUSED = 0, // Paused. Keep this stream playback around though so it can be restarted. + PLAYING = 1, // Playing. Fading may still be necessary if volume changes! + FADE_OUT_TO_PAUSE = 2, // About to pause. + FADE_OUT_TO_DELETION = 3, // About to stop. + AWAITING_DELETION = 4, + }; + // If zero or positive, a place in the stream to seek to during the next mix. + SafeNumeric<float> setseek; + SafeNumeric<float> pitch_scale; + SafeNumeric<float> highshelf_gain; + SafeNumeric<float> attenuation_filter_cutoff_hz; // This isn't used unless highshelf_gain is nonzero. + AudioFilterSW::Processor filter_process[8]; + // Updating this ref after the list node is created breaks consistency guarantees, don't do it! + Ref<AudioStreamPlayback> stream_playback; + // Playback state determines the fate of a particular AudioStreamListNode during the mix step. Must be atomically replaced. + std::atomic<PlaybackState> state = AWAITING_DELETION; + // This data should only ever be modified by an atomic replacement of the pointer. + std::atomic<AudioStreamPlaybackBusDetails *> bus_details = nullptr; + // Previous bus details should only be accessed on the audio thread. + AudioStreamPlaybackBusDetails *prev_bus_details = nullptr; + // The next few samples are stored here so we have some time to fade audio out if it ends abruptly at the beginning of the next mix. + AudioFrame lookahead[LOOKAHEAD_BUFFER_SIZE]; + }; + + SafeList<AudioStreamPlaybackListNode *> playback_list; + SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard; + + // TODO document if this is necessary. + SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard_frame_old; + Vector<Vector<AudioFrame>> temp_buffer; //temp_buffer for each level + Vector<AudioFrame> mix_buffer; Vector<Bus *> buses; Map<StringName, Bus *> bus_map; @@ -230,18 +277,19 @@ private: void init_channels_and_buffers(); void _mix_step(); + void _mix_step_for_channel(AudioFrame *p_out_buf, AudioFrame *p_source_buf, AudioFrame p_vol_start, AudioFrame p_vol_final, float p_attenuation_filter_cutoff_hz, float p_highshelf_gain, AudioFilterSW::Processor *p_processor_l, AudioFilterSW::Processor *p_processor_r); + + // Should only be called on the main thread. + AudioStreamPlaybackListNode *_find_playback_list_node(Ref<AudioStreamPlayback> p_playback); struct CallbackItem { AudioCallback callback; void *userdata; - - bool operator<(const CallbackItem &p_item) const { - return (callback == p_item.callback ? userdata < p_item.userdata : callback < p_item.callback); - } }; - Set<CallbackItem> callbacks; - Set<CallbackItem> update_callbacks; + SafeList<CallbackItem *> update_callback_list; + SafeList<CallbackItem *> mix_callback_list; + SafeList<CallbackItem *> listener_changed_callback_list; friend class AudioDriver; void _driver_process(int p_frames, int32_t *p_buffer); @@ -319,6 +367,27 @@ public: void set_playback_speed_scale(float p_scale); float get_playback_speed_scale() const; + // Convenience method. + void start_playback_stream(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time = 0); + // Expose all parameters. + void start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time = 0, float p_highshelf_gain = 0, float p_attenuation_cutoff_hz = 0, float p_pitch_scale = 1); + void stop_playback_stream(Ref<AudioStreamPlayback> p_playback); + + void set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volumes); + void set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes); + void set_playback_all_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Vector<AudioFrame> p_volumes); + void set_playback_pitch_scale(Ref<AudioStreamPlayback> p_playback, float p_pitch_scale); + void set_playback_paused(Ref<AudioStreamPlayback> p_playback, bool p_paused); + void set_playback_highshelf_params(Ref<AudioStreamPlayback> p_playback, float p_gain, float p_attenuation_cutoff_hz); + + bool is_playback_active(Ref<AudioStreamPlayback> p_playback); + float get_playback_position(Ref<AudioStreamPlayback> p_playback); + bool is_playback_paused(Ref<AudioStreamPlayback> p_playback); + + uint64_t get_mix_count() const; + + void notify_listener_changed(); + virtual void init(); virtual void finish(); virtual void update(); @@ -340,12 +409,15 @@ public: virtual double get_time_to_next_mix() const; virtual double get_time_since_last_mix() const; - void add_callback(AudioCallback p_callback, void *p_userdata); - void remove_callback(AudioCallback p_callback, void *p_userdata); + void add_listener_changed_callback(AudioCallback p_callback, void *p_userdata); + void remove_listener_changed_callback(AudioCallback p_callback, void *p_userdata); void add_update_callback(AudioCallback p_callback, void *p_userdata); void remove_update_callback(AudioCallback p_callback, void *p_userdata); + void add_mix_callback(AudioCallback p_callback, void *p_userdata); + void remove_mix_callback(AudioCallback p_callback, void *p_userdata); + void set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout); Ref<AudioBusLayout> generate_bus_layout() const; diff --git a/servers/physics_2d/area_2d_sw.cpp b/servers/physics_2d/area_2d_sw.cpp index 532cb259b3..663a47f273 100644 --- a/servers/physics_2d/area_2d_sw.cpp +++ b/servers/physics_2d/area_2d_sw.cpp @@ -274,6 +274,26 @@ void Area2DSW::call_queries() { } } +void Area2DSW::compute_gravity(const Vector2 &p_position, Vector2 &r_gravity) const { + if (is_gravity_point()) { + const real_t gravity_distance_scale = get_gravity_distance_scale(); + Vector2 v = get_transform().xform(get_gravity_vector()) - p_position; + if (gravity_distance_scale > 0) { + const real_t v_length = v.length(); + if (v_length > 0) { + const real_t v_scaled = v_length * gravity_distance_scale; + r_gravity = (v.normalized() * (get_gravity() / (v_scaled * v_scaled))); + } else { + r_gravity = Vector2(); + } + } else { + r_gravity = v.normalized() * get_gravity(); + } + } else { + r_gravity = get_gravity_vector() * get_gravity(); + } +} + Area2DSW::Area2DSW() : CollisionObject2DSW(TYPE_AREA), monitor_query_list(this), diff --git a/servers/physics_2d/area_2d_sw.h b/servers/physics_2d/area_2d_sw.h index 3bf603b30d..d9147d6f1d 100644 --- a/servers/physics_2d/area_2d_sw.h +++ b/servers/physics_2d/area_2d_sw.h @@ -34,7 +34,6 @@ #include "collision_object_2d_sw.h" #include "core/templates/self_list.h" #include "servers/physics_server_2d.h" -//#include "servers/physics_3d/query_sw.h" class Space2DSW; class Body2DSW; @@ -94,17 +93,12 @@ class Area2DSW : public CollisionObject2DSW { Map<BodyKey, BodyState> monitored_bodies; Map<BodyKey, BodyState> monitored_areas; - //virtual void shape_changed_notify(Shape2DSW *p_shape); - //virtual void shape_deleted_notify(Shape2DSW *p_shape); Set<Constraint2DSW *> constraints; virtual void _shapes_changed(); void _queue_monitor_update(); public: - //_FORCE_INLINE_ const Matrix32& get_inverse_transform() const { return inverse_transform; } - //_FORCE_INLINE_ SpaceSW* get_owner() { return owner; } - void set_monitor_callback(ObjectID p_id, const StringName &p_method); _FORCE_INLINE_ bool has_monitor_callback() const { return monitor_callback_id.is_valid(); } @@ -161,6 +155,8 @@ public: void call_queries(); + void compute_gravity(const Vector2 &p_position, Vector2 &r_gravity) const; + Area2DSW(); ~Area2DSW(); }; diff --git a/servers/physics_2d/area_pair_2d_sw.cpp b/servers/physics_2d/area_pair_2d_sw.cpp index 5ca16cb6fc..4f1148c26f 100644 --- a/servers/physics_2d/area_pair_2d_sw.cpp +++ b/servers/physics_2d/area_pair_2d_sw.cpp @@ -33,7 +33,7 @@ bool AreaPair2DSW::setup(real_t p_step) { bool result = false; - if (area->interacts_with(body) && CollisionSolver2DSW::solve(body->get_shape(body_shape), body->get_transform() * body->get_shape_transform(body_shape), Vector2(), area->get_shape(area_shape), area->get_transform() * area->get_shape_transform(area_shape), Vector2(), nullptr, this)) { + if (area->collides_with(body) && CollisionSolver2DSW::solve(body->get_shape(body_shape), body->get_transform() * body->get_shape_transform(body_shape), Vector2(), area->get_shape(area_shape), area->get_transform() * area->get_shape_transform(area_shape), Vector2(), nullptr, this)) { result = true; } @@ -109,46 +109,51 @@ AreaPair2DSW::~AreaPair2DSW() { ////////////////////////////////// bool Area2Pair2DSW::setup(real_t p_step) { - bool result = false; - if (area_a->interacts_with(area_b) && CollisionSolver2DSW::solve(area_a->get_shape(shape_a), area_a->get_transform() * area_a->get_shape_transform(shape_a), Vector2(), area_b->get_shape(shape_b), area_b->get_transform() * area_b->get_shape_transform(shape_b), Vector2(), nullptr, this)) { - result = true; + bool result_a = area_a->collides_with(area_b); + bool result_b = area_b->collides_with(area_a); + if ((result_a || result_b) && !CollisionSolver2DSW::solve(area_a->get_shape(shape_a), area_a->get_transform() * area_a->get_shape_transform(shape_a), Vector2(), area_b->get_shape(shape_b), area_b->get_transform() * area_b->get_shape_transform(shape_b), Vector2(), nullptr, this)) { + result_a = false; + result_b = false; } - process_collision = false; - if (result != colliding) { - if (area_b->has_area_monitor_callback() && area_a->is_monitorable()) { - process_collision = true; - } else if (area_a->has_area_monitor_callback() && area_b->is_monitorable()) { + bool process_collision = false; + + process_collision_a = false; + if (result_a != colliding_a) { + if (area_a->has_area_monitor_callback() && area_b->is_monitorable()) { + process_collision_a = true; process_collision = true; } + colliding_a = result_a; + } - colliding = result; + process_collision_b = false; + if (result_b != colliding_b) { + if (area_b->has_area_monitor_callback() && area_a->is_monitorable()) { + process_collision_b = true; + process_collision = true; + } + colliding_b = result_b; } return process_collision; } bool Area2Pair2DSW::pre_solve(real_t p_step) { - if (!process_collision) { - return false; + if (process_collision_a) { + if (colliding_a) { + area_a->add_area_to_query(area_b, shape_b, shape_a); + } else { + area_a->remove_area_from_query(area_b, shape_b, shape_a); + } } - if (colliding) { - if (area_b->has_area_monitor_callback() && area_a->is_monitorable()) { + if (process_collision_b) { + if (colliding_b) { area_b->add_area_to_query(area_a, shape_a, shape_b); - } - - if (area_a->has_area_monitor_callback() && area_b->is_monitorable()) { - area_a->add_area_to_query(area_b, shape_b, shape_a); - } - } else { - if (area_b->has_area_monitor_callback() && area_a->is_monitorable()) { + } else { area_b->remove_area_from_query(area_a, shape_a, shape_b); } - - if (area_a->has_area_monitor_callback() && area_b->is_monitorable()) { - area_a->remove_area_from_query(area_b, shape_b, shape_a); - } } return false; // Never do any post solving. @@ -168,16 +173,18 @@ Area2Pair2DSW::Area2Pair2DSW(Area2DSW *p_area_a, int p_shape_a, Area2DSW *p_area } Area2Pair2DSW::~Area2Pair2DSW() { - if (colliding) { - if (area_b->has_area_monitor_callback()) { - area_b->remove_area_from_query(area_a, shape_a, shape_b); - } - + if (colliding_a) { if (area_a->has_area_monitor_callback()) { area_a->remove_area_from_query(area_b, shape_b, shape_a); } } + if (colliding_b) { + if (area_b->has_area_monitor_callback()) { + area_b->remove_area_from_query(area_a, shape_a, shape_b); + } + } + area_a->remove_constraint(this); area_b->remove_constraint(this); } diff --git a/servers/physics_2d/area_pair_2d_sw.h b/servers/physics_2d/area_pair_2d_sw.h index 4632a307d9..66e9f1afee 100644 --- a/servers/physics_2d/area_pair_2d_sw.h +++ b/servers/physics_2d/area_pair_2d_sw.h @@ -57,8 +57,10 @@ class Area2Pair2DSW : public Constraint2DSW { Area2DSW *area_b = nullptr; int shape_a = 0; int shape_b = 0; - bool colliding = false; - bool process_collision = false; + bool colliding_a = false; + bool colliding_b = false; + bool process_collision_a = false; + bool process_collision_b = false; public: virtual bool setup(real_t p_step) override; diff --git a/servers/physics_2d/body_2d_sw.cpp b/servers/physics_2d/body_2d_sw.cpp index 7aa2f9b7de..edd769aa9a 100644 --- a/servers/physics_2d/body_2d_sw.cpp +++ b/servers/physics_2d/body_2d_sw.cpp @@ -29,51 +29,77 @@ /*************************************************************************/ #include "body_2d_sw.h" + #include "area_2d_sw.h" -#include "physics_server_2d_sw.h" +#include "body_direct_state_2d_sw.h" #include "space_2d_sw.h" -void Body2DSW::_update_inertia() { - if (!user_inertia && get_space() && !inertia_update_list.in_list()) { - get_space()->body_add_to_inertia_update_list(&inertia_update_list); +void Body2DSW::_mass_properties_changed() { + if (get_space() && !mass_properties_update_list.in_list() && (calculate_inertia || calculate_center_of_mass)) { + get_space()->body_add_to_mass_properties_update_list(&mass_properties_update_list); } } -void Body2DSW::update_inertias() { +void Body2DSW::update_mass_properties() { //update shapes and motions switch (mode) { case PhysicsServer2D::BODY_MODE_DYNAMIC: { - if (user_inertia) { - _inv_inertia = inertia > 0 ? (1.0 / inertia) : 0; - break; - } - //update tensor for allshapes, not the best way but should be somehow OK. (inspired from bullet) real_t total_area = 0; - for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } total_area += get_shape_aabb(i).get_area(); } - inertia = 0; + if (calculate_center_of_mass) { + // We have to recompute the center of mass. + center_of_mass = Vector2(); - for (int i = 0; i < get_shape_count(); i++) { - if (is_shape_disabled(i)) { - continue; + if (total_area != 0.0) { + for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } + + real_t area = get_shape_aabb(i).get_area(); + + real_t mass = area * this->mass / total_area; + + // NOTE: we assume that the shape origin is also its center of mass. + center_of_mass += mass * get_shape_transform(i).get_origin(); + } + + center_of_mass /= mass; } + } + + if (calculate_inertia) { + inertia = 0; + + for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } - const Shape2DSW *shape = get_shape(i); + const Shape2DSW *shape = get_shape(i); - real_t area = get_shape_aabb(i).get_area(); + real_t area = get_shape_aabb(i).get_area(); + if (area == 0.0) { + continue; + } - real_t mass = area * this->mass / total_area; + real_t mass = area * this->mass / total_area; - Transform2D mtx = get_shape_transform(i); - Vector2 scale = mtx.get_scale(); - inertia += shape->get_moment_of_inertia(mass, scale) + mass * mtx.get_origin().length_squared(); + Transform2D mtx = get_shape_transform(i); + Vector2 scale = mtx.get_scale(); + Vector2 shape_origin = mtx.get_origin() - center_of_mass; + inertia += shape->get_moment_of_inertia(mass, scale) + mass * shape_origin.length_squared(); + } } - _inv_inertia = inertia > 0 ? (1.0 / inertia) : 0; + _inv_inertia = inertia > 0.0 ? (1.0 / inertia) : 0.0; if (mass) { _inv_mass = 1.0 / mass; @@ -93,9 +119,12 @@ void Body2DSW::update_inertias() { } break; } - //_update_inertia_tensor(); +} - //_update_shapes(); +void Body2DSW::reset_mass_properties() { + calculate_inertia = true; + calculate_center_of_mass = true; + _mass_properties_changed(); } void Body2DSW::set_active(bool p_active) { @@ -117,7 +146,7 @@ void Body2DSW::set_active(bool p_active) { } } -void Body2DSW::set_param(PhysicsServer2D::BodyParameter p_param, real_t p_value) { +void Body2DSW::set_param(PhysicsServer2D::BodyParameter p_param, const Variant &p_value) { switch (p_param) { case PhysicsServer2D::BODY_PARAM_BOUNCE: { bounce = p_value; @@ -126,21 +155,32 @@ void Body2DSW::set_param(PhysicsServer2D::BodyParameter p_param, real_t p_value) friction = p_value; } break; case PhysicsServer2D::BODY_PARAM_MASS: { - ERR_FAIL_COND(p_value <= 0); - mass = p_value; - _update_inertia(); - + real_t mass_value = p_value; + ERR_FAIL_COND(mass_value <= 0); + mass = mass_value; + if (mode >= PhysicsServer2D::BODY_MODE_DYNAMIC) { + _mass_properties_changed(); + } } break; case PhysicsServer2D::BODY_PARAM_INERTIA: { - if (p_value <= 0) { - user_inertia = false; - _update_inertia(); + real_t inertia_value = p_value; + if (inertia_value <= 0.0) { + calculate_inertia = true; + if (mode == PhysicsServer2D::BODY_MODE_DYNAMIC) { + _mass_properties_changed(); + } } else { - user_inertia = true; - inertia = p_value; - _inv_inertia = 1.0 / p_value; + calculate_inertia = false; + inertia = inertia_value; + if (mode == PhysicsServer2D::BODY_MODE_DYNAMIC) { + _inv_inertia = 1.0 / inertia; + } } } break; + case PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS: { + calculate_center_of_mass = false; + center_of_mass = p_value; + } break; case PhysicsServer2D::BODY_PARAM_GRAVITY_SCALE: { gravity_scale = p_value; } break; @@ -155,7 +195,7 @@ void Body2DSW::set_param(PhysicsServer2D::BodyParameter p_param, real_t p_value) } } -real_t Body2DSW::get_param(PhysicsServer2D::BodyParameter p_param) const { +Variant Body2DSW::get_param(PhysicsServer2D::BodyParameter p_param) const { switch (p_param) { case PhysicsServer2D::BODY_PARAM_BOUNCE: { return bounce; @@ -169,6 +209,9 @@ real_t Body2DSW::get_param(PhysicsServer2D::BodyParameter p_param) const { case PhysicsServer2D::BODY_PARAM_INERTIA: { return inertia; } + case PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS: { + return center_of_mass; + } case PhysicsServer2D::BODY_PARAM_GRAVITY_SCALE: { return gravity_scale; } @@ -206,7 +249,10 @@ void Body2DSW::set_mode(PhysicsServer2D::BodyMode p_mode) { } break; case PhysicsServer2D::BODY_MODE_DYNAMIC: { _inv_mass = mass > 0 ? (1.0 / mass) : 0; - _inv_inertia = inertia > 0 ? (1.0 / inertia) : 0; + if (!calculate_inertia) { + _inv_inertia = 1.0 / inertia; + } + _mass_properties_changed(); _set_static(false); set_active(true); @@ -214,18 +260,11 @@ void Body2DSW::set_mode(PhysicsServer2D::BodyMode p_mode) { case PhysicsServer2D::BODY_MODE_DYNAMIC_LOCKED: { _inv_mass = mass > 0 ? (1.0 / mass) : 0; _inv_inertia = 0; + angular_velocity = 0; _set_static(false); set_active(true); - angular_velocity = 0; - } break; - } - if (p_mode == PhysicsServer2D::BODY_MODE_DYNAMIC && _inv_inertia == 0) { - _update_inertia(); + } } - /* - if (get_space()) - _update_queries(); - */ } PhysicsServer2D::BodyMode Body2DSW::get_mode() const { @@ -233,7 +272,7 @@ PhysicsServer2D::BodyMode Body2DSW::get_mode() const { } void Body2DSW::_shapes_changed() { - _update_inertia(); + _mass_properties_changed(); wakeup_neighbours(); } @@ -268,11 +307,13 @@ void Body2DSW::set_state(PhysicsServer2D::BodyState p_state, const Variant &p_va } break; case PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY: { linear_velocity = p_variant; + constant_linear_velocity = linear_velocity; wakeup(); } break; case PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY: { angular_velocity = p_variant; + constant_angular_velocity = angular_velocity; wakeup(); } break; @@ -295,7 +336,7 @@ void Body2DSW::set_state(PhysicsServer2D::BodyState p_state, const Variant &p_va } break; case PhysicsServer2D::BODY_STATE_CAN_SLEEP: { can_sleep = p_variant; - if (mode == PhysicsServer2D::BODY_MODE_DYNAMIC && !active && !can_sleep) { + if (mode >= PhysicsServer2D::BODY_MODE_DYNAMIC && !active && !can_sleep) { set_active(true); } @@ -329,8 +370,8 @@ void Body2DSW::set_space(Space2DSW *p_space) { if (get_space()) { wakeup_neighbours(); - if (inertia_update_list.in_list()) { - get_space()->body_remove_from_inertia_update_list(&inertia_update_list); + if (mass_properties_update_list.in_list()) { + get_space()->body_remove_from_mass_properties_update_list(&mass_properties_update_list); } if (active_list.in_list()) { get_space()->body_remove_from_active_list(&active_list); @@ -343,26 +384,17 @@ void Body2DSW::set_space(Space2DSW *p_space) { _set_space(p_space); if (get_space()) { - _update_inertia(); + _mass_properties_changed(); if (active) { get_space()->body_add_to_active_list(&active_list); } } - - first_integration = false; } -void Body2DSW::_compute_area_gravity_and_dampenings(const Area2DSW *p_area) { - if (p_area->is_gravity_point()) { - if (p_area->get_gravity_distance_scale() > 0) { - Vector2 v = p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin(); - gravity += v.normalized() * (p_area->get_gravity() / Math::pow(v.length() * p_area->get_gravity_distance_scale() + 1, 2)); - } else { - gravity += (p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin()).normalized() * p_area->get_gravity(); - } - } else { - gravity += p_area->get_gravity_vector() * p_area->get_gravity(); - } +void Body2DSW::_compute_area_gravity_and_damping(const Area2DSW *p_area) { + Vector2 area_gravity; + p_area->compute_gravity(get_transform().get_origin(), area_gravity); + gravity += area_gravity; area_linear_damp += p_area->get_linear_damp(); area_angular_damp += p_area->get_angular_damp(); @@ -391,7 +423,7 @@ void Body2DSW::integrate_forces(real_t p_step) { switch (mode) { case PhysicsServer2D::AREA_SPACE_OVERRIDE_COMBINE: case PhysicsServer2D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: { - _compute_area_gravity_and_dampenings(aa[i].area); + _compute_area_gravity_and_damping(aa[i].area); stopped = mode == PhysicsServer2D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE; } break; case PhysicsServer2D::AREA_SPACE_OVERRIDE_REPLACE: @@ -399,7 +431,7 @@ void Body2DSW::integrate_forces(real_t p_step) { gravity = Vector2(0, 0); area_angular_damp = 0; area_linear_damp = 0; - _compute_area_gravity_and_dampenings(aa[i].area); + _compute_area_gravity_and_damping(aa[i].area); stopped = mode == PhysicsServer2D::AREA_SPACE_OVERRIDE_REPLACE; } break; default: { @@ -408,7 +440,7 @@ void Body2DSW::integrate_forces(real_t p_step) { } } if (!stopped) { - _compute_area_gravity_and_dampenings(def_area); + _compute_area_gravity_and_damping(def_area); } gravity *= gravity_scale; @@ -435,10 +467,10 @@ void Body2DSW::integrate_forces(real_t p_step) { if (mode == PhysicsServer2D::BODY_MODE_KINEMATIC) { //compute motion, angular and etc. velocities from prev transform motion = new_transform.get_origin() - get_transform().get_origin(); - linear_velocity = motion / p_step; + linear_velocity = constant_linear_velocity + motion / p_step; real_t rot = new_transform.get_rotation() - get_transform().get_rotation(); - angular_velocity = remainder(rot, 2.0 * Math_PI) / p_step; + angular_velocity = constant_angular_velocity + remainder(rot, 2.0 * Math_PI) / p_step; do_motion = true; @@ -450,7 +482,7 @@ void Body2DSW::integrate_forces(real_t p_step) { */ } else { - if (!omit_force_integration && !first_integration) { + if (!omit_force_integration) { //overridden by direct state query Vector2 force = gravity * mass; @@ -484,7 +516,6 @@ void Body2DSW::integrate_forces(real_t p_step) { //motion=linear_velocity*p_step; - first_integration = false; biased_angular_velocity = 0; biased_linear_velocity = Vector2(); @@ -502,7 +533,7 @@ void Body2DSW::integrate_velocities(real_t p_step) { return; } - if (fi_callback) { + if (fi_callback_data || body_state_callback) { get_space()->body_add_to_state_query_list(&direct_state_query_list); } @@ -521,14 +552,22 @@ void Body2DSW::integrate_velocities(real_t p_step) { real_t angle = get_transform().get_rotation() + total_angular_velocity * p_step; Vector2 pos = get_transform().get_origin() + total_linear_velocity * p_step; + real_t center_of_mass_distance = center_of_mass.length(); + if (center_of_mass_distance > CMP_EPSILON) { + // Calculate displacement due to center of mass offset. + real_t prev_angle = get_transform().get_rotation(); + real_t angle_base = Math::atan2(center_of_mass.y, center_of_mass.x); + Vector2 point1(Math::cos(angle_base + prev_angle), Math::sin(angle_base + prev_angle)); + Vector2 point2(Math::cos(angle_base + angle), Math::sin(angle_base + angle)); + pos += center_of_mass_distance * (point1 - point2); + } + _set_transform(Transform2D(angle, pos), continuous_cd_mode == PhysicsServer2D::CCD_MODE_DISABLED); _set_inv_transform(get_transform().inverse()); if (continuous_cd_mode != PhysicsServer2D::CCD_MODE_DISABLED) { new_transform = get_transform(); } - - //_update_inertia_tensor(); } void Body2DSW::wakeup_neighbours() { @@ -542,7 +581,7 @@ void Body2DSW::wakeup_neighbours() { continue; } Body2DSW *b = n[i]; - if (b->mode != PhysicsServer2D::BODY_MODE_DYNAMIC) { + if (b->mode < PhysicsServer2D::BODY_MODE_DYNAMIC) { continue; } @@ -554,27 +593,27 @@ void Body2DSW::wakeup_neighbours() { } void Body2DSW::call_queries() { - if (fi_callback) { - PhysicsDirectBodyState2DSW *dbs = PhysicsDirectBodyState2DSW::singleton; - dbs->body = this; - - Variant v = dbs; - const Variant *vp[2] = { &v, &fi_callback->callback_udata }; - - Object *obj = fi_callback->callable.get_object(); - if (!obj) { + if (fi_callback_data) { + if (!fi_callback_data->callable.get_object()) { set_force_integration_callback(Callable()); } else { + Variant direct_state_variant = get_direct_state(); + const Variant *vp[2] = { &direct_state_variant, &fi_callback_data->udata }; + Callable::CallError ce; Variant rv; - if (fi_callback->callback_udata.get_type() != Variant::NIL) { - fi_callback->callable.call(vp, 2, rv, ce); + if (fi_callback_data->udata.get_type() != Variant::NIL) { + fi_callback_data->callable.call(vp, 2, rv, ce); } else { - fi_callback->callable.call(vp, 1, rv, ce); + fi_callback_data->callable.call(vp, 1, rv, ce); } } } + + if (body_state_callback) { + (body_state_callback)(body_state_callback_instance, get_direct_state()); + } } bool Body2DSW::sleep_test(real_t p_step) { @@ -594,78 +633,45 @@ bool Body2DSW::sleep_test(real_t p_step) { } } +void Body2DSW::set_state_sync_callback(void *p_instance, PhysicsServer2D::BodyStateCallback p_callback) { + body_state_callback_instance = p_instance; + body_state_callback = p_callback; +} + void Body2DSW::set_force_integration_callback(const Callable &p_callable, const Variant &p_udata) { - if (fi_callback) { - memdelete(fi_callback); - fi_callback = nullptr; + if (p_callable.get_object()) { + if (!fi_callback_data) { + fi_callback_data = memnew(ForceIntegrationCallbackData); + } + fi_callback_data->callable = p_callable; + fi_callback_data->udata = p_udata; + } else if (fi_callback_data) { + memdelete(fi_callback_data); + fi_callback_data = nullptr; } +} - if (p_callable.get_object()) { - fi_callback = memnew(ForceIntegrationCallback); - fi_callback->callable = p_callable; - fi_callback->callback_udata = p_udata; +PhysicsDirectBodyState2DSW *Body2DSW::get_direct_state() { + if (!direct_state) { + direct_state = memnew(PhysicsDirectBodyState2DSW); + direct_state->body = this; } + return direct_state; } Body2DSW::Body2DSW() : CollisionObject2DSW(TYPE_BODY), active_list(this), - inertia_update_list(this), + mass_properties_update_list(this), direct_state_query_list(this) { - mode = PhysicsServer2D::BODY_MODE_DYNAMIC; - active = true; - angular_velocity = 0; - biased_angular_velocity = 0; - mass = 1; - inertia = 0; - user_inertia = false; - _inv_inertia = 0; - _inv_mass = 1; - bounce = 0; - friction = 1; - omit_force_integration = false; - applied_torque = 0; - island_step = 0; _set_static(false); - first_time_kinematic = false; - linear_damp = -1; - angular_damp = -1; - area_angular_damp = 0; - area_linear_damp = 0; - contact_count = 0; - gravity_scale = 1.0; - first_integration = false; - - still_time = 0; - continuous_cd_mode = PhysicsServer2D::CCD_MODE_DISABLED; - can_sleep = true; - fi_callback = nullptr; } Body2DSW::~Body2DSW() { - if (fi_callback) { - memdelete(fi_callback); - } -} - -PhysicsDirectBodyState2DSW *PhysicsDirectBodyState2DSW::singleton = nullptr; - -PhysicsDirectSpaceState2D *PhysicsDirectBodyState2DSW::get_space_state() { - return body->get_space()->get_direct_state(); -} - -Variant PhysicsDirectBodyState2DSW::get_contact_collider_shape_metadata(int p_contact_idx) const { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Variant()); - - if (!PhysicsServer2DSW::singletonsw->body_owner.owns(body->contacts[p_contact_idx].collider)) { - return Variant(); + if (fi_callback_data) { + memdelete(fi_callback_data); } - Body2DSW *other = PhysicsServer2DSW::singletonsw->body_owner.getornull(body->contacts[p_contact_idx].collider); - - int sidx = body->contacts[p_contact_idx].collider_shape; - if (sidx < 0 || sidx >= other->get_shape_count()) { - return Variant(); + if (direct_state) { + memdelete(direct_state); } - - return other->get_shape_metadata(sidx); } diff --git a/servers/physics_2d/body_2d_sw.h b/servers/physics_2d/body_2d_sw.h index 74bef433dc..95e89786cd 100644 --- a/servers/physics_2d/body_2d_sw.h +++ b/servers/physics_2d/body_2d_sw.h @@ -38,50 +38,58 @@ #include "core/templates/vset.h" class Constraint2DSW; +class PhysicsDirectBodyState2DSW; class Body2DSW : public CollisionObject2DSW { - PhysicsServer2D::BodyMode mode; + PhysicsServer2D::BodyMode mode = PhysicsServer2D::BODY_MODE_DYNAMIC; Vector2 biased_linear_velocity; - real_t biased_angular_velocity; + real_t biased_angular_velocity = 0.0; Vector2 linear_velocity; - real_t angular_velocity; + real_t angular_velocity = 0.0; - real_t linear_damp; - real_t angular_damp; - real_t gravity_scale; + Vector2 constant_linear_velocity; + real_t constant_angular_velocity = 0.0; - real_t mass; - real_t inertia; - real_t bounce; - real_t friction; + real_t linear_damp = -1.0; + real_t angular_damp = -1.0; + real_t gravity_scale = 1.0; - real_t _inv_mass; - real_t _inv_inertia; - bool user_inertia; + real_t bounce = 0.0; + real_t friction = 1.0; + + real_t mass = 1.0; + real_t _inv_mass = 1.0; + + real_t inertia = 0.0; + real_t _inv_inertia = 0.0; + + Vector2 center_of_mass; + + bool calculate_inertia = true; + bool calculate_center_of_mass = true; Vector2 gravity; - real_t area_linear_damp; - real_t area_angular_damp; + real_t area_linear_damp = 0.0; + real_t area_angular_damp = 0.0; - real_t still_time; + real_t still_time = 0.0; Vector2 applied_force; - real_t applied_torque; + real_t applied_torque = 0.0; SelfList<Body2DSW> active_list; - SelfList<Body2DSW> inertia_update_list; + SelfList<Body2DSW> mass_properties_update_list; SelfList<Body2DSW> direct_state_query_list; VSet<RID> exceptions; - PhysicsServer2D::CCDMode continuous_cd_mode; - bool omit_force_integration; - bool active; - bool can_sleep; - bool first_time_kinematic; - bool first_integration; - void _update_inertia(); + PhysicsServer2D::CCDMode continuous_cd_mode = PhysicsServer2D::CCD_MODE_DISABLED; + bool omit_force_integration = false; + bool active = true; + bool can_sleep = true; + bool first_time_kinematic = false; + void _mass_properties_changed(); virtual void _shapes_changed(); Transform2D new_transform; @@ -114,24 +122,32 @@ class Body2DSW : public CollisionObject2DSW { }; Vector<Contact> contacts; //no contacts by default - int contact_count; + int contact_count = 0; + + void *body_state_callback_instance = nullptr; + PhysicsServer2D::BodyStateCallback body_state_callback = nullptr; - struct ForceIntegrationCallback { + struct ForceIntegrationCallbackData { Callable callable; - Variant callback_udata; + Variant udata; }; - ForceIntegrationCallback *fi_callback; + ForceIntegrationCallbackData *fi_callback_data = nullptr; - uint64_t island_step; + PhysicsDirectBodyState2DSW *direct_state = nullptr; - _FORCE_INLINE_ void _compute_area_gravity_and_dampenings(const Area2DSW *p_area); + uint64_t island_step = 0; + + _FORCE_INLINE_ void _compute_area_gravity_and_damping(const Area2DSW *p_area); friend class PhysicsDirectBodyState2DSW; // i give up, too many functions to expose public: + void set_state_sync_callback(void *p_instance, PhysicsServer2D::BodyStateCallback p_callback); void set_force_integration_callback(const Callable &p_callable, const Variant &p_udata = Variant()); + PhysicsDirectBodyState2DSW *get_direct_state(); + _FORCE_INLINE_ void add_area(Area2DSW *p_area) { int index = areas.find(AreaCMP(p_area)); if (index > -1) { @@ -198,7 +214,7 @@ public: _FORCE_INLINE_ void apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position = Vector2()) { linear_velocity += p_impulse * _inv_mass; - angular_velocity += _inv_inertia * p_position.cross(p_impulse); + angular_velocity += _inv_inertia * (p_position - center_of_mass).cross(p_impulse); } _FORCE_INLINE_ void apply_torque_impulse(real_t p_torque) { @@ -207,7 +223,7 @@ public: _FORCE_INLINE_ void apply_bias_impulse(const Vector2 &p_impulse, const Vector2 &p_position = Vector2()) { biased_linear_velocity += p_impulse * _inv_mass; - biased_angular_velocity += _inv_inertia * p_position.cross(p_impulse); + biased_angular_velocity += _inv_inertia * (p_position - center_of_mass).cross(p_impulse); } void set_active(bool p_active); @@ -220,8 +236,8 @@ public: set_active(true); } - void set_param(PhysicsServer2D::BodyParameter p_param, real_t); - real_t get_param(PhysicsServer2D::BodyParameter p_param) const; + void set_param(PhysicsServer2D::BodyParameter p_param, const Variant &p_value); + Variant get_param(PhysicsServer2D::BodyParameter p_param) const; void set_mode(PhysicsServer2D::BodyMode p_mode); PhysicsServer2D::BodyMode get_mode() const; @@ -241,7 +257,7 @@ public: _FORCE_INLINE_ void add_force(const Vector2 &p_force, const Vector2 &p_position = Vector2()) { applied_force += p_force; - applied_torque += p_position.cross(p_force); + applied_torque += (p_position - center_of_mass).cross(p_force); } _FORCE_INLINE_ void add_torque(real_t p_torque) { @@ -253,8 +269,10 @@ public: void set_space(Space2DSW *p_space); - void update_inertias(); + void update_mass_properties(); + void reset_mass_properties(); + _FORCE_INLINE_ Vector2 get_center_of_mass() const { return center_of_mass; } _FORCE_INLINE_ real_t get_inv_mass() const { return _inv_mass; } _FORCE_INLINE_ real_t get_inv_inertia() const { return _inv_inertia; } _FORCE_INLINE_ real_t get_friction() const { return friction; } @@ -332,87 +350,4 @@ void Body2DSW::add_contact(const Vector2 &p_local_pos, const Vector2 &p_local_no c[idx].collider_velocity_at_pos = p_collider_velocity_at_pos; } -class PhysicsDirectBodyState2DSW : public PhysicsDirectBodyState2D { - GDCLASS(PhysicsDirectBodyState2DSW, PhysicsDirectBodyState2D); - -public: - static PhysicsDirectBodyState2DSW *singleton; - Body2DSW *body; - real_t step; - - virtual Vector2 get_total_gravity() const override { return body->gravity; } // get gravity vector working on this body space/area - virtual real_t get_total_angular_damp() const override { return body->area_angular_damp; } // get density of this body space/area - virtual real_t get_total_linear_damp() const override { return body->area_linear_damp; } // get density of this body space/area - - virtual real_t get_inverse_mass() const override { return body->get_inv_mass(); } // get the mass - virtual real_t get_inverse_inertia() const override { return body->get_inv_inertia(); } // get density of this body space - - virtual void set_linear_velocity(const Vector2 &p_velocity) override { body->set_linear_velocity(p_velocity); } - virtual Vector2 get_linear_velocity() const override { return body->get_linear_velocity(); } - - virtual void set_angular_velocity(real_t p_velocity) override { body->set_angular_velocity(p_velocity); } - virtual real_t get_angular_velocity() const override { return body->get_angular_velocity(); } - - virtual void set_transform(const Transform2D &p_transform) override { body->set_state(PhysicsServer2D::BODY_STATE_TRANSFORM, p_transform); } - virtual Transform2D get_transform() const override { return body->get_transform(); } - - virtual Vector2 get_velocity_at_local_position(const Vector2 &p_position) const override { return body->get_velocity_in_local_point(p_position); } - - virtual void add_central_force(const Vector2 &p_force) override { body->add_central_force(p_force); } - virtual void add_force(const Vector2 &p_force, const Vector2 &p_position = Vector2()) override { body->add_force(p_force, p_position); } - virtual void add_torque(real_t p_torque) override { body->add_torque(p_torque); } - virtual void apply_central_impulse(const Vector2 &p_impulse) override { body->apply_central_impulse(p_impulse); } - virtual void apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position = Vector2()) override { body->apply_impulse(p_impulse, p_position); } - virtual void apply_torque_impulse(real_t p_torque) override { body->apply_torque_impulse(p_torque); } - - virtual void set_sleep_state(bool p_enable) override { body->set_active(!p_enable); } - virtual bool is_sleeping() const override { return !body->is_active(); } - - virtual int get_contact_count() const override { return body->contact_count; } - - virtual Vector2 get_contact_local_position(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2()); - return body->contacts[p_contact_idx].local_pos; - } - virtual Vector2 get_contact_local_normal(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2()); - return body->contacts[p_contact_idx].local_normal; - } - virtual int get_contact_local_shape(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, -1); - return body->contacts[p_contact_idx].local_shape; - } - - virtual RID get_contact_collider(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, RID()); - return body->contacts[p_contact_idx].collider; - } - virtual Vector2 get_contact_collider_position(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2()); - return body->contacts[p_contact_idx].collider_pos; - } - virtual ObjectID get_contact_collider_id(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, ObjectID()); - return body->contacts[p_contact_idx].collider_instance_id; - } - virtual int get_contact_collider_shape(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, 0); - return body->contacts[p_contact_idx].collider_shape; - } - virtual Variant get_contact_collider_shape_metadata(int p_contact_idx) const override; - - virtual Vector2 get_contact_collider_velocity_at_position(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2()); - return body->contacts[p_contact_idx].collider_velocity_at_pos; - } - - virtual PhysicsDirectSpaceState2D *get_space_state() override; - - virtual real_t get_step() const override { return step; } - PhysicsDirectBodyState2DSW() { - singleton = this; - body = nullptr; - } -}; - #endif // BODY_2D_SW_H diff --git a/servers/physics_2d/body_direct_state_2d_sw.cpp b/servers/physics_2d/body_direct_state_2d_sw.cpp new file mode 100644 index 0000000000..58250c3077 --- /dev/null +++ b/servers/physics_2d/body_direct_state_2d_sw.cpp @@ -0,0 +1,186 @@ +/*************************************************************************/ +/* body_direct_state_2d_sw.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 "body_direct_state_2d_sw.h" + +#include "body_2d_sw.h" +#include "physics_server_2d_sw.h" +#include "space_2d_sw.h" + +Vector2 PhysicsDirectBodyState2DSW::get_total_gravity() const { + return body->gravity; +} + +real_t PhysicsDirectBodyState2DSW::get_total_angular_damp() const { + return body->area_angular_damp; +} + +real_t PhysicsDirectBodyState2DSW::get_total_linear_damp() const { + return body->area_linear_damp; +} + +Vector2 PhysicsDirectBodyState2DSW::get_center_of_mass() const { + return body->get_center_of_mass(); +} + +real_t PhysicsDirectBodyState2DSW::get_inverse_mass() const { + return body->get_inv_mass(); +} + +real_t PhysicsDirectBodyState2DSW::get_inverse_inertia() const { + return body->get_inv_inertia(); +} + +void PhysicsDirectBodyState2DSW::set_linear_velocity(const Vector2 &p_velocity) { + body->set_linear_velocity(p_velocity); +} + +Vector2 PhysicsDirectBodyState2DSW::get_linear_velocity() const { + return body->get_linear_velocity(); +} + +void PhysicsDirectBodyState2DSW::set_angular_velocity(real_t p_velocity) { + body->set_angular_velocity(p_velocity); +} + +real_t PhysicsDirectBodyState2DSW::get_angular_velocity() const { + return body->get_angular_velocity(); +} + +void PhysicsDirectBodyState2DSW::set_transform(const Transform2D &p_transform) { + body->set_state(PhysicsServer2D::BODY_STATE_TRANSFORM, p_transform); +} + +Transform2D PhysicsDirectBodyState2DSW::get_transform() const { + return body->get_transform(); +} + +Vector2 PhysicsDirectBodyState2DSW::get_velocity_at_local_position(const Vector2 &p_position) const { + return body->get_velocity_in_local_point(p_position); +} + +void PhysicsDirectBodyState2DSW::add_central_force(const Vector2 &p_force) { + body->add_central_force(p_force); +} + +void PhysicsDirectBodyState2DSW::add_force(const Vector2 &p_force, const Vector2 &p_position) { + body->add_force(p_force, p_position); +} + +void PhysicsDirectBodyState2DSW::add_torque(real_t p_torque) { + body->add_torque(p_torque); +} + +void PhysicsDirectBodyState2DSW::apply_central_impulse(const Vector2 &p_impulse) { + body->apply_central_impulse(p_impulse); +} + +void PhysicsDirectBodyState2DSW::apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position) { + body->apply_impulse(p_impulse, p_position); +} + +void PhysicsDirectBodyState2DSW::apply_torque_impulse(real_t p_torque) { + body->apply_torque_impulse(p_torque); +} + +void PhysicsDirectBodyState2DSW::set_sleep_state(bool p_enable) { + body->set_active(!p_enable); +} + +bool PhysicsDirectBodyState2DSW::is_sleeping() const { + return !body->is_active(); +} + +int PhysicsDirectBodyState2DSW::get_contact_count() const { + return body->contact_count; +} + +Vector2 PhysicsDirectBodyState2DSW::get_contact_local_position(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2()); + return body->contacts[p_contact_idx].local_pos; +} + +Vector2 PhysicsDirectBodyState2DSW::get_contact_local_normal(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2()); + return body->contacts[p_contact_idx].local_normal; +} + +int PhysicsDirectBodyState2DSW::get_contact_local_shape(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, -1); + return body->contacts[p_contact_idx].local_shape; +} + +RID PhysicsDirectBodyState2DSW::get_contact_collider(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, RID()); + return body->contacts[p_contact_idx].collider; +} +Vector2 PhysicsDirectBodyState2DSW::get_contact_collider_position(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2()); + return body->contacts[p_contact_idx].collider_pos; +} + +ObjectID PhysicsDirectBodyState2DSW::get_contact_collider_id(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, ObjectID()); + return body->contacts[p_contact_idx].collider_instance_id; +} + +int PhysicsDirectBodyState2DSW::get_contact_collider_shape(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, 0); + return body->contacts[p_contact_idx].collider_shape; +} + +Vector2 PhysicsDirectBodyState2DSW::get_contact_collider_velocity_at_position(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2()); + return body->contacts[p_contact_idx].collider_velocity_at_pos; +} + +Variant PhysicsDirectBodyState2DSW::get_contact_collider_shape_metadata(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Variant()); + + if (!PhysicsServer2DSW::singletonsw->body_owner.owns(body->contacts[p_contact_idx].collider)) { + return Variant(); + } + Body2DSW *other = PhysicsServer2DSW::singletonsw->body_owner.getornull(body->contacts[p_contact_idx].collider); + + int sidx = body->contacts[p_contact_idx].collider_shape; + if (sidx < 0 || sidx >= other->get_shape_count()) { + return Variant(); + } + + return other->get_shape_metadata(sidx); +} + +PhysicsDirectSpaceState2D *PhysicsDirectBodyState2DSW::get_space_state() { + return body->get_space()->get_direct_state(); +} + +real_t PhysicsDirectBodyState2DSW::get_step() const { + return body->get_space()->get_last_step(); +} diff --git a/servers/physics_2d/body_direct_state_2d_sw.h b/servers/physics_2d/body_direct_state_2d_sw.h new file mode 100644 index 0000000000..34faa174d8 --- /dev/null +++ b/servers/physics_2d/body_direct_state_2d_sw.h @@ -0,0 +1,92 @@ +/*************************************************************************/ +/* body_direct_state_2d_sw.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 BODY_DIRECT_STATE_2D_SW_H +#define BODY_DIRECT_STATE_2D_SW_H + +#include "servers/physics_server_2d.h" + +class Body2DSW; + +class PhysicsDirectBodyState2DSW : public PhysicsDirectBodyState2D { + GDCLASS(PhysicsDirectBodyState2DSW, PhysicsDirectBodyState2D); + +public: + Body2DSW *body = nullptr; + + virtual Vector2 get_total_gravity() const override; + virtual real_t get_total_angular_damp() const override; + virtual real_t get_total_linear_damp() const override; + + virtual Vector2 get_center_of_mass() const override; + virtual real_t get_inverse_mass() const override; + virtual real_t get_inverse_inertia() const override; + + virtual void set_linear_velocity(const Vector2 &p_velocity) override; + virtual Vector2 get_linear_velocity() const override; + + virtual void set_angular_velocity(real_t p_velocity) override; + virtual real_t get_angular_velocity() const override; + + virtual void set_transform(const Transform2D &p_transform) override; + virtual Transform2D get_transform() const override; + + virtual Vector2 get_velocity_at_local_position(const Vector2 &p_position) const override; + + virtual void add_central_force(const Vector2 &p_force) override; + virtual void add_force(const Vector2 &p_force, const Vector2 &p_position = Vector2()) override; + virtual void add_torque(real_t p_torque) override; + virtual void apply_central_impulse(const Vector2 &p_impulse) override; + virtual void apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position = Vector2()) override; + virtual void apply_torque_impulse(real_t p_torque) override; + + virtual void set_sleep_state(bool p_enable) override; + virtual bool is_sleeping() const override; + + virtual int get_contact_count() const override; + + virtual Vector2 get_contact_local_position(int p_contact_idx) const override; + virtual Vector2 get_contact_local_normal(int p_contact_idx) const override; + virtual int get_contact_local_shape(int p_contact_idx) const override; + + virtual RID get_contact_collider(int p_contact_idx) const override; + virtual Vector2 get_contact_collider_position(int p_contact_idx) const override; + virtual ObjectID get_contact_collider_id(int p_contact_idx) const override; + virtual int get_contact_collider_shape(int p_contact_idx) const override; + virtual Variant get_contact_collider_shape_metadata(int p_contact_idx) const override; + + virtual Vector2 get_contact_collider_velocity_at_position(int p_contact_idx) const override; + + virtual PhysicsDirectSpaceState2D *get_space_state() override; + + virtual real_t get_step() const override; +}; + +#endif // BODY_2D_SW_H diff --git a/servers/physics_2d/body_pair_2d_sw.cpp b/servers/physics_2d/body_pair_2d_sw.cpp index 91d747b492..8bcc4609f4 100644 --- a/servers/physics_2d/body_pair_2d_sw.cpp +++ b/servers/physics_2d/body_pair_2d_sw.cpp @@ -298,7 +298,7 @@ bool BodyPair2DSW::setup(real_t p_step) { } if (!prev_collided) { - if (A->is_shape_set_as_one_way_collision(shape_A)) { + if (shape_B_ptr->allows_one_way_collision() && A->is_shape_set_as_one_way_collision(shape_A)) { Vector2 direction = xform_A.get_axis(1).normalized(); bool valid = false; for (int i = 0; i < contact_count; i++) { @@ -319,7 +319,7 @@ bool BodyPair2DSW::setup(real_t p_step) { } } - if (B->is_shape_set_as_one_way_collision(shape_B)) { + if (shape_A_ptr->allows_one_way_collision() && B->is_shape_set_as_one_way_collision(shape_B)) { Vector2 direction = xform_B.get_axis(1).normalized(); bool valid = false; for (int i = 0; i < contact_count; i++) { diff --git a/servers/physics_2d/collision_solver_2d_sat.cpp b/servers/physics_2d/collision_solver_2d_sat.cpp index 30a99d3d74..b1aee01bde 100644 --- a/servers/physics_2d/collision_solver_2d_sat.cpp +++ b/servers/physics_2d/collision_solver_2d_sat.cpp @@ -1115,11 +1115,13 @@ bool sat_2d_calculate_penetration(const Shape2DSW *p_shape_A, const Transform2D PhysicsServer2D::ShapeType type_A = p_shape_A->get_type(); ERR_FAIL_COND_V(type_A == PhysicsServer2D::SHAPE_WORLD_MARGIN, false); + ERR_FAIL_COND_V(type_A == PhysicsServer2D::SHAPE_SEPARATION_RAY, false); ERR_FAIL_COND_V(p_shape_A->is_concave(), false); PhysicsServer2D::ShapeType type_B = p_shape_B->get_type(); ERR_FAIL_COND_V(type_B == PhysicsServer2D::SHAPE_WORLD_MARGIN, false); + ERR_FAIL_COND_V(type_B == PhysicsServer2D::SHAPE_SEPARATION_RAY, false); ERR_FAIL_COND_V(p_shape_B->is_concave(), false); static const CollisionFunc collision_table[5][5] = { @@ -1382,23 +1384,23 @@ bool sat_2d_calculate_penetration(const Shape2DSW *p_shape_A, const Transform2D if (p_margin_A || p_margin_B) { if (*motion_A == Vector2() && *motion_B == Vector2()) { - collision_func = collision_table_margin[type_A - 1][type_B - 1]; + collision_func = collision_table_margin[type_A - 2][type_B - 2]; } else if (*motion_A != Vector2() && *motion_B == Vector2()) { - collision_func = collision_table_castA_margin[type_A - 1][type_B - 1]; + collision_func = collision_table_castA_margin[type_A - 2][type_B - 2]; } else if (*motion_A == Vector2() && *motion_B != Vector2()) { - collision_func = collision_table_castB_margin[type_A - 1][type_B - 1]; + collision_func = collision_table_castB_margin[type_A - 2][type_B - 2]; } else { - collision_func = collision_table_castA_castB_margin[type_A - 1][type_B - 1]; + collision_func = collision_table_castA_castB_margin[type_A - 2][type_B - 2]; } } else { if (*motion_A == Vector2() && *motion_B == Vector2()) { - collision_func = collision_table[type_A - 1][type_B - 1]; + collision_func = collision_table[type_A - 2][type_B - 2]; } else if (*motion_A != Vector2() && *motion_B == Vector2()) { - collision_func = collision_table_castA[type_A - 1][type_B - 1]; + collision_func = collision_table_castA[type_A - 2][type_B - 2]; } else if (*motion_A == Vector2() && *motion_B != Vector2()) { - collision_func = collision_table_castB[type_A - 1][type_B - 1]; + collision_func = collision_table_castB[type_A - 2][type_B - 2]; } else { - collision_func = collision_table_castA_castB[type_A - 1][type_B - 1]; + collision_func = collision_table_castA_castB[type_A - 2][type_B - 2]; } } diff --git a/servers/physics_2d/collision_solver_2d_sw.cpp b/servers/physics_2d/collision_solver_2d_sw.cpp index 8f8a4a862c..ae50615953 100644 --- a/servers/physics_2d/collision_solver_2d_sw.cpp +++ b/servers/physics_2d/collision_solver_2d_sw.cpp @@ -73,6 +73,65 @@ bool CollisionSolver2DSW::solve_static_world_margin(const Shape2DSW *p_shape_A, return found; } +bool CollisionSolver2DSW::solve_separation_ray(const Shape2DSW *p_shape_A, const Vector2 &p_motion_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *r_sep_axis, real_t p_margin) { + const SeparationRayShape2DSW *ray = static_cast<const SeparationRayShape2DSW *>(p_shape_A); + if (p_shape_B->get_type() == PhysicsServer2D::SHAPE_SEPARATION_RAY) { + return false; + } + + Vector2 from = p_transform_A.get_origin(); + Vector2 to = from + p_transform_A[1] * (ray->get_length() + p_margin); + if (p_motion_A != Vector2()) { + //not the best but should be enough + Vector2 normal = (to - from).normalized(); + to += normal * MAX(0.0, normal.dot(p_motion_A)); + } + Vector2 support_A = to; + + Transform2D invb = p_transform_B.affine_inverse(); + from = invb.xform(from); + to = invb.xform(to); + + Vector2 p, n; + if (!p_shape_B->intersect_segment(from, to, p, n)) { + if (r_sep_axis) { + *r_sep_axis = p_transform_A[1].normalized(); + } + return false; + } + + // Discard contacts when the ray is fully contained inside the shape. + if (n == Vector2()) { + if (r_sep_axis) { + *r_sep_axis = p_transform_A[1].normalized(); + } + return false; + } + + // Discard contacts in the wrong direction. + if (n.dot(from - to) < CMP_EPSILON) { + if (r_sep_axis) { + *r_sep_axis = p_transform_A[1].normalized(); + } + return false; + } + + Vector2 support_B = p_transform_B.xform(p); + if (ray->get_slide_on_slope()) { + Vector2 global_n = invb.basis_xform_inv(n).normalized(); + support_B = support_A + (support_B - support_A).length() * global_n; + } + + if (p_result_callback) { + if (p_swap_result) { + p_result_callback(support_B, support_A, p_userdata); + } else { + p_result_callback(support_A, support_B, p_userdata); + } + } + return true; +} + struct _ConcaveCollisionInfo2D { const Transform2D *transform_A; const Shape2DSW *shape_A; @@ -90,23 +149,23 @@ struct _ConcaveCollisionInfo2D { Vector2 *sep_axis; }; -void CollisionSolver2DSW::concave_callback(void *p_userdata, Shape2DSW *p_convex) { +bool CollisionSolver2DSW::concave_callback(void *p_userdata, Shape2DSW *p_convex) { _ConcaveCollisionInfo2D &cinfo = *(_ConcaveCollisionInfo2D *)(p_userdata); cinfo.aabb_tests++; - if (!cinfo.result_callback && cinfo.collided) { - return; //already collided and no contacts requested, don't test anymore - } bool collided = collision_solver(cinfo.shape_A, *cinfo.transform_A, cinfo.motion_A, p_convex, *cinfo.transform_B, cinfo.motion_B, cinfo.result_callback, cinfo.userdata, cinfo.swap_result, cinfo.sep_axis, cinfo.margin_A, cinfo.margin_B); if (!collided) { - return; + return false; } cinfo.collided = true; cinfo.collisions++; + + // Stop at first collision if contacts are not needed. + return !cinfo.result_callback; } -bool CollisionSolver2DSW::solve_concave(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *sep_axis, real_t p_margin_A, real_t p_margin_B) { +bool CollisionSolver2DSW::solve_concave(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *r_sep_axis, real_t p_margin_A, real_t p_margin_B) { const ConcaveShape2DSW *concave_B = static_cast<const ConcaveShape2DSW *>(p_shape_B); _ConcaveCollisionInfo2D cinfo; @@ -119,7 +178,7 @@ bool CollisionSolver2DSW::solve_concave(const Shape2DSW *p_shape_A, const Transf cinfo.swap_result = p_swap_result; cinfo.collided = false; cinfo.collisions = 0; - cinfo.sep_axis = sep_axis; + cinfo.sep_axis = r_sep_axis; cinfo.margin_A = p_margin_A; cinfo.margin_B = p_margin_B; @@ -150,7 +209,7 @@ bool CollisionSolver2DSW::solve_concave(const Shape2DSW *p_shape_A, const Transf return cinfo.collided; } -bool CollisionSolver2DSW::solve(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, Vector2 *sep_axis, real_t p_margin_A, real_t p_margin_B) { +bool CollisionSolver2DSW::solve(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, Vector2 *r_sep_axis, real_t p_margin_A, real_t p_margin_B) { PhysicsServer2D::ShapeType type_A = p_shape_A->get_type(); PhysicsServer2D::ShapeType type_B = p_shape_B->get_type(); bool concave_A = p_shape_A->is_concave(); @@ -177,18 +236,29 @@ bool CollisionSolver2DSW::solve(const Shape2DSW *p_shape_A, const Transform2D &p return solve_static_world_margin(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false); } + } else if (type_A == PhysicsServer2D::SHAPE_SEPARATION_RAY) { + if (type_B == PhysicsServer2D::SHAPE_SEPARATION_RAY) { + return false; //no ray-ray + } + + if (swap) { + return solve_separation_ray(p_shape_B, p_motion_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, r_sep_axis, p_margin_B); + } else { + return solve_separation_ray(p_shape_A, p_motion_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, r_sep_axis, p_margin_A); + } + } else if (concave_B) { if (concave_A) { return false; } if (!swap) { - return solve_concave(p_shape_A, p_transform_A, p_motion_A, p_shape_B, p_transform_B, p_motion_B, p_result_callback, p_userdata, false, sep_axis, margin_A, margin_B); + return solve_concave(p_shape_A, p_transform_A, p_motion_A, p_shape_B, p_transform_B, p_motion_B, p_result_callback, p_userdata, false, r_sep_axis, margin_A, margin_B); } else { - return solve_concave(p_shape_B, p_transform_B, p_motion_B, p_shape_A, p_transform_A, p_motion_A, p_result_callback, p_userdata, true, sep_axis, margin_A, margin_B); + return solve_concave(p_shape_B, p_transform_B, p_motion_B, p_shape_A, p_transform_A, p_motion_A, p_result_callback, p_userdata, true, r_sep_axis, margin_A, margin_B); } } else { - return collision_solver(p_shape_A, p_transform_A, p_motion_A, p_shape_B, p_transform_B, p_motion_B, p_result_callback, p_userdata, false, sep_axis, margin_A, margin_B); + return collision_solver(p_shape_A, p_transform_A, p_motion_A, p_shape_B, p_transform_B, p_motion_B, p_result_callback, p_userdata, false, r_sep_axis, margin_A, margin_B); } } diff --git a/servers/physics_2d/collision_solver_2d_sw.h b/servers/physics_2d/collision_solver_2d_sw.h index 17c0c2fe70..62fccc4ff3 100644 --- a/servers/physics_2d/collision_solver_2d_sw.h +++ b/servers/physics_2d/collision_solver_2d_sw.h @@ -39,12 +39,12 @@ public: private: static bool solve_static_world_margin(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result); - static void concave_callback(void *p_userdata, Shape2DSW *p_convex); - static bool solve_concave(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *sep_axis = nullptr, real_t p_margin_A = 0, real_t p_margin_B = 0); - static bool solve_raycast(const Shape2DSW *p_shape_A, const Vector2 &p_motion_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *sep_axis = nullptr); + static bool concave_callback(void *p_userdata, Shape2DSW *p_convex); + static bool solve_concave(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *r_sep_axis = nullptr, real_t p_margin_A = 0, real_t p_margin_B = 0); + static bool solve_separation_ray(const Shape2DSW *p_shape_A, const Vector2 &p_motion_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *r_sep_axis = nullptr, real_t p_margin = 0); public: - static bool solve(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, Vector2 *sep_axis = nullptr, real_t p_margin_A = 0, real_t p_margin_B = 0); + static bool solve(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, Vector2 *r_sep_axis = nullptr, real_t p_margin_A = 0, real_t p_margin_B = 0); }; #endif // COLLISION_SOLVER_2D_SW_H diff --git a/servers/physics_2d/joints_2d_sw.cpp b/servers/physics_2d/joints_2d_sw.cpp index 5a0a628fbc..fa8499a81d 100644 --- a/servers/physics_2d/joints_2d_sw.cpp +++ b/servers/physics_2d/joints_2d_sw.cpp @@ -68,13 +68,13 @@ static inline real_t k_scalar(Body2DSW *a, Body2DSW *b, const Vector2 &rA, const { value += a->get_inv_mass(); - real_t rcn = rA.cross(n); + real_t rcn = (rA - a->get_center_of_mass()).cross(n); value += a->get_inv_inertia() * rcn * rcn; } if (b) { value += b->get_inv_mass(); - real_t rcn = rB.cross(n); + real_t rcn = (rB - b->get_center_of_mass()).cross(n); value += b->get_inv_inertia() * rcn * rcn; } @@ -83,9 +83,9 @@ static inline real_t k_scalar(Body2DSW *a, Body2DSW *b, const Vector2 &rA, const static inline Vector2 relative_velocity(Body2DSW *a, Body2DSW *b, Vector2 rA, Vector2 rB) { - Vector2 sum = a->get_linear_velocity() - rA.orthogonal() * a->get_angular_velocity(); + Vector2 sum = a->get_linear_velocity() - (rA - a->get_center_of_mass()).orthogonal() * a->get_angular_velocity(); if (b) { - return (b->get_linear_velocity() - rB.orthogonal() * b->get_angular_velocity()) - sum; + return (b->get_linear_velocity() - (rB - b->get_center_of_mass()).orthogonal() * b->get_angular_velocity()) - sum; } else { return -sum; } @@ -172,11 +172,11 @@ bool PinJoint2DSW::pre_solve(real_t p_step) { void PinJoint2DSW::solve(real_t p_step) { // compute relative velocity - Vector2 vA = A->get_linear_velocity() - custom_cross(rA, A->get_angular_velocity()); + Vector2 vA = A->get_linear_velocity() - custom_cross(rA - A->get_center_of_mass(), A->get_angular_velocity()); Vector2 rel_vel; if (B) { - rel_vel = B->get_linear_velocity() - custom_cross(rB, B->get_angular_velocity()) - vA; + rel_vel = B->get_linear_velocity() - custom_cross(rB - B->get_center_of_mass(), B->get_angular_velocity()) - vA; } else { rel_vel = -vA; } @@ -238,6 +238,9 @@ k_tensor(Body2DSW *a, Body2DSW *b, Vector2 r1, Vector2 r2, Vector2 *k1, Vector2 k21 = 0.0f; k22 = m_sum; + r1 -= a->get_center_of_mass(); + r2 -= b->get_center_of_mass(); + // add the influence from r1 real_t a_i_inv = a->get_inv_inertia(); real_t r1xsq = r1.x * r1.x * a_i_inv; diff --git a/servers/physics_2d/physics_server_2d_sw.cpp b/servers/physics_2d/physics_server_2d_sw.cpp index 88c097453e..d0a42ca95b 100644 --- a/servers/physics_2d/physics_server_2d_sw.cpp +++ b/servers/physics_2d/physics_server_2d_sw.cpp @@ -30,6 +30,7 @@ #include "physics_server_2d_sw.h" +#include "body_direct_state_2d_sw.h" #include "broad_phase_2d_bvh.h" #include "collision_solver_2d_sw.h" #include "core/config/project_settings.h" @@ -45,6 +46,9 @@ RID PhysicsServer2DSW::_shape_create(ShapeType p_shape) { case SHAPE_WORLD_MARGIN: { shape = memnew(WorldMarginShape2DSW); } break; + case SHAPE_SEPARATION_RAY: { + shape = memnew(SeparationRayShape2DSW); + } break; case SHAPE_SEGMENT: { shape = memnew(SegmentShape2DSW); } break; @@ -79,6 +83,10 @@ RID PhysicsServer2DSW::world_margin_shape_create() { return _shape_create(SHAPE_WORLD_MARGIN); } +RID PhysicsServer2DSW::separation_ray_shape_create() { + return _shape_create(SHAPE_SEPARATION_RAY); +} + RID PhysicsServer2DSW::segment_shape_create() { return _shape_create(SHAPE_SEGMENT); } @@ -735,20 +743,27 @@ uint32_t PhysicsServer2DSW::body_get_collision_mask(RID p_body) const { return body->get_collision_mask(); }; -void PhysicsServer2DSW::body_set_param(RID p_body, BodyParameter p_param, real_t p_value) { +void PhysicsServer2DSW::body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) { Body2DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND(!body); body->set_param(p_param, p_value); }; -real_t PhysicsServer2DSW::body_get_param(RID p_body, BodyParameter p_param) const { +Variant PhysicsServer2DSW::body_get_param(RID p_body, BodyParameter p_param) const { Body2DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, 0); return body->get_param(p_param); }; +void PhysicsServer2DSW::body_reset_mass_properties(RID p_body) { + Body2DSW *body = body_owner.getornull(p_body); + ERR_FAIL_COND(!body); + + return body->reset_mass_properties(); +} + void PhysicsServer2DSW::body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) { Body2DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND(!body); @@ -919,6 +934,12 @@ int PhysicsServer2DSW::body_get_max_contacts_reported(RID p_body) const { return body->get_max_contacts_reported(); } +void PhysicsServer2DSW::body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) { + Body2DSW *body = body_owner.getornull(p_body); + ERR_FAIL_COND(!body); + body->set_state_sync_callback(p_instance, p_callback); +} + void PhysicsServer2DSW::body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata) { Body2DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND(!body); @@ -939,7 +960,7 @@ void PhysicsServer2DSW::body_set_pickable(RID p_body, bool p_pickable) { body->set_pickable(p_pickable); } -bool PhysicsServer2DSW::body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, MotionResult *r_result, const Set<RID> &p_exclude) { +bool PhysicsServer2DSW::body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, MotionResult *r_result, bool p_collide_separation_ray, const Set<RID> &p_exclude) { Body2DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, false); ERR_FAIL_COND_V(!body->get_space(), false); @@ -947,23 +968,19 @@ bool PhysicsServer2DSW::body_test_motion(RID p_body, const Transform2D &p_from, _update_shapes(); - return body->get_space()->test_body_motion(body, p_from, p_motion, p_margin, r_result, p_exclude); + return body->get_space()->test_body_motion(body, p_from, p_motion, p_margin, r_result, p_collide_separation_ray, p_exclude); } PhysicsDirectBodyState2D *PhysicsServer2DSW::body_get_direct_state(RID p_body) { ERR_FAIL_COND_V_MSG((using_threads && !doing_sync), nullptr, "Body state is inaccessible right now, wait for iteration or physics process notification."); - if (!body_owner.owns(p_body)) { - return nullptr; - } - Body2DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, nullptr); + ERR_FAIL_COND_V(!body->get_space(), nullptr); ERR_FAIL_COND_V_MSG(body->get_space()->is_locked(), nullptr, "Body state is inaccessible right now, wait for iteration or physics process notification."); - direct_state->body = body; - return direct_state; + return body->get_direct_state(); } /* JOINT API */ @@ -1227,10 +1244,8 @@ void PhysicsServer2DSW::set_collision_iterations(int p_iterations) { void PhysicsServer2DSW::init() { doing_sync = false; - last_step = 0.001; iterations = 8; // 8? stepper = memnew(Step2DSW); - direct_state = memnew(PhysicsDirectBodyState2DSW); }; void PhysicsServer2DSW::step(real_t p_step) { @@ -1240,8 +1255,6 @@ void PhysicsServer2DSW::step(real_t p_step) { _update_shapes(); - last_step = p_step; - PhysicsDirectBodyState2DSW::singleton->step = p_step; island_count = 0; active_objects = 0; collision_pairs = 0; @@ -1313,7 +1326,6 @@ void PhysicsServer2DSW::end_sync() { void PhysicsServer2DSW::finish() { memdelete(stepper); - memdelete(direct_state); }; void PhysicsServer2DSW::_update_shapes() { diff --git a/servers/physics_2d/physics_server_2d_sw.h b/servers/physics_2d/physics_server_2d_sw.h index 3610f43f93..6a2d9e37e0 100644 --- a/servers/physics_2d/physics_server_2d_sw.h +++ b/servers/physics_2d/physics_server_2d_sw.h @@ -46,7 +46,6 @@ class PhysicsServer2DSW : public PhysicsServer2D { bool active; int iterations; bool doing_sync; - real_t last_step; int island_count; int active_objects; @@ -59,8 +58,6 @@ class PhysicsServer2DSW : public PhysicsServer2D { Step2DSW *stepper; Set<const Space2DSW *> active_spaces; - PhysicsDirectBodyState2DSW *direct_state; - mutable RID_PtrOwner<Shape2DSW, true> shape_owner; mutable RID_PtrOwner<Space2DSW, true> space_owner; mutable RID_PtrOwner<Area2DSW, true> area_owner; @@ -88,6 +85,7 @@ public: }; virtual RID world_margin_shape_create() override; + virtual RID separation_ray_shape_create() override; virtual RID segment_shape_create() override; virtual RID circle_shape_create() override; virtual RID rectangle_shape_create() override; @@ -207,8 +205,10 @@ public: virtual void body_set_collision_mask(RID p_body, uint32_t p_mask) override; virtual uint32_t body_get_collision_mask(RID p_body) const override; - virtual void body_set_param(RID p_body, BodyParameter p_param, real_t p_value) override; - virtual real_t body_get_param(RID p_body, BodyParameter p_param) const override; + virtual void body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) override; + virtual Variant body_get_param(RID p_body, BodyParameter p_param) const override; + + virtual void body_reset_mass_properties(RID p_body) override; virtual void body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) override; virtual Variant body_get_state(RID p_body, BodyState p_state) const override; @@ -241,12 +241,14 @@ public: virtual void body_set_max_contacts_reported(RID p_body, int p_contacts) override; virtual int body_get_max_contacts_reported(RID p_body) const override; + virtual void body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) override; virtual void body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata = Variant()) override; + virtual bool body_collide_shape(RID p_body, int p_body_shape, RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, Vector2 *r_results, int p_result_max, int &r_result_count) override; virtual void body_set_pickable(RID p_body, bool p_pickable) override; - virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) override; + virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) override; // this function only works on physics process, errors and returns null otherwise virtual PhysicsDirectBodyState2D *body_get_direct_state(RID p_body) override; diff --git a/servers/physics_2d/physics_server_2d_wrap_mt.h b/servers/physics_2d/physics_server_2d_wrap_mt.h index b93178919d..e65c4f5f3a 100644 --- a/servers/physics_2d/physics_server_2d_wrap_mt.h +++ b/servers/physics_2d/physics_server_2d_wrap_mt.h @@ -80,6 +80,7 @@ public: //FUNC1RID(shape,ShapeType); todo fix FUNCRID(world_margin_shape) + FUNCRID(separation_ray_shape) FUNCRID(segment_shape) FUNCRID(circle_shape) FUNCRID(rectangle_shape) @@ -211,8 +212,10 @@ public: FUNC2(body_set_collision_mask, RID, uint32_t); FUNC1RC(uint32_t, body_get_collision_mask, RID); - FUNC3(body_set_param, RID, BodyParameter, real_t); - FUNC2RC(real_t, body_get_param, RID, BodyParameter); + FUNC3(body_set_param, RID, BodyParameter, const Variant &); + FUNC2RC(Variant, body_get_param, RID, BodyParameter); + + FUNC1(body_reset_mass_properties, RID); FUNC3(body_set_state, RID, BodyState, const Variant &); FUNC2RC(Variant, body_get_state, RID, BodyState); @@ -244,6 +247,7 @@ public: FUNC2(body_set_omit_force_integration, RID, bool); FUNC1RC(bool, body_is_omitting_force_integration, RID); + FUNC3(body_set_state_sync_callback, RID, void *, BodyStateCallback); FUNC3(body_set_force_integration_callback, RID, const Callable &, const Variant &); bool body_collide_shape(RID p_body, int p_body_shape, RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, Vector2 *r_results, int p_result_max, int &r_result_count) override { @@ -252,9 +256,9 @@ public: FUNC2(body_set_pickable, RID, bool); - bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) override { + bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) override { ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false); - return physics_2d_server->body_test_motion(p_body, p_from, p_motion, p_margin, r_result, p_exclude); + return physics_2d_server->body_test_motion(p_body, p_from, p_motion, p_margin, r_result, p_collide_separation_ray, p_exclude); } // this function only works on physics process, errors and returns null otherwise diff --git a/servers/physics_2d/shape_2d_sw.cpp b/servers/physics_2d/shape_2d_sw.cpp index b3e4ca84c3..064c4afe52 100644 --- a/servers/physics_2d/shape_2d_sw.cpp +++ b/servers/physics_2d/shape_2d_sw.cpp @@ -143,6 +143,46 @@ Variant WorldMarginShape2DSW::get_data() const { /*********************************************************/ /*********************************************************/ +void SeparationRayShape2DSW::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const { + r_amount = 1; + + if (p_normal.y > 0) { + *r_supports = Vector2(0, length); + } else { + *r_supports = Vector2(); + } +} + +bool SeparationRayShape2DSW::contains_point(const Vector2 &p_point) const { + return false; +} + +bool SeparationRayShape2DSW::intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const { + return false; //rays can't be intersected +} + +real_t SeparationRayShape2DSW::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const { + return 0; //rays are mass-less +} + +void SeparationRayShape2DSW::set_data(const Variant &p_data) { + Dictionary d = p_data; + length = d["length"]; + slide_on_slope = d["slide_on_slope"]; + configure(Rect2(0, 0, 0.001, length)); +} + +Variant SeparationRayShape2DSW::get_data() const { + Dictionary d; + d["length"] = length; + d["slide_on_slope"] = slide_on_slope; + return d; +} + +/*********************************************************/ +/*********************************************************/ +/*********************************************************/ + void SegmentShape2DSW::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const { if (Math::abs(p_normal.dot(n)) > _SEGMENT_IS_VALID_SUPPORT_THRESHOLD) { r_supports[0] = a; @@ -530,14 +570,7 @@ bool ConvexPolygonShape2DSW::intersect_segment(const Vector2 &p_begin, const Vec } } - if (inters) { - if (n.dot(r_normal) > 0) { - r_normal = -r_normal; - } - } - - //return get_aabb().intersects_segment(p_begin,p_end,&r_point,&r_normal); - return inters; //todo + return inters; } real_t ConvexPolygonShape2DSW::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const { @@ -888,7 +921,7 @@ Variant ConcavePolygonShape2DSW::get_data() const { return rsegments; } -void ConcavePolygonShape2DSW::cull(const Rect2 &p_local_aabb, Callback p_callback, void *p_userdata) const { +void ConcavePolygonShape2DSW::cull(const Rect2 &p_local_aabb, QueryCallback p_callback, void *p_userdata) const { uint32_t *stack = (uint32_t *)alloca(sizeof(int) * bvh_depth); enum { @@ -936,7 +969,9 @@ void ConcavePolygonShape2DSW::cull(const Rect2 &p_local_aabb, Callback p_callbac SegmentShape2DSW ss(a, b, (b - a).orthogonal().normalized()); - p_callback(p_userdata, &ss); + if (p_callback(p_userdata, &ss)) { + return; + } stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; } else { diff --git a/servers/physics_2d/shape_2d_sw.h b/servers/physics_2d/shape_2d_sw.h index 45d3379dfa..1185d343ee 100644 --- a/servers/physics_2d/shape_2d_sw.h +++ b/servers/physics_2d/shape_2d_sw.h @@ -64,6 +64,8 @@ public: _FORCE_INLINE_ Rect2 get_aabb() const { return aabb; } _FORCE_INLINE_ bool is_configured() const { return configured; } + virtual bool allows_one_way_collision() const { return true; } + virtual bool is_concave() const { return false; } virtual bool contains_point(const Vector2 &p_point) const = 0; @@ -177,6 +179,43 @@ public: } }; +class SeparationRayShape2DSW : public Shape2DSW { + real_t length; + bool slide_on_slope; + +public: + _FORCE_INLINE_ real_t get_length() const { return length; } + _FORCE_INLINE_ bool get_slide_on_slope() const { return slide_on_slope; } + + virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_SEPARATION_RAY; } + + virtual bool allows_one_way_collision() const override { return false; } + + virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { project_range(p_normal, p_transform, r_min, r_max); } + virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const override; + + virtual bool contains_point(const Vector2 &p_point) const override; + virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const override; + virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const override; + + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; + + _FORCE_INLINE_ void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { + //real large + r_max = p_normal.dot(p_transform.get_origin()); + r_min = p_normal.dot(p_transform.xform(Vector2(0, length))); + if (r_max < r_min) { + SWAP(r_max, r_min); + } + } + + DEFAULT_PROJECT_RANGE_CAST + + _FORCE_INLINE_ SeparationRayShape2DSW() {} + _FORCE_INLINE_ SeparationRayShape2DSW(real_t p_length) { length = p_length; } +}; + class SegmentShape2DSW : public Shape2DSW { Vector2 a; Vector2 b; @@ -426,9 +465,11 @@ public: class ConcaveShape2DSW : public Shape2DSW { public: virtual bool is_concave() const override { return true; } - typedef void (*Callback)(void *p_userdata, Shape2DSW *p_convex); - virtual void cull(const Rect2 &p_local_aabb, Callback p_callback, void *p_userdata) const = 0; + // Returns true to stop the query. + typedef bool (*QueryCallback)(void *p_userdata, Shape2DSW *p_convex); + + virtual void cull(const Rect2 &p_local_aabb, QueryCallback p_callback, void *p_userdata) const = 0; }; class ConcavePolygonShape2DSW : public ConcaveShape2DSW { @@ -486,7 +527,7 @@ public: virtual void set_data(const Variant &p_data) override; virtual Variant get_data() const override; - virtual void cull(const Rect2 &p_local_aabb, Callback p_callback, void *p_userdata) const override; + virtual void cull(const Rect2 &p_local_aabb, QueryCallback p_callback, void *p_userdata) const override; DEFAULT_PROJECT_RANGE_CAST }; diff --git a/servers/physics_2d/space_2d_sw.cpp b/servers/physics_2d/space_2d_sw.cpp index f04f3ab583..7dbd1243cc 100644 --- a/servers/physics_2d/space_2d_sw.cpp +++ b/servers/physics_2d/space_2d_sw.cpp @@ -482,7 +482,7 @@ bool PhysicsDirectSpaceState2DSW::rest_info(RID p_shape, const Transform2D &p_sh r_info->metadata = rcd.best_object->get_shape_metadata(rcd.best_shape); if (rcd.best_object->get_type() == CollisionObject2DSW::TYPE_BODY) { const Body2DSW *body = static_cast<const Body2DSW *>(rcd.best_object); - Vector2 rel_vec = r_info->point - body->get_transform().get_origin(); + Vector2 rel_vec = r_info->point - (body->get_transform().get_origin() + body->get_center_of_mass()); r_info->linear_velocity = Vector2(-body->get_angular_velocity() * rel_vec.y, body->get_angular_velocity() * rel_vec.x) + body->get_linear_velocity(); } else { @@ -528,7 +528,7 @@ int Space2DSW::_cull_aabb_for_body(Body2DSW *p_body, const Rect2 &p_aabb) { return amount; } -bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, PhysicsServer2D::MotionResult *r_result, const Set<RID> &p_exclude) { +bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, PhysicsServer2D::MotionResult *r_result, bool p_collide_separation_ray, const Set<RID> &p_exclude) { //give me back regular physics engine logic //this is madness //and most people using this function will think @@ -621,7 +621,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co Transform2D col_obj_shape_xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx); - if (col_obj->is_shape_set_as_one_way_collision(shape_idx)) { + if (body_shape->allows_one_way_collision() && col_obj->is_shape_set_as_one_way_collision(shape_idx)) { cbk.valid_dir = col_obj_shape_xform.get_axis(1).normalized(); real_t owc_margin = col_obj->get_shape_one_way_collision_margin(shape_idx); @@ -634,7 +634,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co //fix for moving platforms (kinematic and dynamic), margin is increased by how much it moved in the given direction Vector2 lv = b->get_linear_velocity(); //compute displacement from linear velocity - Vector2 motion = lv * PhysicsDirectBodyState2DSW::singleton->step; + Vector2 motion = lv * last_step; real_t motion_len = motion.length(); motion.normalize(); cbk.valid_depth += motion_len * MAX(motion.dot(-cbk.valid_dir), 0.0); @@ -726,6 +726,16 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co } Shape2DSW *body_shape = p_body->get_shape(body_shape_idx); + + // Colliding separation rays allows to properly snap to the ground, + // otherwise it's not needed in regular motion. + if (!p_collide_separation_ray && (body_shape->get_type() == PhysicsServer2D::SHAPE_SEPARATION_RAY)) { + // When slide on slope is on, separation ray shape acts like a regular shape. + if (!static_cast<SeparationRayShape2DSW *>(body_shape)->get_slide_on_slope()) { + continue; + } + } + Transform2D body_shape_xform = body_transform * p_body->get_shape_transform(body_shape_idx); bool stuck = false; @@ -762,7 +772,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co //test initial overlap if (CollisionSolver2DSW::solve(body_shape, body_shape_xform, Vector2(), against_shape, col_obj_shape_xform, Vector2(), nullptr, nullptr, nullptr, 0)) { - if (col_obj->is_shape_set_as_one_way_collision(col_shape_idx)) { + if (body_shape->allows_one_way_collision() && col_obj->is_shape_set_as_one_way_collision(col_shape_idx)) { Vector2 direction = col_obj_shape_xform.get_axis(1).normalized(); if (motion_normal.dot(direction) < 0) { continue; @@ -806,7 +816,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co } } - if (col_obj->is_shape_set_as_one_way_collision(col_shape_idx)) { + if (body_shape->allows_one_way_collision() && col_obj->is_shape_set_as_one_way_collision(col_shape_idx)) { Vector2 cd[2]; PhysicsServer2DSW::CollCbkData cbk; cbk.max = 1; @@ -904,7 +914,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co Transform2D col_obj_shape_xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx); - if (col_obj->is_shape_set_as_one_way_collision(shape_idx)) { + if (body_shape->allows_one_way_collision() && col_obj->is_shape_set_as_one_way_collision(shape_idx)) { rcd.valid_dir = col_obj_shape_xform.get_axis(1).normalized(); real_t owc_margin = col_obj->get_shape_one_way_collision_margin(shape_idx); @@ -916,7 +926,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co //fix for moving platforms (kinematic and dynamic), margin is increased by how much it moved in the given direction Vector2 lv = b->get_linear_velocity(); //compute displacement from linear velocity - Vector2 motion = lv * PhysicsDirectBodyState2DSW::singleton->step; + Vector2 motion = lv * last_step; real_t motion_len = motion.length(); motion.normalize(); rcd.valid_depth += motion_len * MAX(motion.dot(-rcd.valid_dir), 0.0); @@ -951,7 +961,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co r_result->collider_metadata = rcd.best_object->get_shape_metadata(rcd.best_shape); const Body2DSW *body = static_cast<const Body2DSW *>(rcd.best_object); - Vector2 rel_vec = r_result->collision_point - body->get_transform().get_origin(); + Vector2 rel_vec = r_result->collision_point - (body->get_transform().get_origin() + body->get_center_of_mass()); r_result->collider_velocity = Vector2(-body->get_angular_velocity() * rel_vec.y, body->get_angular_velocity() * rel_vec.x) + body->get_linear_velocity(); r_result->travel = safe * p_motion; @@ -1031,12 +1041,12 @@ void Space2DSW::body_remove_from_active_list(SelfList<Body2DSW> *p_body) { active_list.remove(p_body); } -void Space2DSW::body_add_to_inertia_update_list(SelfList<Body2DSW> *p_body) { - inertia_update_list.add(p_body); +void Space2DSW::body_add_to_mass_properties_update_list(SelfList<Body2DSW> *p_body) { + mass_properties_update_list.add(p_body); } -void Space2DSW::body_remove_from_inertia_update_list(SelfList<Body2DSW> *p_body) { - inertia_update_list.remove(p_body); +void Space2DSW::body_remove_from_mass_properties_update_list(SelfList<Body2DSW> *p_body) { + mass_properties_update_list.remove(p_body); } BroadPhase2DSW *Space2DSW::get_broadphase() { @@ -1102,9 +1112,9 @@ void Space2DSW::call_queries() { void Space2DSW::setup() { contact_debug_count = 0; - while (inertia_update_list.first()) { - inertia_update_list.first()->self()->update_inertias(); - inertia_update_list.remove(inertia_update_list.first()); + while (mass_properties_update_list.first()) { + mass_properties_update_list.first()->self()->update_mass_properties(); + mass_properties_update_list.remove(mass_properties_update_list.first()); } } diff --git a/servers/physics_2d/space_2d_sw.h b/servers/physics_2d/space_2d_sw.h index 3be36852b0..ad82a14af5 100644 --- a/servers/physics_2d/space_2d_sw.h +++ b/servers/physics_2d/space_2d_sw.h @@ -49,13 +49,13 @@ class PhysicsDirectSpaceState2DSW : public PhysicsDirectSpaceState2D { public: Space2DSW *space; - virtual int intersect_point(const Vector2 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) override; - virtual int intersect_point_on_canvas(const Vector2 &p_point, ObjectID p_canvas_instance_id, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) override; - virtual bool intersect_ray(const Vector2 &p_from, const Vector2 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual int intersect_shape(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool cast_motion(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool collide_shape(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, Vector2 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool rest_info(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual int intersect_point(const Vector2 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) override; + virtual int intersect_point_on_canvas(const Vector2 &p_point, ObjectID p_canvas_instance_id, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) override; + virtual bool intersect_ray(const Vector2 &p_from, const Vector2 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual int intersect_shape(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool cast_motion(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool collide_shape(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, Vector2 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool rest_info(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; PhysicsDirectSpaceState2DSW(); }; @@ -86,7 +86,7 @@ private: BroadPhase2DSW *broadphase; SelfList<Body2DSW>::List active_list; - SelfList<Body2DSW>::List inertia_update_list; + SelfList<Body2DSW>::List mass_properties_update_list; SelfList<Body2DSW>::List state_query_list; SelfList<Area2DSW>::List monitor_query_list; SelfList<Area2DSW>::List area_moved_list; @@ -117,6 +117,8 @@ private: bool locked; + real_t last_step = 0.001; + int island_count; int active_objects; int collision_pairs; @@ -138,8 +140,8 @@ public: const SelfList<Body2DSW>::List &get_active_body_list() const; void body_add_to_active_list(SelfList<Body2DSW> *p_body); void body_remove_from_active_list(SelfList<Body2DSW> *p_body); - void body_add_to_inertia_update_list(SelfList<Body2DSW> *p_body); - void body_remove_from_inertia_update_list(SelfList<Body2DSW> *p_body); + void body_add_to_mass_properties_update_list(SelfList<Body2DSW> *p_body); + void body_remove_from_mass_properties_update_list(SelfList<Body2DSW> *p_body); void area_add_to_moved_list(SelfList<Area2DSW> *p_area); void area_remove_from_moved_list(SelfList<Area2DSW> *p_area); const SelfList<Area2DSW>::List &get_moved_area_list() const; @@ -172,6 +174,9 @@ public: void lock(); void unlock(); + real_t get_last_step() const { return last_step; } + void set_last_step(real_t p_step) { last_step = p_step; } + void set_param(PhysicsServer2D::SpaceParameter p_param, real_t p_value); real_t get_param(PhysicsServer2D::SpaceParameter p_param) const; @@ -183,7 +188,7 @@ public: int get_collision_pairs() const { return collision_pairs; } - bool test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, PhysicsServer2D::MotionResult *r_result, const Set<RID> &p_exclude = Set<RID>()); + bool test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, PhysicsServer2D::MotionResult *r_result, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()); void set_debug_contacts(int p_amount) { contact_debug.resize(p_amount); } _FORCE_INLINE_ bool is_debugging_contacts() const { return !contact_debug.is_empty(); } diff --git a/servers/physics_2d/step_2d_sw.cpp b/servers/physics_2d/step_2d_sw.cpp index 8b30160cc1..0306ec5050 100644 --- a/servers/physics_2d/step_2d_sw.cpp +++ b/servers/physics_2d/step_2d_sw.cpp @@ -129,6 +129,8 @@ void Step2DSW::step(Space2DSW *p_space, real_t p_delta, int p_iterations) { p_space->setup(); //update inertias, etc + p_space->set_last_step(p_delta); + iterations = p_iterations; delta = p_delta; diff --git a/servers/physics_3d/area_3d_sw.cpp b/servers/physics_3d/area_3d_sw.cpp index 364f63e4ad..c9e8bcb8ca 100644 --- a/servers/physics_3d/area_3d_sw.cpp +++ b/servers/physics_3d/area_3d_sw.cpp @@ -163,6 +163,20 @@ void Area3DSW::set_param(PhysicsServer3D::AreaParameter p_param, const Variant & case PhysicsServer3D::AREA_PARAM_PRIORITY: priority = p_value; break; + case PhysicsServer3D::AREA_PARAM_WIND_FORCE_MAGNITUDE: + ERR_FAIL_COND_MSG(wind_force_magnitude < 0, "Wind force magnitude must be a non-negative real number, but a negative number was specified."); + wind_force_magnitude = p_value; + break; + case PhysicsServer3D::AREA_PARAM_WIND_SOURCE: + wind_source = p_value; + break; + case PhysicsServer3D::AREA_PARAM_WIND_DIRECTION: + wind_direction = p_value; + break; + case PhysicsServer3D::AREA_PARAM_WIND_ATTENUATION_FACTOR: + ERR_FAIL_COND_MSG(wind_attenuation_factor < 0, "Wind attenuation factor must be a non-negative real number, but a negative number was specified."); + wind_attenuation_factor = p_value; + break; } } @@ -184,6 +198,14 @@ Variant Area3DSW::get_param(PhysicsServer3D::AreaParameter p_param) const { return angular_damp; case PhysicsServer3D::AREA_PARAM_PRIORITY: return priority; + case PhysicsServer3D::AREA_PARAM_WIND_FORCE_MAGNITUDE: + return wind_force_magnitude; + case PhysicsServer3D::AREA_PARAM_WIND_SOURCE: + return wind_source; + case PhysicsServer3D::AREA_PARAM_WIND_DIRECTION: + return wind_direction; + case PhysicsServer3D::AREA_PARAM_WIND_ATTENUATION_FACTOR: + return wind_attenuation_factor; } return Variant(); @@ -282,6 +304,26 @@ void Area3DSW::call_queries() { } } +void Area3DSW::compute_gravity(const Vector3 &p_position, Vector3 &r_gravity) const { + if (is_gravity_point()) { + const real_t gravity_distance_scale = get_gravity_distance_scale(); + Vector3 v = get_transform().xform(get_gravity_vector()) - p_position; + if (gravity_distance_scale > 0) { + const real_t v_length = v.length(); + if (v_length > 0) { + const real_t v_scaled = v_length * gravity_distance_scale; + r_gravity = (v.normalized() * (get_gravity() / (v_scaled * v_scaled))); + } else { + r_gravity = Vector3(); + } + } else { + r_gravity = v.normalized() * get_gravity(); + } + } else { + r_gravity = get_gravity_vector() * get_gravity(); + } +} + Area3DSW::Area3DSW() : CollisionObject3DSW(TYPE_AREA), monitor_query_list(this), diff --git a/servers/physics_3d/area_3d_sw.h b/servers/physics_3d/area_3d_sw.h index 5959ee1e95..d5f1e60119 100644 --- a/servers/physics_3d/area_3d_sw.h +++ b/servers/physics_3d/area_3d_sw.h @@ -34,7 +34,6 @@ #include "collision_object_3d_sw.h" #include "core/templates/self_list.h" #include "servers/physics_server_3d.h" -//#include "servers/physics_3d/query_sw.h" class Space3DSW; class Body3DSW; @@ -50,6 +49,10 @@ class Area3DSW : public CollisionObject3DSW { real_t point_attenuation; real_t linear_damp; real_t angular_damp; + real_t wind_force_magnitude = 0.0; + real_t wind_attenuation_factor = 0.0; + Vector3 wind_source; + Vector3 wind_direction; int priority; bool monitorable; @@ -97,18 +100,12 @@ class Area3DSW : public CollisionObject3DSW { Map<BodyKey, BodyState> monitored_bodies; Map<BodyKey, BodyState> monitored_areas; - //virtual void shape_changed_notify(ShapeSW *p_shape); - //virtual void shape_deleted_notify(ShapeSW *p_shape); - Set<Constraint3DSW *> constraints; virtual void _shapes_changed(); void _queue_monitor_update(); public: - //_FORCE_INLINE_ const Transform& get_inverse_transform() const { return inverse_transform; } - //_FORCE_INLINE_ SpaceSW* get_owner() { return owner; } - void set_monitor_callback(ObjectID p_id, const StringName &p_method); _FORCE_INLINE_ bool has_monitor_callback() const { return monitor_callback_id.is_valid(); } @@ -154,6 +151,18 @@ public: _FORCE_INLINE_ void set_priority(int p_priority) { priority = p_priority; } _FORCE_INLINE_ int get_priority() const { return priority; } + _FORCE_INLINE_ void set_wind_force_magnitude(real_t p_wind_force_magnitude) { wind_force_magnitude = p_wind_force_magnitude; } + _FORCE_INLINE_ real_t get_wind_force_magnitude() const { return wind_force_magnitude; } + + _FORCE_INLINE_ void set_wind_attenuation_factor(real_t p_wind_attenuation_factor) { wind_attenuation_factor = p_wind_attenuation_factor; } + _FORCE_INLINE_ real_t get_wind_attenuation_factor() const { return wind_attenuation_factor; } + + _FORCE_INLINE_ void set_wind_source(const Vector3 &p_wind_source) { wind_source = p_wind_source; } + _FORCE_INLINE_ const Vector3 &get_wind_source() const { return wind_source; } + + _FORCE_INLINE_ void set_wind_direction(const Vector3 &p_wind_direction) { wind_direction = p_wind_direction; } + _FORCE_INLINE_ const Vector3 &get_wind_direction() const { return wind_direction; } + _FORCE_INLINE_ void add_constraint(Constraint3DSW *p_constraint) { constraints.insert(p_constraint); } _FORCE_INLINE_ void remove_constraint(Constraint3DSW *p_constraint) { constraints.erase(p_constraint); } _FORCE_INLINE_ const Set<Constraint3DSW *> &get_constraints() const { return constraints; } @@ -168,6 +177,8 @@ public: void call_queries(); + void compute_gravity(const Vector3 &p_position, Vector3 &r_gravity) const; + Area3DSW(); ~Area3DSW(); }; diff --git a/servers/physics_3d/area_pair_3d_sw.cpp b/servers/physics_3d/area_pair_3d_sw.cpp index e740565da6..bf4f0035b4 100644 --- a/servers/physics_3d/area_pair_3d_sw.cpp +++ b/servers/physics_3d/area_pair_3d_sw.cpp @@ -33,7 +33,7 @@ bool AreaPair3DSW::setup(real_t p_step) { bool result = false; - if (area->interacts_with(body) && CollisionSolver3DSW::solve_static(body->get_shape(body_shape), body->get_transform() * body->get_shape_transform(body_shape), area->get_shape(area_shape), area->get_transform() * area->get_shape_transform(area_shape), nullptr, this)) { + if (area->collides_with(body) && CollisionSolver3DSW::solve_static(body->get_shape(body_shape), body->get_transform() * body->get_shape_transform(body_shape), area->get_shape(area_shape), area->get_transform() * area->get_shape_transform(area_shape), nullptr, this)) { result = true; } @@ -109,46 +109,51 @@ AreaPair3DSW::~AreaPair3DSW() { //////////////////////////////////////////////////// bool Area2Pair3DSW::setup(real_t p_step) { - bool result = false; - if (area_a->interacts_with(area_b) && CollisionSolver3DSW::solve_static(area_a->get_shape(shape_a), area_a->get_transform() * area_a->get_shape_transform(shape_a), area_b->get_shape(shape_b), area_b->get_transform() * area_b->get_shape_transform(shape_b), nullptr, this)) { - result = true; + bool result_a = area_a->collides_with(area_b); + bool result_b = area_b->collides_with(area_a); + if ((result_a || result_b) && !CollisionSolver3DSW::solve_static(area_a->get_shape(shape_a), area_a->get_transform() * area_a->get_shape_transform(shape_a), area_b->get_shape(shape_b), area_b->get_transform() * area_b->get_shape_transform(shape_b), nullptr, this)) { + result_a = false; + result_b = false; } - process_collision = false; - if (result != colliding) { - if (area_b->has_area_monitor_callback() && area_a->is_monitorable()) { - process_collision = true; - } else if (area_a->has_area_monitor_callback() && area_b->is_monitorable()) { + bool process_collision = false; + + process_collision_a = false; + if (result_a != colliding_a) { + if (area_a->has_area_monitor_callback() && area_b->is_monitorable()) { + process_collision_a = true; process_collision = true; } + colliding_a = result_a; + } - colliding = result; + process_collision_b = false; + if (result_b != colliding_b) { + if (area_b->has_area_monitor_callback() && area_a->is_monitorable()) { + process_collision_b = true; + process_collision = true; + } + colliding_b = result_b; } return process_collision; } bool Area2Pair3DSW::pre_solve(real_t p_step) { - if (!process_collision) { - return false; + if (process_collision_a) { + if (colliding_a) { + area_a->add_area_to_query(area_b, shape_b, shape_a); + } else { + area_a->remove_area_from_query(area_b, shape_b, shape_a); + } } - if (colliding) { - if (area_b->has_area_monitor_callback() && area_a->is_monitorable()) { + if (process_collision_b) { + if (colliding_b) { area_b->add_area_to_query(area_a, shape_a, shape_b); - } - - if (area_a->has_area_monitor_callback() && area_b->is_monitorable()) { - area_a->add_area_to_query(area_b, shape_b, shape_a); - } - } else { - if (area_b->has_area_monitor_callback() && area_a->is_monitorable()) { + } else { area_b->remove_area_from_query(area_a, shape_a, shape_b); } - - if (area_a->has_area_monitor_callback() && area_b->is_monitorable()) { - area_a->remove_area_from_query(area_b, shape_b, shape_a); - } } return false; // Never do any post solving. @@ -168,16 +173,18 @@ Area2Pair3DSW::Area2Pair3DSW(Area3DSW *p_area_a, int p_shape_a, Area3DSW *p_area } Area2Pair3DSW::~Area2Pair3DSW() { - if (colliding) { - if (area_b->has_area_monitor_callback()) { - area_b->remove_area_from_query(area_a, shape_a, shape_b); - } - + if (colliding_a) { if (area_a->has_area_monitor_callback()) { area_a->remove_area_from_query(area_b, shape_b, shape_a); } } + if (colliding_b) { + if (area_b->has_area_monitor_callback()) { + area_b->remove_area_from_query(area_a, shape_a, shape_b); + } + } + area_a->remove_constraint(this); area_b->remove_constraint(this); } @@ -187,7 +194,7 @@ Area2Pair3DSW::~Area2Pair3DSW() { bool AreaSoftBodyPair3DSW::setup(real_t p_step) { bool result = false; if ( - area->interacts_with(soft_body) && + area->collides_with(soft_body) && CollisionSolver3DSW::solve_static( soft_body->get_shape(soft_body_shape), soft_body->get_transform() * soft_body->get_shape_transform(soft_body_shape), diff --git a/servers/physics_3d/area_pair_3d_sw.h b/servers/physics_3d/area_pair_3d_sw.h index 8cc9e9ad63..4572dcbb23 100644 --- a/servers/physics_3d/area_pair_3d_sw.h +++ b/servers/physics_3d/area_pair_3d_sw.h @@ -58,8 +58,10 @@ class Area2Pair3DSW : public Constraint3DSW { Area3DSW *area_b; int shape_a; int shape_b; - bool colliding = false; - bool process_collision = false; + bool colliding_a = false; + bool colliding_b = false; + bool process_collision_a = false; + bool process_collision_b = false; public: virtual bool setup(real_t p_step) override; diff --git a/servers/physics_3d/body_3d_sw.cpp b/servers/physics_3d/body_3d_sw.cpp index 0c4079332d..41745545d8 100644 --- a/servers/physics_3d/body_3d_sw.cpp +++ b/servers/physics_3d/body_3d_sw.cpp @@ -29,12 +29,14 @@ /*************************************************************************/ #include "body_3d_sw.h" + #include "area_3d_sw.h" +#include "body_direct_state_3d_sw.h" #include "space_3d_sw.h" -void Body3DSW::_update_inertia() { - if (get_space() && !inertia_update_list.in_list()) { - get_space()->body_add_to_inertia_update_list(&inertia_update_list); +void Body3DSW::_mass_properties_changed() { + if (get_space() && !mass_properties_update_list.in_list() && (calculate_inertia || calculate_center_of_mass)) { + get_space()->body_add_to_mass_properties_update_list(&mass_properties_update_list); } } @@ -42,7 +44,7 @@ void Body3DSW::_update_transform_dependant() { center_of_mass = get_transform().basis.xform(center_of_mass_local); principal_inertia_axes = get_transform().basis * principal_inertia_axes_local; - // update inertia tensor + // Update inertia tensor. Basis tb = principal_inertia_axes; Basis tbt = tb.transposed(); Basis diag; @@ -50,74 +52,95 @@ void Body3DSW::_update_transform_dependant() { _inv_inertia_tensor = tb * diag * tbt; } -void Body3DSW::update_inertias() { +void Body3DSW::update_mass_properties() { // Update shapes and motions. switch (mode) { case PhysicsServer3D::BODY_MODE_DYNAMIC: { - // Update tensor for all shapes, not the best way but should be somehow OK. (inspired from bullet) real_t total_area = 0; - for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } + total_area += get_shape_area(i); } - // We have to recompute the center of mass. - center_of_mass_local.zero(); + if (calculate_center_of_mass) { + // We have to recompute the center of mass. + center_of_mass_local.zero(); - if (total_area != 0.0) { - for (int i = 0; i < get_shape_count(); i++) { - real_t area = get_shape_area(i); + if (total_area != 0.0) { + for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } - real_t mass = area * this->mass / total_area; + real_t area = get_shape_area(i); - // NOTE: we assume that the shape origin is also its center of mass. - center_of_mass_local += mass * get_shape_transform(i).origin; - } + real_t mass = area * this->mass / total_area; + + // NOTE: we assume that the shape origin is also its center of mass. + center_of_mass_local += mass * get_shape_transform(i).origin; + } - center_of_mass_local /= mass; + center_of_mass_local /= mass; + } } - // Recompute the inertia tensor. - Basis inertia_tensor; - inertia_tensor.set_zero(); - bool inertia_set = false; + if (calculate_inertia) { + // Recompute the inertia tensor. + Basis inertia_tensor; + inertia_tensor.set_zero(); + bool inertia_set = false; - for (int i = 0; i < get_shape_count(); i++) { - if (is_shape_disabled(i)) { - continue; - } + for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } - real_t area = get_shape_area(i); - if (area == 0.0) { - continue; - } + real_t area = get_shape_area(i); + if (area == 0.0) { + continue; + } + + inertia_set = true; - inertia_set = true; + const Shape3DSW *shape = get_shape(i); - const Shape3DSW *shape = get_shape(i); + real_t mass = area * this->mass / total_area; - real_t mass = area * this->mass / total_area; + Basis shape_inertia_tensor = shape->get_moment_of_inertia(mass).to_diagonal_matrix(); + Transform3D shape_transform = get_shape_transform(i); + Basis shape_basis = shape_transform.basis.orthonormalized(); - Basis shape_inertia_tensor = shape->get_moment_of_inertia(mass).to_diagonal_matrix(); - Transform3D shape_transform = get_shape_transform(i); - Basis shape_basis = shape_transform.basis.orthonormalized(); + // NOTE: we don't take the scale of collision shapes into account when computing the inertia tensor! + shape_inertia_tensor = shape_basis * shape_inertia_tensor * shape_basis.transposed(); - // NOTE: we don't take the scale of collision shapes into account when computing the inertia tensor! - shape_inertia_tensor = shape_basis * shape_inertia_tensor * shape_basis.transposed(); + Vector3 shape_origin = shape_transform.origin - center_of_mass_local; + inertia_tensor += shape_inertia_tensor + (Basis() * shape_origin.dot(shape_origin) - shape_origin.outer(shape_origin)) * mass; + } - Vector3 shape_origin = shape_transform.origin - center_of_mass_local; - inertia_tensor += shape_inertia_tensor + (Basis() * shape_origin.dot(shape_origin) - shape_origin.outer(shape_origin)) * mass; - } + // Set the inertia to a valid value when there are no valid shapes. + if (!inertia_set) { + inertia_tensor.set_diagonal(Vector3(1.0, 1.0, 1.0)); + } - // Set the inertia to a valid value when there are no valid shapes. - if (!inertia_set) { - inertia_tensor.set_diagonal(Vector3(1.0, 1.0, 1.0)); - } + // Handle partial custom inertia. + if (inertia.x > 0.0) { + inertia_tensor[0][0] = inertia.x; + } + if (inertia.y > 0.0) { + inertia_tensor[1][1] = inertia.y; + } + if (inertia.z > 0.0) { + inertia_tensor[2][2] = inertia.z; + } - // Compute the principal axes of inertia. - principal_inertia_axes_local = inertia_tensor.diagonalize().transposed(); - _inv_inertia = inertia_tensor.get_main_diagonal().inverse(); + // Compute the principal axes of inertia. + principal_inertia_axes_local = inertia_tensor.diagonalize().transposed(); + _inv_inertia = inertia_tensor.get_main_diagonal().inverse(); + } if (mass) { _inv_mass = 1.0 / mass; @@ -126,10 +149,9 @@ void Body3DSW::update_inertias() { } } break; - case PhysicsServer3D::BODY_MODE_KINEMATIC: case PhysicsServer3D::BODY_MODE_STATIC: { - _inv_inertia_tensor.set_zero(); + _inv_inertia = Vector3(); _inv_mass = 0; } break; case PhysicsServer3D::BODY_MODE_DYNAMIC_LOCKED: { @@ -139,11 +161,15 @@ void Body3DSW::update_inertias() { } break; } - //_update_shapes(); - _update_transform_dependant(); } +void Body3DSW::reset_mass_properties() { + calculate_inertia = true; + calculate_center_of_mass = true; + _mass_properties_changed(); +} + void Body3DSW::set_active(bool p_active) { if (active == p_active) { return; @@ -163,7 +189,7 @@ void Body3DSW::set_active(bool p_active) { } } -void Body3DSW::set_param(PhysicsServer3D::BodyParameter p_param, real_t p_value) { +void Body3DSW::set_param(PhysicsServer3D::BodyParameter p_param, const Variant &p_value) { switch (p_param) { case PhysicsServer3D::BODY_PARAM_BOUNCE: { bounce = p_value; @@ -172,10 +198,33 @@ void Body3DSW::set_param(PhysicsServer3D::BodyParameter p_param, real_t p_value) friction = p_value; } break; case PhysicsServer3D::BODY_PARAM_MASS: { - ERR_FAIL_COND(p_value <= 0); - mass = p_value; - _update_inertia(); - + real_t mass_value = p_value; + ERR_FAIL_COND(mass_value <= 0); + mass = mass_value; + if (mode >= PhysicsServer3D::BODY_MODE_DYNAMIC) { + _mass_properties_changed(); + } + } break; + case PhysicsServer3D::BODY_PARAM_INERTIA: { + inertia = p_value; + if ((inertia.x <= 0.0) || (inertia.y <= 0.0) || (inertia.z <= 0.0)) { + calculate_inertia = true; + if (mode == PhysicsServer3D::BODY_MODE_DYNAMIC) { + _mass_properties_changed(); + } + } else { + calculate_inertia = false; + if (mode == PhysicsServer3D::BODY_MODE_DYNAMIC) { + principal_inertia_axes_local.set_diagonal(Vector3(1.0, 1.0, 1.0)); + _inv_inertia = inertia.inverse(); + _update_transform_dependant(); + } + } + } break; + case PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS: { + calculate_center_of_mass = false; + center_of_mass_local = p_value; + _update_transform_dependant(); } break; case PhysicsServer3D::BODY_PARAM_GRAVITY_SCALE: { gravity_scale = p_value; @@ -191,7 +240,7 @@ void Body3DSW::set_param(PhysicsServer3D::BodyParameter p_param, real_t p_value) } } -real_t Body3DSW::get_param(PhysicsServer3D::BodyParameter p_param) const { +Variant Body3DSW::get_param(PhysicsServer3D::BodyParameter p_param) const { switch (p_param) { case PhysicsServer3D::BODY_PARAM_BOUNCE: { return bounce; @@ -202,6 +251,16 @@ real_t Body3DSW::get_param(PhysicsServer3D::BodyParameter p_param) const { case PhysicsServer3D::BODY_PARAM_MASS: { return mass; } break; + case PhysicsServer3D::BODY_PARAM_INERTIA: { + if (mode == PhysicsServer3D::BODY_MODE_DYNAMIC) { + return _inv_inertia.inverse(); + } else { + return Vector3(); + } + } break; + case PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS: { + return center_of_mass; + } break; case PhysicsServer3D::BODY_PARAM_GRAVITY_SCALE: { return gravity_scale; } break; @@ -224,40 +283,42 @@ void Body3DSW::set_mode(PhysicsServer3D::BodyMode p_mode) { mode = p_mode; switch (p_mode) { - //CLEAR UP EVERYTHING IN CASE IT NOT WORKS! case PhysicsServer3D::BODY_MODE_STATIC: case PhysicsServer3D::BODY_MODE_KINEMATIC: { _set_inv_transform(get_transform().affine_inverse()); _inv_mass = 0; + _inv_inertia = Vector3(); _set_static(p_mode == PhysicsServer3D::BODY_MODE_STATIC); - //set_active(p_mode==PhysicsServer3D::BODY_MODE_KINEMATIC); set_active(p_mode == PhysicsServer3D::BODY_MODE_KINEMATIC && contacts.size()); linear_velocity = Vector3(); angular_velocity = Vector3(); if (mode == PhysicsServer3D::BODY_MODE_KINEMATIC && prev != mode) { first_time_kinematic = true; } + _update_transform_dependant(); } break; case PhysicsServer3D::BODY_MODE_DYNAMIC: { _inv_mass = mass > 0 ? (1.0 / mass) : 0; + if (!calculate_inertia) { + principal_inertia_axes_local.set_diagonal(Vector3(1.0, 1.0, 1.0)); + _inv_inertia = inertia.inverse(); + _update_transform_dependant(); + } + _mass_properties_changed(); _set_static(false); set_active(true); } break; case PhysicsServer3D::BODY_MODE_DYNAMIC_LOCKED: { _inv_mass = mass > 0 ? (1.0 / mass) : 0; + _inv_inertia = Vector3(); + angular_velocity = Vector3(); + _update_transform_dependant(); _set_static(false); set_active(true); - angular_velocity = Vector3(); - } break; + } } - - _update_inertia(); - /* - if (get_space()) - _update_queries(); - */ } PhysicsServer3D::BodyMode Body3DSW::get_mode() const { @@ -265,7 +326,7 @@ PhysicsServer3D::BodyMode Body3DSW::get_mode() const { } void Body3DSW::_shapes_changed() { - _update_inertia(); + _mass_properties_changed(); } void Body3DSW::set_state(PhysicsServer3D::BodyState p_state, const Variant &p_variant) { @@ -300,10 +361,12 @@ void Body3DSW::set_state(PhysicsServer3D::BodyState p_state, const Variant &p_va } break; case PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY: { linear_velocity = p_variant; + constant_linear_velocity = linear_velocity; wakeup(); } break; case PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY: { angular_velocity = p_variant; + constant_angular_velocity = angular_velocity; wakeup(); } break; @@ -324,7 +387,7 @@ void Body3DSW::set_state(PhysicsServer3D::BodyState p_state, const Variant &p_va } break; case PhysicsServer3D::BODY_STATE_CAN_SLEEP: { can_sleep = p_variant; - if (mode == PhysicsServer3D::BODY_MODE_DYNAMIC && !active && !can_sleep) { + if (mode >= PhysicsServer3D::BODY_MODE_DYNAMIC && !active && !can_sleep) { set_active(true); } @@ -356,8 +419,8 @@ Variant Body3DSW::get_state(PhysicsServer3D::BodyState p_state) const { void Body3DSW::set_space(Space3DSW *p_space) { if (get_space()) { - if (inertia_update_list.in_list()) { - get_space()->body_remove_from_inertia_update_list(&inertia_update_list); + if (mass_properties_update_list.in_list()) { + get_space()->body_remove_from_mass_properties_update_list(&mass_properties_update_list); } if (active_list.in_list()) { get_space()->body_remove_from_active_list(&active_list); @@ -370,26 +433,17 @@ void Body3DSW::set_space(Space3DSW *p_space) { _set_space(p_space); if (get_space()) { - _update_inertia(); + _mass_properties_changed(); if (active) { get_space()->body_add_to_active_list(&active_list); } } - - first_integration = true; } -void Body3DSW::_compute_area_gravity_and_dampenings(const Area3DSW *p_area) { - if (p_area->is_gravity_point()) { - if (p_area->get_gravity_distance_scale() > 0) { - Vector3 v = p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin(); - gravity += v.normalized() * (p_area->get_gravity() / Math::pow(v.length() * p_area->get_gravity_distance_scale() + 1, 2)); - } else { - gravity += (p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin()).normalized() * p_area->get_gravity(); - } - } else { - gravity += p_area->get_gravity_vector() * p_area->get_gravity(); - } +void Body3DSW::_compute_area_gravity_and_damping(const Area3DSW *p_area) { + Vector3 area_gravity; + p_area->compute_gravity(get_transform().get_origin(), area_gravity); + gravity += area_gravity; area_linear_damp += p_area->get_linear_damp(); area_angular_damp += p_area->get_angular_damp(); @@ -431,7 +485,7 @@ void Body3DSW::integrate_forces(real_t p_step) { switch (mode) { case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE: case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: { - _compute_area_gravity_and_dampenings(aa[i].area); + _compute_area_gravity_and_damping(aa[i].area); stopped = mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE; } break; case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE: @@ -439,7 +493,7 @@ void Body3DSW::integrate_forces(real_t p_step) { gravity = Vector3(0, 0, 0); area_angular_damp = 0; area_linear_damp = 0; - _compute_area_gravity_and_dampenings(aa[i].area); + _compute_area_gravity_and_damping(aa[i].area); stopped = mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE; } break; default: { @@ -449,7 +503,7 @@ void Body3DSW::integrate_forces(real_t p_step) { } if (!stopped) { - _compute_area_gravity_and_dampenings(def_area); + _compute_area_gravity_and_damping(def_area); } gravity *= gravity_scale; @@ -478,7 +532,7 @@ void Body3DSW::integrate_forces(real_t p_step) { //compute motion, angular and etc. velocities from prev transform motion = new_transform.origin - get_transform().origin; do_motion = true; - linear_velocity = motion / p_step; + linear_velocity = constant_linear_velocity + motion / p_step; //compute a FAKE angular velocity, not so easy Basis rot = new_transform.basis.orthonormalized() * get_transform().basis.orthonormalized().transposed(); @@ -487,9 +541,9 @@ void Body3DSW::integrate_forces(real_t p_step) { rot.get_axis_angle(axis, angle); axis.normalize(); - angular_velocity = axis * (angle / p_step); + angular_velocity = constant_angular_velocity + axis * (angle / p_step); } else { - if (!omit_force_integration && !first_integration) { + if (!omit_force_integration) { //overridden by direct state query Vector3 force = gravity * mass; @@ -523,7 +577,6 @@ void Body3DSW::integrate_forces(real_t p_step) { applied_force = Vector3(); applied_torque = Vector3(); - first_integration = false; //motion=linear_velocity*p_step; @@ -543,7 +596,7 @@ void Body3DSW::integrate_velocities(real_t p_step) { return; } - if (fi_callback) { + if (fi_callback_data || body_state_callback) { get_space()->body_add_to_state_query_list(&direct_state_query_list); } @@ -600,11 +653,6 @@ void Body3DSW::integrate_velocities(real_t p_step) { _set_inv_transform(get_transform().inverse()); _update_transform_dependant(); - - /* - if (fi_callback) { - get_space()->body_add_to_state_query_list(&direct_state_query_list); - */ } /* @@ -650,7 +698,7 @@ void Body3DSW::wakeup_neighbours() { continue; } Body3DSW *b = n[i]; - if (b->mode != PhysicsServer3D::BODY_MODE_DYNAMIC) { + if (b->mode < PhysicsServer3D::BODY_MODE_DYNAMIC) { continue; } @@ -662,24 +710,23 @@ void Body3DSW::wakeup_neighbours() { } void Body3DSW::call_queries() { - if (fi_callback) { - PhysicsDirectBodyState3DSW *dbs = PhysicsDirectBodyState3DSW::singleton; - dbs->body = this; - - Variant v = dbs; - - Object *obj = fi_callback->callable.get_object(); - if (!obj) { + if (fi_callback_data) { + if (!fi_callback_data->callable.get_object()) { set_force_integration_callback(Callable()); } else { - const Variant *vp[2] = { &v, &fi_callback->udata }; + Variant direct_state_variant = get_direct_state(); + const Variant *vp[2] = { &direct_state_variant, &fi_callback_data->udata }; Callable::CallError ce; - int argc = (fi_callback->udata.get_type() == Variant::NIL) ? 1 : 2; + int argc = (fi_callback_data->udata.get_type() == Variant::NIL) ? 1 : 2; Variant rv; - fi_callback->callable.call(vp, argc, rv, ce); + fi_callback_data->callable.call(vp, argc, rv, ce); } } + + if (body_state_callback_instance) { + (body_state_callback)(body_state_callback_instance, get_direct_state()); + } } bool Body3DSW::sleep_test(real_t p_step) { @@ -699,60 +746,45 @@ bool Body3DSW::sleep_test(real_t p_step) { } } +void Body3DSW::set_state_sync_callback(void *p_instance, PhysicsServer3D::BodyStateCallback p_callback) { + body_state_callback_instance = p_instance; + body_state_callback = p_callback; +} + void Body3DSW::set_force_integration_callback(const Callable &p_callable, const Variant &p_udata) { - if (fi_callback) { - memdelete(fi_callback); - fi_callback = nullptr; + if (p_callable.get_object()) { + if (!fi_callback_data) { + fi_callback_data = memnew(ForceIntegrationCallbackData); + } + fi_callback_data->callable = p_callable; + fi_callback_data->udata = p_udata; + } else if (fi_callback_data) { + memdelete(fi_callback_data); + fi_callback_data = nullptr; } +} - if (p_callable.get_object()) { - fi_callback = memnew(ForceIntegrationCallback); - fi_callback->callable = p_callable; - fi_callback->udata = p_udata; +PhysicsDirectBodyState3DSW *Body3DSW::get_direct_state() { + if (!direct_state) { + direct_state = memnew(PhysicsDirectBodyState3DSW); + direct_state->body = this; } + return direct_state; } Body3DSW::Body3DSW() : CollisionObject3DSW(TYPE_BODY), - active_list(this), - inertia_update_list(this), + mass_properties_update_list(this), direct_state_query_list(this) { - mode = PhysicsServer3D::BODY_MODE_DYNAMIC; - active = true; - - mass = 1; - _inv_mass = 1; - bounce = 0; - friction = 1; - omit_force_integration = false; - //applied_torque=0; - island_step = 0; - first_time_kinematic = false; - first_integration = false; _set_static(false); - - contact_count = 0; - gravity_scale = 1.0; - linear_damp = -1; - angular_damp = -1; - area_angular_damp = 0; - area_linear_damp = 0; - - still_time = 0; - continuous_cd = false; - can_sleep = true; - fi_callback = nullptr; } Body3DSW::~Body3DSW() { - if (fi_callback) { - memdelete(fi_callback); + if (fi_callback_data) { + memdelete(fi_callback_data); + } + if (direct_state) { + memdelete(direct_state); } -} - -PhysicsDirectBodyState3DSW *PhysicsDirectBodyState3DSW::singleton = nullptr; - -PhysicsDirectSpaceState3D *PhysicsDirectBodyState3DSW::get_space_state() { - return body->get_space()->get_direct_state(); } diff --git a/servers/physics_3d/body_3d_sw.h b/servers/physics_3d/body_3d_sw.h index efb114a325..8b74c7e5b9 100644 --- a/servers/physics_3d/body_3d_sw.h +++ b/servers/physics_3d/body_3d_sw.h @@ -28,34 +28,39 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef BODY_SW_H -#define BODY_SW_H +#ifndef BODY_3D_SW_H +#define BODY_3D_SW_H #include "area_3d_sw.h" #include "collision_object_3d_sw.h" #include "core/templates/vset.h" class Constraint3DSW; +class PhysicsDirectBodyState3DSW; class Body3DSW : public CollisionObject3DSW { - PhysicsServer3D::BodyMode mode; + PhysicsServer3D::BodyMode mode = PhysicsServer3D::BODY_MODE_DYNAMIC; Vector3 linear_velocity; Vector3 angular_velocity; + Vector3 constant_linear_velocity; + Vector3 constant_angular_velocity; + Vector3 biased_linear_velocity; Vector3 biased_angular_velocity; - real_t mass; - real_t bounce; - real_t friction; + real_t mass = 1.0; + real_t bounce = 0.0; + real_t friction = 1.0; + Vector3 inertia; - real_t linear_damp; - real_t angular_damp; - real_t gravity_scale; + real_t linear_damp = -1.0; + real_t angular_damp = -1.0; + real_t gravity_scale = 1.0; uint16_t locked_axis = 0; - real_t _inv_mass; + real_t _inv_mass = 1.0; Vector3 _inv_inertia; // Relative to the principal axes of inertia // Relative to the local frame of reference @@ -67,30 +72,32 @@ class Body3DSW : public CollisionObject3DSW { Basis principal_inertia_axes; Vector3 center_of_mass; + bool calculate_inertia = true; + bool calculate_center_of_mass = true; + Vector3 gravity; - real_t still_time; + real_t still_time = 0.0; Vector3 applied_force; Vector3 applied_torque; - real_t area_angular_damp; - real_t area_linear_damp; + real_t area_angular_damp = 0.0; + real_t area_linear_damp = 0.0; SelfList<Body3DSW> active_list; - SelfList<Body3DSW> inertia_update_list; + SelfList<Body3DSW> mass_properties_update_list; SelfList<Body3DSW> direct_state_query_list; VSet<RID> exceptions; - bool omit_force_integration; - bool active; + bool omit_force_integration = false; + bool active = true; - bool first_integration; + bool continuous_cd = false; + bool can_sleep = true; + bool first_time_kinematic = false; - bool continuous_cd; - bool can_sleep; - bool first_time_kinematic; - void _update_inertia(); + void _mass_properties_changed(); virtual void _shapes_changed(); Transform3D new_transform; @@ -111,26 +118,34 @@ class Body3DSW : public CollisionObject3DSW { }; Vector<Contact> contacts; //no contacts by default - int contact_count; + int contact_count = 0; + + void *body_state_callback_instance = nullptr; + PhysicsServer3D::BodyStateCallback body_state_callback = nullptr; - struct ForceIntegrationCallback { + struct ForceIntegrationCallbackData { Callable callable; Variant udata; }; - ForceIntegrationCallback *fi_callback; + ForceIntegrationCallbackData *fi_callback_data = nullptr; - uint64_t island_step; + PhysicsDirectBodyState3DSW *direct_state = nullptr; - _FORCE_INLINE_ void _compute_area_gravity_and_dampenings(const Area3DSW *p_area); + uint64_t island_step = 0; + + _FORCE_INLINE_ void _compute_area_gravity_and_damping(const Area3DSW *p_area); _FORCE_INLINE_ void _update_transform_dependant(); friend class PhysicsDirectBodyState3DSW; // i give up, too many functions to expose public: + void set_state_sync_callback(void *p_instance, PhysicsServer3D::BodyStateCallback p_callback); void set_force_integration_callback(const Callable &p_callable, const Variant &p_udata = Variant()); + PhysicsDirectBodyState3DSW *get_direct_state(); + _FORCE_INLINE_ void add_area(Area3DSW *p_area) { int index = areas.find(AreaCMP(p_area)); if (index > -1) { @@ -242,8 +257,8 @@ public: set_active(true); } - void set_param(PhysicsServer3D::BodyParameter p_param, real_t); - real_t get_param(PhysicsServer3D::BodyParameter p_param) const; + void set_param(PhysicsServer3D::BodyParameter p_param, const Variant &p_value); + Variant get_param(PhysicsServer3D::BodyParameter p_param) const; void set_mode(PhysicsServer3D::BodyMode p_mode); PhysicsServer3D::BodyMode get_mode() const; @@ -262,7 +277,8 @@ public: void set_space(Space3DSW *p_space); - void update_inertias(); + void update_mass_properties(); + void reset_mass_properties(); _FORCE_INLINE_ real_t get_inv_mass() const { return _inv_mass; } _FORCE_INLINE_ const Vector3 &get_inv_inertia() const { return _inv_inertia; } @@ -349,96 +365,4 @@ void Body3DSW::add_contact(const Vector3 &p_local_pos, const Vector3 &p_local_no c[idx].collider_velocity_at_pos = p_collider_velocity_at_pos; } -class PhysicsDirectBodyState3DSW : public PhysicsDirectBodyState3D { - GDCLASS(PhysicsDirectBodyState3DSW, PhysicsDirectBodyState3D); - -public: - static PhysicsDirectBodyState3DSW *singleton; - Body3DSW *body; - real_t step; - - virtual Vector3 get_total_gravity() const override { return body->gravity; } // get gravity vector working on this body space/area - virtual real_t get_total_angular_damp() const override { return body->area_angular_damp; } // get density of this body space/area - virtual real_t get_total_linear_damp() const override { return body->area_linear_damp; } // get density of this body space/area - - virtual Vector3 get_center_of_mass() const override { return body->get_center_of_mass(); } - virtual Basis get_principal_inertia_axes() const override { return body->get_principal_inertia_axes(); } - - virtual real_t get_inverse_mass() const override { return body->get_inv_mass(); } // get the mass - virtual Vector3 get_inverse_inertia() const override { return body->get_inv_inertia(); } // get density of this body space - virtual Basis get_inverse_inertia_tensor() const override { return body->get_inv_inertia_tensor(); } // get density of this body space - - virtual void set_linear_velocity(const Vector3 &p_velocity) override { body->set_linear_velocity(p_velocity); } - virtual Vector3 get_linear_velocity() const override { return body->get_linear_velocity(); } - - virtual void set_angular_velocity(const Vector3 &p_velocity) override { body->set_angular_velocity(p_velocity); } - virtual Vector3 get_angular_velocity() const override { return body->get_angular_velocity(); } - - virtual void set_transform(const Transform3D &p_transform) override { body->set_state(PhysicsServer3D::BODY_STATE_TRANSFORM, p_transform); } - virtual Transform3D get_transform() const override { return body->get_transform(); } - - virtual Vector3 get_velocity_at_local_position(const Vector3 &p_position) const override { return body->get_velocity_in_local_point(p_position); } - - virtual void add_central_force(const Vector3 &p_force) override { body->add_central_force(p_force); } - virtual void add_force(const Vector3 &p_force, const Vector3 &p_position = Vector3()) override { - body->add_force(p_force, p_position); - } - virtual void add_torque(const Vector3 &p_torque) override { body->add_torque(p_torque); } - virtual void apply_central_impulse(const Vector3 &p_impulse) override { body->apply_central_impulse(p_impulse); } - virtual void apply_impulse(const Vector3 &p_impulse, const Vector3 &p_position = Vector3()) override { - body->apply_impulse(p_impulse, p_position); - } - virtual void apply_torque_impulse(const Vector3 &p_impulse) override { body->apply_torque_impulse(p_impulse); } - - virtual void set_sleep_state(bool p_sleep) override { body->set_active(!p_sleep); } - virtual bool is_sleeping() const override { return !body->is_active(); } - - virtual int get_contact_count() const override { return body->contact_count; } - - virtual Vector3 get_contact_local_position(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); - return body->contacts[p_contact_idx].local_pos; - } - virtual Vector3 get_contact_local_normal(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); - return body->contacts[p_contact_idx].local_normal; - } - virtual real_t get_contact_impulse(int p_contact_idx) const override { - return 0.0f; // Only implemented for bullet - } - virtual int get_contact_local_shape(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, -1); - return body->contacts[p_contact_idx].local_shape; - } - - virtual RID get_contact_collider(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, RID()); - return body->contacts[p_contact_idx].collider; - } - virtual Vector3 get_contact_collider_position(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); - return body->contacts[p_contact_idx].collider_pos; - } - virtual ObjectID get_contact_collider_id(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, ObjectID()); - return body->contacts[p_contact_idx].collider_instance_id; - } - virtual int get_contact_collider_shape(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, 0); - return body->contacts[p_contact_idx].collider_shape; - } - virtual Vector3 get_contact_collider_velocity_at_position(int p_contact_idx) const override { - ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); - return body->contacts[p_contact_idx].collider_velocity_at_pos; - } - - virtual PhysicsDirectSpaceState3D *get_space_state() override; - - virtual real_t get_step() const override { return step; } - PhysicsDirectBodyState3DSW() { - singleton = this; - body = nullptr; - } -}; - -#endif // BODY__SW_H +#endif // BODY_3D_SW_H diff --git a/servers/physics_3d/body_direct_state_3d_sw.cpp b/servers/physics_3d/body_direct_state_3d_sw.cpp new file mode 100644 index 0000000000..d197dd288d --- /dev/null +++ b/servers/physics_3d/body_direct_state_3d_sw.cpp @@ -0,0 +1,182 @@ +/*************************************************************************/ +/* body_direct_state_3d_sw.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 "body_direct_state_3d_sw.h" + +#include "body_3d_sw.h" +#include "space_3d_sw.h" + +Vector3 PhysicsDirectBodyState3DSW::get_total_gravity() const { + return body->gravity; +} + +real_t PhysicsDirectBodyState3DSW::get_total_angular_damp() const { + return body->area_angular_damp; +} + +real_t PhysicsDirectBodyState3DSW::get_total_linear_damp() const { + return body->area_linear_damp; +} + +Vector3 PhysicsDirectBodyState3DSW::get_center_of_mass() const { + return body->get_center_of_mass(); +} + +Basis PhysicsDirectBodyState3DSW::get_principal_inertia_axes() const { + return body->get_principal_inertia_axes(); +} + +real_t PhysicsDirectBodyState3DSW::get_inverse_mass() const { + return body->get_inv_mass(); +} + +Vector3 PhysicsDirectBodyState3DSW::get_inverse_inertia() const { + return body->get_inv_inertia(); +} + +Basis PhysicsDirectBodyState3DSW::get_inverse_inertia_tensor() const { + return body->get_inv_inertia_tensor(); +} + +void PhysicsDirectBodyState3DSW::set_linear_velocity(const Vector3 &p_velocity) { + body->set_linear_velocity(p_velocity); +} + +Vector3 PhysicsDirectBodyState3DSW::get_linear_velocity() const { + return body->get_linear_velocity(); +} + +void PhysicsDirectBodyState3DSW::set_angular_velocity(const Vector3 &p_velocity) { + body->set_angular_velocity(p_velocity); +} + +Vector3 PhysicsDirectBodyState3DSW::get_angular_velocity() const { + return body->get_angular_velocity(); +} + +void PhysicsDirectBodyState3DSW::set_transform(const Transform3D &p_transform) { + body->set_state(PhysicsServer3D::BODY_STATE_TRANSFORM, p_transform); +} + +Transform3D PhysicsDirectBodyState3DSW::get_transform() const { + return body->get_transform(); +} + +Vector3 PhysicsDirectBodyState3DSW::get_velocity_at_local_position(const Vector3 &p_position) const { + return body->get_velocity_in_local_point(p_position); +} + +void PhysicsDirectBodyState3DSW::add_central_force(const Vector3 &p_force) { + body->add_central_force(p_force); +} + +void PhysicsDirectBodyState3DSW::add_force(const Vector3 &p_force, const Vector3 &p_position) { + body->add_force(p_force, p_position); +} + +void PhysicsDirectBodyState3DSW::add_torque(const Vector3 &p_torque) { + body->add_torque(p_torque); +} + +void PhysicsDirectBodyState3DSW::apply_central_impulse(const Vector3 &p_impulse) { + body->apply_central_impulse(p_impulse); +} + +void PhysicsDirectBodyState3DSW::apply_impulse(const Vector3 &p_impulse, const Vector3 &p_position) { + body->apply_impulse(p_impulse, p_position); +} + +void PhysicsDirectBodyState3DSW::apply_torque_impulse(const Vector3 &p_impulse) { + body->apply_torque_impulse(p_impulse); +} + +void PhysicsDirectBodyState3DSW::set_sleep_state(bool p_sleep) { + body->set_active(!p_sleep); +} + +bool PhysicsDirectBodyState3DSW::is_sleeping() const { + return !body->is_active(); +} + +int PhysicsDirectBodyState3DSW::get_contact_count() const { + return body->contact_count; +} + +Vector3 PhysicsDirectBodyState3DSW::get_contact_local_position(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); + return body->contacts[p_contact_idx].local_pos; +} + +Vector3 PhysicsDirectBodyState3DSW::get_contact_local_normal(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); + return body->contacts[p_contact_idx].local_normal; +} + +real_t PhysicsDirectBodyState3DSW::get_contact_impulse(int p_contact_idx) const { + return 0.0f; // Only implemented for bullet +} + +int PhysicsDirectBodyState3DSW::get_contact_local_shape(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, -1); + return body->contacts[p_contact_idx].local_shape; +} + +RID PhysicsDirectBodyState3DSW::get_contact_collider(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, RID()); + return body->contacts[p_contact_idx].collider; +} + +Vector3 PhysicsDirectBodyState3DSW::get_contact_collider_position(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); + return body->contacts[p_contact_idx].collider_pos; +} + +ObjectID PhysicsDirectBodyState3DSW::get_contact_collider_id(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, ObjectID()); + return body->contacts[p_contact_idx].collider_instance_id; +} + +int PhysicsDirectBodyState3DSW::get_contact_collider_shape(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, 0); + return body->contacts[p_contact_idx].collider_shape; +} + +Vector3 PhysicsDirectBodyState3DSW::get_contact_collider_velocity_at_position(int p_contact_idx) const { + ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3()); + return body->contacts[p_contact_idx].collider_velocity_at_pos; +} + +PhysicsDirectSpaceState3D *PhysicsDirectBodyState3DSW::get_space_state() { + return body->get_space()->get_direct_state(); +} + +real_t PhysicsDirectBodyState3DSW::get_step() const { + return body->get_space()->get_last_step(); +} diff --git a/servers/physics_3d/body_direct_state_3d_sw.h b/servers/physics_3d/body_direct_state_3d_sw.h new file mode 100644 index 0000000000..5132376715 --- /dev/null +++ b/servers/physics_3d/body_direct_state_3d_sw.h @@ -0,0 +1,94 @@ +/*************************************************************************/ +/* body_direct_state_3d_sw.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 BODY_DIRECT_STATE_3D_SW_H +#define BODY_DIRECT_STATE_3D_SW_H + +#include "servers/physics_server_3d.h" + +class Body3DSW; + +class PhysicsDirectBodyState3DSW : public PhysicsDirectBodyState3D { + GDCLASS(PhysicsDirectBodyState3DSW, PhysicsDirectBodyState3D); + +public: + Body3DSW *body = nullptr; + + virtual Vector3 get_total_gravity() const override; + virtual real_t get_total_angular_damp() const override; + virtual real_t get_total_linear_damp() const override; + + virtual Vector3 get_center_of_mass() const override; + virtual Basis get_principal_inertia_axes() const override; + + virtual real_t get_inverse_mass() const override; + virtual Vector3 get_inverse_inertia() const override; + virtual Basis get_inverse_inertia_tensor() const override; + + virtual void set_linear_velocity(const Vector3 &p_velocity) override; + virtual Vector3 get_linear_velocity() const override; + + virtual void set_angular_velocity(const Vector3 &p_velocity) override; + virtual Vector3 get_angular_velocity() const override; + + virtual void set_transform(const Transform3D &p_transform) override; + virtual Transform3D get_transform() const override; + + virtual Vector3 get_velocity_at_local_position(const Vector3 &p_position) const override; + + virtual void add_central_force(const Vector3 &p_force) override; + virtual void add_force(const Vector3 &p_force, const Vector3 &p_position = Vector3()) override; + virtual void add_torque(const Vector3 &p_torque) override; + virtual void apply_central_impulse(const Vector3 &p_impulse) override; + virtual void apply_impulse(const Vector3 &p_impulse, const Vector3 &p_position = Vector3()) override; + virtual void apply_torque_impulse(const Vector3 &p_impulse) override; + + virtual void set_sleep_state(bool p_sleep) override; + virtual bool is_sleeping() const override; + + virtual int get_contact_count() const override; + + virtual Vector3 get_contact_local_position(int p_contact_idx) const override; + virtual Vector3 get_contact_local_normal(int p_contact_idx) const override; + virtual real_t get_contact_impulse(int p_contact_idx) const override; + virtual int get_contact_local_shape(int p_contact_idx) const override; + + virtual RID get_contact_collider(int p_contact_idx) const override; + virtual Vector3 get_contact_collider_position(int p_contact_idx) const override; + virtual ObjectID get_contact_collider_id(int p_contact_idx) const override; + virtual int get_contact_collider_shape(int p_contact_idx) const override; + virtual Vector3 get_contact_collider_velocity_at_position(int p_contact_idx) const override; + + virtual PhysicsDirectSpaceState3D *get_space_state() override; + + virtual real_t get_step() const override; +}; + +#endif // BODY_DIRECT_STATE_3D_SW_H diff --git a/servers/physics_3d/collision_solver_3d_sat.cpp b/servers/physics_3d/collision_solver_3d_sat.cpp index 6e6a2cb9e7..de81348b4e 100644 --- a/servers/physics_3d/collision_solver_3d_sat.cpp +++ b/servers/physics_3d/collision_solver_3d_sat.cpp @@ -2273,11 +2273,13 @@ bool sat_calculate_penetration(const Shape3DSW *p_shape_A, const Transform3D &p_ PhysicsServer3D::ShapeType type_A = p_shape_A->get_type(); ERR_FAIL_COND_V(type_A == PhysicsServer3D::SHAPE_PLANE, false); + ERR_FAIL_COND_V(type_A == PhysicsServer3D::SHAPE_SEPARATION_RAY, false); ERR_FAIL_COND_V(p_shape_A->is_concave(), false); PhysicsServer3D::ShapeType type_B = p_shape_B->get_type(); ERR_FAIL_COND_V(type_B == PhysicsServer3D::SHAPE_PLANE, false); + ERR_FAIL_COND_V(type_B == PhysicsServer3D::SHAPE_SEPARATION_RAY, false); ERR_FAIL_COND_V(p_shape_B->is_concave(), false); static const CollisionFunc collision_table[6][6] = { @@ -2382,10 +2384,10 @@ bool sat_calculate_penetration(const Shape3DSW *p_shape_A, const Transform3D &p_ CollisionFunc collision_func; if (margin_A != 0.0 || margin_B != 0.0) { - collision_func = collision_table_margin[type_A - 1][type_B - 1]; + collision_func = collision_table_margin[type_A - 2][type_B - 2]; } else { - collision_func = collision_table[type_A - 1][type_B - 1]; + collision_func = collision_table[type_A - 2][type_B - 2]; } ERR_FAIL_COND_V(!collision_func, false); diff --git a/servers/physics_3d/collision_solver_3d_sw.cpp b/servers/physics_3d/collision_solver_3d_sw.cpp index dcecac1c73..4a4a8164d3 100644 --- a/servers/physics_3d/collision_solver_3d_sw.cpp +++ b/servers/physics_3d/collision_solver_3d_sw.cpp @@ -89,6 +89,49 @@ bool CollisionSolver3DSW::solve_static_plane(const Shape3DSW *p_shape_A, const T return found; } +bool CollisionSolver3DSW::solve_separation_ray(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin) { + const SeparationRayShape3DSW *ray = static_cast<const SeparationRayShape3DSW *>(p_shape_A); + + Vector3 from = p_transform_A.origin; + Vector3 to = from + p_transform_A.basis.get_axis(2) * (ray->get_length() + p_margin); + Vector3 support_A = to; + + Transform3D ai = p_transform_B.affine_inverse(); + + from = ai.xform(from); + to = ai.xform(to); + + Vector3 p, n; + if (!p_shape_B->intersect_segment(from, to, p, n)) { + return false; + } + + // Discard contacts when the ray is fully contained inside the shape. + if (n == Vector3()) { + return false; + } + + // Discard contacts in the wrong direction. + if (n.dot(from - to) < CMP_EPSILON) { + return false; + } + + Vector3 support_B = p_transform_B.xform(p); + if (ray->get_slide_on_slope()) { + Vector3 global_n = ai.basis.xform_inv(n).normalized(); + support_B = support_A + (support_B - support_A).length() * global_n; + } + + if (p_result_callback) { + if (p_swap_result) { + p_result_callback(support_B, 0, support_A, 0, p_userdata); + } else { + p_result_callback(support_A, 0, support_B, 0, p_userdata); + } + } + return true; +} + struct _SoftBodyContactCollisionInfo { int node_index = 0; CollisionSolver3DSW::CallbackResult result_callback = nullptr; @@ -135,17 +178,17 @@ bool CollisionSolver3DSW::soft_body_query_callback(uint32_t p_node_index, void * transform_B.origin = query_cinfo.node_transform.xform(node_position); query_cinfo.contact_info.node_index = p_node_index; - solve_static(query_cinfo.shape_A, query_cinfo.transform_A, query_cinfo.shape_B, transform_B, soft_body_contact_callback, &query_cinfo.contact_info); + bool collided = solve_static(query_cinfo.shape_A, query_cinfo.transform_A, query_cinfo.shape_B, transform_B, soft_body_contact_callback, &query_cinfo.contact_info); #ifdef DEBUG_ENABLED ++query_cinfo.node_query_count; #endif - // Continue with the query. - return false; + // Stop at first collision if contacts are not needed. + return (collided && !query_cinfo.contact_info.result_callback); } -void CollisionSolver3DSW::soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex) { +bool CollisionSolver3DSW::soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex) { _SoftBodyQueryInfo &query_cinfo = *(_SoftBodyQueryInfo *)(p_userdata); query_cinfo.shape_A = p_convex; @@ -167,9 +210,14 @@ void CollisionSolver3DSW::soft_body_concave_callback(void *p_userdata, Shape3DSW query_cinfo.soft_body->query_aabb(shape_aabb, soft_body_query_callback, &query_cinfo); + bool collided = (query_cinfo.contact_info.contact_count > 0); + #ifdef DEBUG_ENABLED ++query_cinfo.convex_query_count; #endif + + // Stop at first collision if contacts are not needed. + return (collided && !query_cinfo.contact_info.result_callback); } bool CollisionSolver3DSW::solve_soft_body(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result) { @@ -243,17 +291,20 @@ struct _ConcaveCollisionInfo { Vector3 close_A, close_B; }; -void CollisionSolver3DSW::concave_callback(void *p_userdata, Shape3DSW *p_convex) { +bool CollisionSolver3DSW::concave_callback(void *p_userdata, Shape3DSW *p_convex) { _ConcaveCollisionInfo &cinfo = *(_ConcaveCollisionInfo *)(p_userdata); cinfo.aabb_tests++; bool collided = collision_solver(cinfo.shape_A, *cinfo.transform_A, p_convex, *cinfo.transform_B, cinfo.result_callback, cinfo.userdata, cinfo.swap_result, nullptr, cinfo.margin_A, cinfo.margin_B); if (!collided) { - return; + return false; } cinfo.collided = true; cinfo.collisions++; + + // Stop at first collision if contacts are not needed. + return !cinfo.result_callback; } bool CollisionSolver3DSW::solve_concave(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin_A, real_t p_margin_B) { @@ -318,6 +369,9 @@ bool CollisionSolver3DSW::solve_static(const Shape3DSW *p_shape_A, const Transfo if (type_B == PhysicsServer3D::SHAPE_PLANE) { return false; } + if (type_B == PhysicsServer3D::SHAPE_SEPARATION_RAY) { + return false; + } if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) { return false; } @@ -328,6 +382,17 @@ bool CollisionSolver3DSW::solve_static(const Shape3DSW *p_shape_A, const Transfo return solve_static_plane(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false); } + } else if (type_A == PhysicsServer3D::SHAPE_SEPARATION_RAY) { + if (type_B == PhysicsServer3D::SHAPE_SEPARATION_RAY) { + return false; + } + + if (swap) { + return solve_separation_ray(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, p_margin_B); + } else { + return solve_separation_ray(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, p_margin_A); + } + } else if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) { if (type_A == PhysicsServer3D::SHAPE_SOFT_BODY) { // Soft Body / Soft Body not supported. @@ -356,19 +421,18 @@ bool CollisionSolver3DSW::solve_static(const Shape3DSW *p_shape_A, const Transfo } } -void CollisionSolver3DSW::concave_distance_callback(void *p_userdata, Shape3DSW *p_convex) { +bool CollisionSolver3DSW::concave_distance_callback(void *p_userdata, Shape3DSW *p_convex) { _ConcaveCollisionInfo &cinfo = *(_ConcaveCollisionInfo *)(p_userdata); cinfo.aabb_tests++; - if (cinfo.collided) { - return; - } Vector3 close_A, close_B; cinfo.collided = !gjk_epa_calculate_distance(cinfo.shape_A, *cinfo.transform_A, p_convex, *cinfo.transform_B, close_A, close_B); if (cinfo.collided) { - return; + // No need to process any more result. + return true; } + if (!cinfo.tested || close_A.distance_squared_to(close_B) < cinfo.close_A.distance_squared_to(cinfo.close_B)) { cinfo.close_A = close_A; cinfo.close_B = close_B; @@ -376,6 +440,7 @@ void CollisionSolver3DSW::concave_distance_callback(void *p_userdata, Shape3DSW } cinfo.collisions++; + return false; } bool CollisionSolver3DSW::solve_distance_plane(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_point_A, Vector3 &r_point_B) { diff --git a/servers/physics_3d/collision_solver_3d_sw.h b/servers/physics_3d/collision_solver_3d_sw.h index a5dd7d48eb..c13614ab3e 100644 --- a/servers/physics_3d/collision_solver_3d_sw.h +++ b/servers/physics_3d/collision_solver_3d_sw.h @@ -40,13 +40,13 @@ public: private: static bool soft_body_query_callback(uint32_t p_node_index, void *p_userdata); static void soft_body_contact_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata); - static void soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex); - static void concave_callback(void *p_userdata, Shape3DSW *p_convex); + static bool soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex); + static bool concave_callback(void *p_userdata, Shape3DSW *p_convex); static bool solve_static_plane(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result); - static bool solve_ray(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result); + static bool solve_separation_ray(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin = 0); static bool solve_soft_body(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result); static bool solve_concave(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin_A = 0, real_t p_margin_B = 0); - static void concave_distance_callback(void *p_userdata, Shape3DSW *p_convex); + static bool concave_distance_callback(void *p_userdata, Shape3DSW *p_convex); static bool solve_distance_plane(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_point_A, Vector3 &r_point_B); public: diff --git a/servers/physics_3d/gjk_epa.cpp b/servers/physics_3d/gjk_epa.cpp index 2df991563d..f2f712193a 100644 --- a/servers/physics_3d/gjk_epa.cpp +++ b/servers/physics_3d/gjk_epa.cpp @@ -37,7 +37,7 @@ /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2008 Erwin Coumans http://continuousphysics.com/Bullet/ +Copyright (c) 2003-2008 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the diff --git a/servers/physics_3d/joints/generic_6dof_joint_3d_sw.cpp b/servers/physics_3d/joints/generic_6dof_joint_3d_sw.cpp index 56aba24b42..d2b64ce6e3 100644 --- a/servers/physics_3d/joints/generic_6dof_joint_3d_sw.cpp +++ b/servers/physics_3d/joints/generic_6dof_joint_3d_sw.cpp @@ -34,7 +34,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ +Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/generic_6dof_joint_3d_sw.h b/servers/physics_3d/joints/generic_6dof_joint_3d_sw.h index d0f3dbbd35..c2a0443aff 100644 --- a/servers/physics_3d/joints/generic_6dof_joint_3d_sw.h +++ b/servers/physics_3d/joints/generic_6dof_joint_3d_sw.h @@ -40,7 +40,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ +Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/hinge_joint_3d_sw.cpp b/servers/physics_3d/joints/hinge_joint_3d_sw.cpp index b928f18231..e2bf2845fe 100644 --- a/servers/physics_3d/joints/hinge_joint_3d_sw.cpp +++ b/servers/physics_3d/joints/hinge_joint_3d_sw.cpp @@ -34,7 +34,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ +Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/hinge_joint_3d_sw.h b/servers/physics_3d/joints/hinge_joint_3d_sw.h index 22eb2f4660..572c35266f 100644 --- a/servers/physics_3d/joints/hinge_joint_3d_sw.h +++ b/servers/physics_3d/joints/hinge_joint_3d_sw.h @@ -40,7 +40,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ +Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/jacobian_entry_3d_sw.h b/servers/physics_3d/joints/jacobian_entry_3d_sw.h index 6afa70c816..30c80db23f 100644 --- a/servers/physics_3d/joints/jacobian_entry_3d_sw.h +++ b/servers/physics_3d/joints/jacobian_entry_3d_sw.h @@ -37,7 +37,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ +Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/pin_joint_3d_sw.cpp b/servers/physics_3d/joints/pin_joint_3d_sw.cpp index 8eb84d1c2f..7a713c1161 100644 --- a/servers/physics_3d/joints/pin_joint_3d_sw.cpp +++ b/servers/physics_3d/joints/pin_joint_3d_sw.cpp @@ -34,7 +34,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ +Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/pin_joint_3d_sw.h b/servers/physics_3d/joints/pin_joint_3d_sw.h index 3d91452850..09deefc5c4 100644 --- a/servers/physics_3d/joints/pin_joint_3d_sw.h +++ b/servers/physics_3d/joints/pin_joint_3d_sw.h @@ -40,7 +40,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ +Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/slider_joint_3d_sw.cpp b/servers/physics_3d/joints/slider_joint_3d_sw.cpp index 1895fe1e2e..9f01196c30 100644 --- a/servers/physics_3d/joints/slider_joint_3d_sw.cpp +++ b/servers/physics_3d/joints/slider_joint_3d_sw.cpp @@ -34,7 +34,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ +Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/slider_joint_3d_sw.h b/servers/physics_3d/joints/slider_joint_3d_sw.h index f357bbd67a..f09476f570 100644 --- a/servers/physics_3d/joints/slider_joint_3d_sw.h +++ b/servers/physics_3d/joints/slider_joint_3d_sw.h @@ -40,7 +40,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ +Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/physics_server_3d_sw.cpp b/servers/physics_3d/physics_server_3d_sw.cpp index fbb374bd74..8de54c5c48 100644 --- a/servers/physics_3d/physics_server_3d_sw.cpp +++ b/servers/physics_3d/physics_server_3d_sw.cpp @@ -30,6 +30,7 @@ #include "physics_server_3d_sw.h" +#include "body_direct_state_3d_sw.h" #include "broad_phase_3d_bvh.h" #include "core/debugger/engine_debugger.h" #include "core/os/os.h" @@ -48,6 +49,12 @@ RID PhysicsServer3DSW::plane_shape_create() { shape->set_self(rid); return rid; } +RID PhysicsServer3DSW::separation_ray_shape_create() { + Shape3DSW *shape = memnew(SeparationRayShape3DSW); + RID rid = shape_owner.make_rid(shape); + shape->set_self(rid); + return rid; +} RID PhysicsServer3DSW::sphere_shape_create() { Shape3DSW *shape = memnew(SphereShape3DSW); RID rid = shape_owner.make_rid(shape); @@ -636,20 +643,27 @@ uint32_t PhysicsServer3DSW::body_get_user_flags(RID p_body) const { return 0; }; -void PhysicsServer3DSW::body_set_param(RID p_body, BodyParameter p_param, real_t p_value) { +void PhysicsServer3DSW::body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) { Body3DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND(!body); body->set_param(p_param, p_value); }; -real_t PhysicsServer3DSW::body_get_param(RID p_body, BodyParameter p_param) const { +Variant PhysicsServer3DSW::body_get_param(RID p_body, BodyParameter p_param) const { Body3DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, 0); return body->get_param(p_param); }; +void PhysicsServer3DSW::body_reset_mass_properties(RID p_body) { + Body3DSW *body = body_owner.getornull(p_body); + ERR_FAIL_COND(!body); + + return body->reset_mass_properties(); +} + void PhysicsServer3DSW::body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) { Body3DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND(!body); @@ -836,6 +850,12 @@ int PhysicsServer3DSW::body_get_max_contacts_reported(RID p_body) const { return body->get_max_contacts_reported(); } +void PhysicsServer3DSW::body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) { + Body3DSW *body = body_owner.getornull(p_body); + ERR_FAIL_COND(!body); + body->set_state_sync_callback(p_instance, p_callback); +} + void PhysicsServer3DSW::body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata) { Body3DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND(!body); @@ -848,7 +868,7 @@ void PhysicsServer3DSW::body_set_ray_pickable(RID p_body, bool p_enable) { body->set_ray_pickable(p_enable); } -bool PhysicsServer3DSW::body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, MotionResult *r_result, const Set<RID> &p_exclude) { +bool PhysicsServer3DSW::body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, MotionResult *r_result, bool p_collide_separation_ray, const Set<RID> &p_exclude) { Body3DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, false); ERR_FAIL_COND_V(!body->get_space(), false); @@ -856,18 +876,19 @@ bool PhysicsServer3DSW::body_test_motion(RID p_body, const Transform3D &p_from, _update_shapes(); - return body->get_space()->test_body_motion(body, p_from, p_motion, p_margin, r_result, p_exclude); + return body->get_space()->test_body_motion(body, p_from, p_motion, p_margin, r_result, p_collide_separation_ray, p_exclude); } PhysicsDirectBodyState3D *PhysicsServer3DSW::body_get_direct_state(RID p_body) { ERR_FAIL_COND_V_MSG((using_threads && !doing_sync), nullptr, "Body state is inaccessible right now, wait for iteration or physics process notification."); Body3DSW *body = body_owner.getornull(p_body); - ERR_FAIL_COND_V(!body, nullptr); + ERR_FAIL_NULL_V(body, nullptr); + + ERR_FAIL_NULL_V(body->get_space(), nullptr); ERR_FAIL_COND_V_MSG(body->get_space()->is_locked(), nullptr, "Body state is inaccessible right now, wait for iteration or physics process notification."); - direct_state->body = body; - return direct_state; + return body->get_direct_state(); } /* SOFT BODY */ @@ -1572,10 +1593,8 @@ void PhysicsServer3DSW::set_collision_iterations(int p_iterations) { }; void PhysicsServer3DSW::init() { - last_step = 0.001; iterations = 8; // 8? stepper = memnew(Step3DSW); - direct_state = memnew(PhysicsDirectBodyState3DSW); }; void PhysicsServer3DSW::step(real_t p_step) { @@ -1587,9 +1606,6 @@ void PhysicsServer3DSW::step(real_t p_step) { _update_shapes(); - last_step = p_step; - PhysicsDirectBodyState3DSW::singleton->step = p_step; - island_count = 0; active_objects = 0; collision_pairs = 0; @@ -1665,7 +1681,6 @@ void PhysicsServer3DSW::end_sync() { void PhysicsServer3DSW::finish() { memdelete(stepper); - memdelete(direct_state); }; int PhysicsServer3DSW::get_process_info(ProcessInfo p_info) { diff --git a/servers/physics_3d/physics_server_3d_sw.h b/servers/physics_3d/physics_server_3d_sw.h index 6e59a77e89..071ad0a694 100644 --- a/servers/physics_3d/physics_server_3d_sw.h +++ b/servers/physics_3d/physics_server_3d_sw.h @@ -44,7 +44,6 @@ class PhysicsServer3DSW : public PhysicsServer3D { friend class PhysicsDirectSpaceState3DSW; bool active; int iterations; - real_t last_step; int island_count; int active_objects; @@ -57,8 +56,6 @@ class PhysicsServer3DSW : public PhysicsServer3D { Step3DSW *stepper; Set<const Space3DSW *> active_spaces; - PhysicsDirectBodyState3DSW *direct_state; - mutable RID_PtrOwner<Shape3DSW, true> shape_owner; mutable RID_PtrOwner<Space3DSW, true> space_owner; mutable RID_PtrOwner<Area3DSW, true> area_owner; @@ -83,6 +80,7 @@ public: static void _shape_col_cbk(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata); virtual RID plane_shape_create() override; + virtual RID separation_ray_shape_create() override; virtual RID sphere_shape_create() override; virtual RID box_shape_create() override; virtual RID capsule_shape_create() override; @@ -200,8 +198,10 @@ public: virtual void body_set_user_flags(RID p_body, uint32_t p_flags) override; virtual uint32_t body_get_user_flags(RID p_body) const override; - virtual void body_set_param(RID p_body, BodyParameter p_param, real_t p_value) override; - virtual real_t body_get_param(RID p_body, BodyParameter p_param) const override; + virtual void body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) override; + virtual Variant body_get_param(RID p_body, BodyParameter p_param) const override; + + virtual void body_reset_mass_properties(RID p_body) override; virtual void body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) override; virtual Variant body_get_state(RID p_body, BodyState p_state) const override; @@ -237,11 +237,12 @@ public: virtual void body_set_max_contacts_reported(RID p_body, int p_contacts) override; virtual int body_get_max_contacts_reported(RID p_body) const override; + virtual void body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) override; virtual void body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata = Variant()) override; virtual void body_set_ray_pickable(RID p_body, bool p_enable) override; - virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) override; + virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) override; // this function only works on physics process, errors and returns null otherwise virtual PhysicsDirectBodyState3D *body_get_direct_state(RID p_body) override; diff --git a/servers/physics_3d/physics_server_3d_wrap_mt.h b/servers/physics_3d/physics_server_3d_wrap_mt.h index 75174628bf..58986969d4 100644 --- a/servers/physics_3d/physics_server_3d_wrap_mt.h +++ b/servers/physics_3d/physics_server_3d_wrap_mt.h @@ -79,6 +79,7 @@ public: //FUNC1RID(shape,ShapeType); todo fix FUNCRID(plane_shape) + FUNCRID(separation_ray_shape) FUNCRID(sphere_shape) FUNCRID(box_shape) FUNCRID(capsule_shape) @@ -209,8 +210,10 @@ public: FUNC2(body_set_user_flags, RID, uint32_t); FUNC1RC(uint32_t, body_get_user_flags, RID); - FUNC3(body_set_param, RID, BodyParameter, real_t); - FUNC2RC(real_t, body_get_param, RID, BodyParameter); + FUNC3(body_set_param, RID, BodyParameter, const Variant &); + FUNC2RC(Variant, body_get_param, RID, BodyParameter); + + FUNC1(body_reset_mass_properties, RID); FUNC3(body_set_state, RID, BodyState, const Variant &); FUNC2RC(Variant, body_get_state, RID, BodyState); @@ -245,13 +248,14 @@ public: FUNC2(body_set_omit_force_integration, RID, bool); FUNC1RC(bool, body_is_omitting_force_integration, RID); + FUNC3(body_set_state_sync_callback, RID, void *, BodyStateCallback); FUNC3(body_set_force_integration_callback, RID, const Callable &, const Variant &); FUNC2(body_set_ray_pickable, RID, bool); - bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) override { + bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) override { ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false); - return physics_3d_server->body_test_motion(p_body, p_from, p_motion, p_margin, r_result, p_exclude); + return physics_3d_server->body_test_motion(p_body, p_from, p_motion, p_margin, r_result, p_collide_separation_ray, p_exclude); } // this function only works on physics process, errors and returns null otherwise diff --git a/servers/physics_3d/shape_3d_sw.cpp b/servers/physics_3d/shape_3d_sw.cpp index a84405de81..945d0120be 100644 --- a/servers/physics_3d/shape_3d_sw.cpp +++ b/servers/physics_3d/shape_3d_sw.cpp @@ -39,7 +39,7 @@ /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org +Copyright (c) 2003-2009 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. @@ -164,6 +164,91 @@ Variant PlaneShape3DSW::get_data() const { PlaneShape3DSW::PlaneShape3DSW() { } +// + +real_t SeparationRayShape3DSW::get_length() const { + return length; +} + +bool SeparationRayShape3DSW::get_slide_on_slope() const { + return slide_on_slope; +} + +void SeparationRayShape3DSW::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + // don't think this will be even used + r_min = 0; + r_max = 1; +} + +Vector3 SeparationRayShape3DSW::get_support(const Vector3 &p_normal) const { + if (p_normal.z > 0) { + return Vector3(0, 0, length); + } else { + return Vector3(0, 0, 0); + } +} + +void SeparationRayShape3DSW::get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { + if (Math::abs(p_normal.z) < _EDGE_IS_VALID_SUPPORT_THRESHOLD) { + r_amount = 2; + r_type = FEATURE_EDGE; + r_supports[0] = Vector3(0, 0, 0); + r_supports[1] = Vector3(0, 0, length); + } else if (p_normal.z > 0) { + r_amount = 1; + r_type = FEATURE_POINT; + *r_supports = Vector3(0, 0, length); + } else { + r_amount = 1; + r_type = FEATURE_POINT; + *r_supports = Vector3(0, 0, 0); + } +} + +bool SeparationRayShape3DSW::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal) const { + return false; //simply not possible +} + +bool SeparationRayShape3DSW::intersect_point(const Vector3 &p_point) const { + return false; //simply not possible +} + +Vector3 SeparationRayShape3DSW::get_closest_point_to(const Vector3 &p_point) const { + Vector3 s[2] = { + Vector3(0, 0, 0), + Vector3(0, 0, length) + }; + + return Geometry3D::get_closest_point_to_segment(p_point, s); +} + +Vector3 SeparationRayShape3DSW::get_moment_of_inertia(real_t p_mass) const { + return Vector3(); +} + +void SeparationRayShape3DSW::_setup(real_t p_length, bool p_slide_on_slope) { + length = p_length; + slide_on_slope = p_slide_on_slope; + configure(AABB(Vector3(0, 0, 0), Vector3(0.1, 0.1, length))); +} + +void SeparationRayShape3DSW::set_data(const Variant &p_data) { + Dictionary d = p_data; + _setup(d["length"], d["slide_on_slope"]); +} + +Variant SeparationRayShape3DSW::get_data() const { + Dictionary d; + d["length"] = length; + d["slide_on_slope"] = slide_on_slope; + return d; +} + +SeparationRayShape3DSW::SeparationRayShape3DSW() { + length = 1; + slide_on_slope = false; +} + /********** SPHERE *************/ real_t SphereShape3DSW::get_radius() const { @@ -1297,11 +1382,11 @@ Vector3 ConcavePolygonShape3DSW::get_closest_point_to(const Vector3 &p_point) co return Vector3(); } -void ConcavePolygonShape3DSW::_cull(int p_idx, _CullParams *p_params) const { +bool ConcavePolygonShape3DSW::_cull(int p_idx, _CullParams *p_params) const { const BVH *bvh = &p_params->bvh[p_idx]; if (!p_params->aabb.intersects(bvh->aabb)) { - return; + return false; } if (bvh->face_index >= 0) { @@ -1311,20 +1396,27 @@ void ConcavePolygonShape3DSW::_cull(int p_idx, _CullParams *p_params) const { face->vertex[0] = p_params->vertices[f->indices[0]]; face->vertex[1] = p_params->vertices[f->indices[1]]; face->vertex[2] = p_params->vertices[f->indices[2]]; - p_params->callback(p_params->userdata, face); - + if (p_params->callback(p_params->userdata, face)) { + return true; + } } else { if (bvh->left >= 0) { - _cull(bvh->left, p_params); + if (_cull(bvh->left, p_params)) { + return true; + } } if (bvh->right >= 0) { - _cull(bvh->right, p_params); + if (_cull(bvh->right, p_params)) { + return true; + } } } + + return false; } -void ConcavePolygonShape3DSW::cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const { +void ConcavePolygonShape3DSW::cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata) const { // make matrix local to concave if (faces.size() == 0) { return; @@ -1584,6 +1676,17 @@ struct _HeightmapSegmentCullParams { FaceShape3DSW *face = nullptr; }; +struct _HeightmapGridCullState { + real_t length = 0.0; + real_t length_flat = 0.0; + + real_t dist = 0.0; + real_t prev_dist = 0.0; + + int x = 0; + int z = 0; +}; + _FORCE_INLINE_ bool _heightmap_face_cull_segment(_HeightmapSegmentCullParams &p_params) { Vector3 res; Vector3 normal; @@ -1596,11 +1699,11 @@ _FORCE_INLINE_ bool _heightmap_face_cull_segment(_HeightmapSegmentCullParams &p_ return false; } -_FORCE_INLINE_ bool _heightmap_cell_cull_segment(_HeightmapSegmentCullParams &p_params, int p_x, int p_z) { +_FORCE_INLINE_ bool _heightmap_cell_cull_segment(_HeightmapSegmentCullParams &p_params, const _HeightmapGridCullState &p_state) { // First triangle. - p_params.heightmap->_get_point(p_x, p_z, p_params.face->vertex[0]); - p_params.heightmap->_get_point(p_x + 1, p_z, p_params.face->vertex[1]); - p_params.heightmap->_get_point(p_x, p_z + 1, p_params.face->vertex[2]); + p_params.heightmap->_get_point(p_state.x, p_state.z, p_params.face->vertex[0]); + p_params.heightmap->_get_point(p_state.x + 1, p_state.z, p_params.face->vertex[1]); + p_params.heightmap->_get_point(p_state.x, p_state.z + 1, p_params.face->vertex[2]); p_params.face->normal = Plane(p_params.face->vertex[0], p_params.face->vertex[1], p_params.face->vertex[2]).normal; if (_heightmap_face_cull_segment(p_params)) { return true; @@ -1608,7 +1711,7 @@ _FORCE_INLINE_ bool _heightmap_cell_cull_segment(_HeightmapSegmentCullParams &p_ // Second triangle. p_params.face->vertex[0] = p_params.face->vertex[1]; - p_params.heightmap->_get_point(p_x + 1, p_z + 1, p_params.face->vertex[1]); + p_params.heightmap->_get_point(p_state.x + 1, p_state.z + 1, p_params.face->vertex[1]); p_params.face->normal = Plane(p_params.face->vertex[0], p_params.face->vertex[1], p_params.face->vertex[2]).normal; if (_heightmap_face_cull_segment(p_params)) { return true; @@ -1617,13 +1720,51 @@ _FORCE_INLINE_ bool _heightmap_cell_cull_segment(_HeightmapSegmentCullParams &p_ return false; } -bool HeightMapShape3DSW::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal) const { - if (heights.is_empty()) { +_FORCE_INLINE_ bool _heightmap_chunk_cull_segment(_HeightmapSegmentCullParams &p_params, const _HeightmapGridCullState &p_state) { + const HeightMapShape3DSW::Range &chunk = p_params.heightmap->_get_bounds_chunk(p_state.x, p_state.z); + + Vector3 enter_pos; + Vector3 exit_pos; + + if (p_state.length_flat > CMP_EPSILON) { + real_t flat_to_3d = p_state.length / p_state.length_flat; + real_t enter_param = p_state.prev_dist * flat_to_3d; + real_t exit_param = p_state.dist * flat_to_3d; + enter_pos = p_params.from + p_params.dir * enter_param; + exit_pos = p_params.from + p_params.dir * exit_param; + } else { + // Consider the ray vertical. + // (though we shouldn't reach this often because there is an early check up-front) + enter_pos = p_params.from; + exit_pos = p_params.to; + } + + // Transform positions to heightmap space. + enter_pos *= HeightMapShape3DSW::BOUNDS_CHUNK_SIZE; + exit_pos *= HeightMapShape3DSW::BOUNDS_CHUNK_SIZE; + + // We did enter the flat projection of the AABB, + // but we have to check if we intersect it on the vertical axis. + if ((enter_pos.y > chunk.max) && (exit_pos.y > chunk.max)) { + return false; + } + if ((enter_pos.y < chunk.min) && (exit_pos.y < chunk.min)) { return false; } - Vector3 local_begin = p_begin + local_origin; - Vector3 local_end = p_end + local_origin; + return p_params.heightmap->_intersect_grid_segment(_heightmap_cell_cull_segment, enter_pos, exit_pos, p_params.heightmap->width, p_params.heightmap->depth, p_params.heightmap->local_origin, p_params.result, p_params.normal); +} + +template <typename ProcessFunction> +bool HeightMapShape3DSW::_intersect_grid_segment(ProcessFunction &p_process, const Vector3 &p_begin, const Vector3 &p_end, int p_width, int p_depth, const Vector3 &offset, Vector3 &r_point, Vector3 &r_normal) const { + Vector3 delta = (p_end - p_begin); + real_t length = delta.length(); + + if (length < CMP_EPSILON) { + return false; + } + + Vector3 local_begin = p_begin + offset; FaceShape3DSW face; face.backface_collision = false; @@ -1631,136 +1772,181 @@ bool HeightMapShape3DSW::intersect_segment(const Vector3 &p_begin, const Vector3 _HeightmapSegmentCullParams params; params.from = p_begin; params.to = p_end; - params.dir = (p_end - p_begin).normalized(); + params.dir = delta / length; params.heightmap = this; params.face = &face; - // Quantize the ray begin/end. - int begin_x = floor(local_begin.x); - int begin_z = floor(local_begin.z); - int end_x = floor(local_end.x); - int end_z = floor(local_end.z); + _HeightmapGridCullState state; - if ((begin_x == end_x) && (begin_z == end_z)) { - // Simple case for rays that don't traverse the grid horizontally. - // Just perform a test on the given cell. - int x = CLAMP(begin_x, 0, width - 2); - int z = CLAMP(begin_z, 0, depth - 2); - if (_heightmap_cell_cull_segment(params, x, z)) { - r_point = params.result; - r_normal = params.normal; - return true; - } - } else { - // Perform grid query from projected ray. - Vector2 ray_dir_proj(local_end.x - local_begin.x, local_end.z - local_begin.z); - real_t ray_dist_proj = ray_dir_proj.length(); + // Perform grid query from projected ray. + Vector2 ray_dir_flat(delta.x, delta.z); + state.length = length; + state.length_flat = ray_dir_flat.length(); - if (ray_dist_proj < CMP_EPSILON) { - ray_dir_proj = Vector2(); - } else { - ray_dir_proj /= ray_dist_proj; - } + if (state.length_flat < CMP_EPSILON) { + ray_dir_flat = Vector2(); + } else { + ray_dir_flat /= state.length_flat; + } - const int x_step = (ray_dir_proj.x > CMP_EPSILON) ? 1 : ((ray_dir_proj.x < -CMP_EPSILON) ? -1 : 0); - const int z_step = (ray_dir_proj.y > CMP_EPSILON) ? 1 : ((ray_dir_proj.y < -CMP_EPSILON) ? -1 : 0); + const int x_step = (ray_dir_flat.x > CMP_EPSILON) ? 1 : ((ray_dir_flat.x < -CMP_EPSILON) ? -1 : 0); + const int z_step = (ray_dir_flat.y > CMP_EPSILON) ? 1 : ((ray_dir_flat.y < -CMP_EPSILON) ? -1 : 0); - const real_t infinite = 1e20; - const real_t delta_x = (x_step != 0) ? 1.f / Math::abs(ray_dir_proj.x) : infinite; - const real_t delta_z = (z_step != 0) ? 1.f / Math::abs(ray_dir_proj.y) : infinite; + const real_t infinite = 1e20; + const real_t delta_x = (x_step != 0) ? 1.f / Math::abs(ray_dir_flat.x) : infinite; + const real_t delta_z = (z_step != 0) ? 1.f / Math::abs(ray_dir_flat.y) : infinite; - real_t cross_x; // At which value of `param` we will cross a x-axis lane? - real_t cross_z; // At which value of `param` we will cross a z-axis lane? + real_t cross_x; // At which value of `param` we will cross a x-axis lane? + real_t cross_z; // At which value of `param` we will cross a z-axis lane? - // X initialization. - if (x_step != 0) { - if (x_step == 1) { - cross_x = (ceil(local_begin.x) - local_begin.x) * delta_x; - } else { - cross_x = (local_begin.x - floor(local_begin.x)) * delta_x; - } + // X initialization. + if (x_step != 0) { + if (x_step == 1) { + cross_x = (Math::ceil(local_begin.x) - local_begin.x) * delta_x; } else { - cross_x = infinite; // Will never cross on X. + cross_x = (local_begin.x - Math::floor(local_begin.x)) * delta_x; } + } else { + cross_x = infinite; // Will never cross on X. + } - // Z initialization. - if (z_step != 0) { - if (z_step == 1) { - cross_z = (ceil(local_begin.z) - local_begin.z) * delta_z; - } else { - cross_z = (local_begin.z - floor(local_begin.z)) * delta_z; - } + // Z initialization. + if (z_step != 0) { + if (z_step == 1) { + cross_z = (Math::ceil(local_begin.z) - local_begin.z) * delta_z; } else { - cross_z = infinite; // Will never cross on Z. + cross_z = (local_begin.z - Math::floor(local_begin.z)) * delta_z; } + } else { + cross_z = infinite; // Will never cross on Z. + } - int x = floor(local_begin.x); - int z = floor(local_begin.z); + int x = Math::floor(local_begin.x); + int z = Math::floor(local_begin.z); - // Workaround cases where the ray starts at an integer position. - if (Math::is_zero_approx(cross_x)) { - cross_x += delta_x; - // If going backwards, we should ignore the position we would get by the above flooring, - // because the ray is not heading in that direction. - if (x_step == -1) { - x -= 1; - } + // Workaround cases where the ray starts at an integer position. + if (Math::is_zero_approx(cross_x)) { + cross_x += delta_x; + // If going backwards, we should ignore the position we would get by the above flooring, + // because the ray is not heading in that direction. + if (x_step == -1) { + x -= 1; } + } - if (Math::is_zero_approx(cross_z)) { - cross_z += delta_z; - if (z_step == -1) { - z -= 1; - } + if (Math::is_zero_approx(cross_z)) { + cross_z += delta_z; + if (z_step == -1) { + z -= 1; } + } + + // Start inside the grid. + int x_start = MAX(MIN(x, p_width - 2), 0); + int z_start = MAX(MIN(z, p_depth - 2), 0); - // Start inside the grid. - int x_start = CLAMP(x, 0, width - 2); - int z_start = CLAMP(z, 0, depth - 2); + // Adjust initial cross values. + cross_x += delta_x * x_step * (x_start - x); + cross_z += delta_z * z_step * (z_start - z); - // Adjust initial cross values. - cross_x += delta_x * x_step * (x_start - x); - cross_z += delta_z * z_step * (z_start - z); + x = x_start; + z = z_start; - x = x_start; - z = z_start; + while (true) { + state.prev_dist = state.dist; + state.x = x; + state.z = z; - if (_heightmap_cell_cull_segment(params, x, z)) { + if (cross_x < cross_z) { + // X lane. + x += x_step; + // Assign before advancing the param, + // to be in sync with the initialization step. + state.dist = cross_x; + cross_x += delta_x; + } else { + // Z lane. + z += z_step; + state.dist = cross_z; + cross_z += delta_z; + } + + if (state.dist > state.length_flat) { + state.dist = state.length_flat; + if (p_process(params, state)) { + r_point = params.result; + r_normal = params.normal; + return true; + } + break; + } + + if (p_process(params, state)) { r_point = params.result; r_normal = params.normal; return true; } - real_t dist = 0.0; - while (true) { - if (cross_x < cross_z) { - // X lane. - x += x_step; - // Assign before advancing the param, - // to be in sync with the initialization step. - dist = cross_x; - cross_x += delta_x; - } else { - // Z lane. - z += z_step; - dist = cross_z; - cross_z += delta_z; - } + // Stop when outside the grid. + if ((x < 0) || (z < 0) || (x >= p_width - 1) || (z >= p_depth - 1)) { + break; + } + } - // Stop when outside the grid. - if ((x < 0) || (z < 0) || (x >= width - 1) || (z >= depth - 1)) { - break; - } + return false; +} - if (_heightmap_cell_cull_segment(params, x, z)) { - r_point = params.result; - r_normal = params.normal; - return true; - } +bool HeightMapShape3DSW::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal) const { + if (heights.is_empty()) { + return false; + } - if (dist > ray_dist_proj) { - break; - } + Vector3 local_begin = p_begin + local_origin; + Vector3 local_end = p_end + local_origin; + + // Quantize the ray begin/end. + int begin_x = Math::floor(local_begin.x); + int begin_z = Math::floor(local_begin.z); + int end_x = Math::floor(local_end.x); + int end_z = Math::floor(local_end.z); + + if ((begin_x == end_x) && (begin_z == end_z)) { + // Simple case for rays that don't traverse the grid horizontally. + // Just perform a test on the given cell. + FaceShape3DSW face; + face.backface_collision = false; + + _HeightmapSegmentCullParams params; + params.from = p_begin; + params.to = p_end; + params.dir = (p_end - p_begin).normalized(); + + params.heightmap = this; + params.face = &face; + + _HeightmapGridCullState state; + state.x = MAX(MIN(begin_x, width - 2), 0); + state.z = MAX(MIN(begin_z, depth - 2), 0); + if (_heightmap_cell_cull_segment(params, state)) { + r_point = params.result; + r_normal = params.normal; + return true; + } + } else if (bounds_grid.is_empty()) { + // Process all cells intersecting the flat projection of the ray. + return _intersect_grid_segment(_heightmap_cell_cull_segment, p_begin, p_end, width, depth, local_origin, r_point, r_normal); + } else { + Vector3 ray_diff = (p_end - p_begin); + real_t length_flat_sqr = ray_diff.x * ray_diff.x + ray_diff.z * ray_diff.z; + if (length_flat_sqr < BOUNDS_CHUNK_SIZE * BOUNDS_CHUNK_SIZE) { + // Don't use chunks, the ray is too short in the plane. + return _intersect_grid_segment(_heightmap_cell_cull_segment, p_begin, p_end, width, depth, local_origin, r_point, r_normal); + } else { + // The ray is long, run raycast on a higher-level grid. + Vector3 bounds_from = p_begin / BOUNDS_CHUNK_SIZE; + Vector3 bounds_to = p_end / BOUNDS_CHUNK_SIZE; + Vector3 bounds_offset = local_origin / BOUNDS_CHUNK_SIZE; + return _intersect_grid_segment(_heightmap_chunk_cull_segment, bounds_from, bounds_to, bounds_grid_width, bounds_grid_depth, bounds_offset, r_point, r_normal); } } @@ -1790,7 +1976,7 @@ void HeightMapShape3DSW::_get_cell(const Vector3 &p_point, int &r_x, int &r_y, i r_z = (clamped_point.z < 0.0) ? (clamped_point.z - 0.5) : (clamped_point.z + 0.5); } -void HeightMapShape3DSW::cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const { +void HeightMapShape3DSW::cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata) const { if (heights.is_empty()) { return; } @@ -1825,14 +2011,18 @@ void HeightMapShape3DSW::cull(const AABB &p_local_aabb, Callback p_callback, voi _get_point(x, z, face.vertex[0]); _get_point(x + 1, z, face.vertex[1]); _get_point(x, z + 1, face.vertex[2]); - face.normal = Plane(face.vertex[0], face.vertex[2], face.vertex[1]).normal; - p_callback(p_userdata, &face); + face.normal = Plane(face.vertex[0], face.vertex[1], face.vertex[2]).normal; + if (p_callback(p_userdata, &face)) { + return; + } // Second triangle. face.vertex[0] = face.vertex[1]; _get_point(x + 1, z + 1, face.vertex[1]); - face.normal = Plane(face.vertex[0], face.vertex[2], face.vertex[1]).normal; - p_callback(p_userdata, &face); + face.normal = Plane(face.vertex[0], face.vertex[1], face.vertex[2]).normal; + if (p_callback(p_userdata, &face)) { + return; + } } } } @@ -1847,6 +2037,75 @@ Vector3 HeightMapShape3DSW::get_moment_of_inertia(real_t p_mass) const { (p_mass / 3.0) * (extents.x * extents.x + extents.y * extents.y)); } +void HeightMapShape3DSW::_build_accelerator() { + bounds_grid.clear(); + + bounds_grid_width = width / BOUNDS_CHUNK_SIZE; + bounds_grid_depth = depth / BOUNDS_CHUNK_SIZE; + + if (width % BOUNDS_CHUNK_SIZE > 0) { + ++bounds_grid_width; // In case terrain size isn't dividable by chunk size. + } + + if (depth % BOUNDS_CHUNK_SIZE > 0) { + ++bounds_grid_depth; + } + + uint32_t bound_grid_size = (uint32_t)(bounds_grid_width * bounds_grid_depth); + + if (bound_grid_size < 2) { + // Grid is empty or just one chunk. + return; + } + + bounds_grid.resize(bound_grid_size); + + // Compute min and max height for all chunks. + for (int cz = 0; cz < bounds_grid_depth; ++cz) { + int z0 = cz * BOUNDS_CHUNK_SIZE; + + for (int cx = 0; cx < bounds_grid_width; ++cx) { + int x0 = cx * BOUNDS_CHUNK_SIZE; + + Range r; + + r.min = _get_height(x0, z0); + r.max = r.min; + + // Compute min and max height for this chunk. + // We have to include one extra cell to account for neighbors. + // Here is why: + // Say we have a flat terrain, and a plateau that fits a chunk perfectly. + // + // Left Right + // 0---0---0---1---1---1 + // | | | | | | + // 0---0---0---1---1---1 + // | | | | | | + // 0---0---0---1---1---1 + // x + // + // If the AABB for the Left chunk did not share vertices with the Right, + // then we would fail collision tests at x due to a gap. + // + int z_max = MIN(z0 + BOUNDS_CHUNK_SIZE + 1, depth); + int x_max = MIN(x0 + BOUNDS_CHUNK_SIZE + 1, width); + for (int z = z0; z < z_max; ++z) { + for (int x = x0; x < x_max; ++x) { + real_t height = _get_height(x, z); + if (height < r.min) { + r.min = height; + } else if (height > r.max) { + r.max = height; + } + } + } + + bounds_grid[cx + cz * bounds_grid_width] = r; + } + } +} + void HeightMapShape3DSW::_setup(const Vector<real_t> &p_heights, int p_width, int p_depth, real_t p_min_height, real_t p_max_height) { heights = p_heights; width = p_width; @@ -1863,6 +2122,8 @@ void HeightMapShape3DSW::_setup(const Vector<real_t> &p_heights, int p_width, in aabb.position -= local_origin; + _build_accelerator(); + configure(aabb); } @@ -1921,7 +2182,7 @@ void HeightMapShape3DSW::set_data(const Variant &p_data) { } else { int heights_size = heights.size(); for (int i = 0; i < heights_size; ++i) { - float h = heights[i]; + real_t h = heights[i]; if (h < min_height) { min_height = h; } else if (h > max_height) { diff --git a/servers/physics_3d/shape_3d_sw.h b/servers/physics_3d/shape_3d_sw.h index ca58900111..73eddc469c 100644 --- a/servers/physics_3d/shape_3d_sw.h +++ b/servers/physics_3d/shape_3d_sw.h @@ -32,6 +32,7 @@ #define SHAPE_SW_H #include "core/math/geometry_3d.h" +#include "core/templates/local_vector.h" #include "servers/physics_server_3d.h" class Shape3DSW; @@ -101,10 +102,12 @@ public: class ConcaveShape3DSW : public Shape3DSW { public: virtual bool is_concave() const override { return true; } - typedef void (*Callback)(void *p_userdata, Shape3DSW *p_convex); virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override { r_amount = 0; } - virtual void cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const = 0; + // Returns true to stop the query. + typedef bool (*QueryCallback)(void *p_userdata, Shape3DSW *p_convex); + + virtual void cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata) const = 0; ConcaveShape3DSW() {} }; @@ -117,23 +120,51 @@ class PlaneShape3DSW : public Shape3DSW { public: Plane get_plane() const; - virtual real_t get_area() const { return INFINITY; } - virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_PLANE; } - virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const; - virtual Vector3 get_support(const Vector3 &p_normal) const; - virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { r_amount = 0; } + virtual real_t get_area() const override { return INFINITY; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_PLANE; } + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override { r_amount = 0; } - virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal) const; - virtual bool intersect_point(const Vector3 &p_point) const; - virtual Vector3 get_closest_point_to(const Vector3 &p_point) const; - virtual Vector3 get_moment_of_inertia(real_t p_mass) const; + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; - virtual void set_data(const Variant &p_data); - virtual Variant get_data() const; + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; PlaneShape3DSW(); }; +class SeparationRayShape3DSW : public Shape3DSW { + real_t length; + bool slide_on_slope; + + void _setup(real_t p_length, bool p_slide_on_slope); + +public: + real_t get_length() const; + bool get_slide_on_slope() const; + + virtual real_t get_area() const override { return 0.0; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_SEPARATION_RAY; } + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override; + virtual Vector3 get_support(const Vector3 &p_normal) const override; + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override; + + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal) const override; + virtual bool intersect_point(const Vector3 &p_point) const override; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; + + virtual void set_data(const Variant &p_data) override; + virtual Variant get_data() const override; + + SeparationRayShape3DSW(); +}; + class SphereShape3DSW : public Shape3DSW { real_t radius; @@ -295,7 +326,7 @@ struct ConcavePolygonShape3DSW : public ConcaveShape3DSW { struct _CullParams { AABB aabb; - Callback callback = nullptr; + QueryCallback callback = nullptr; void *userdata = nullptr; const Face *faces = nullptr; const Vector3 *vertices = nullptr; @@ -321,7 +352,7 @@ struct ConcavePolygonShape3DSW : public ConcaveShape3DSW { bool backface_collision = false; void _cull_segment(int p_idx, _SegmentCullParams *p_params) const; - void _cull(int p_idx, _CullParams *p_params) const; + bool _cull(int p_idx, _CullParams *p_params) const; void _fill_bvh(_VolumeSW_BVH *p_bvh_tree, BVH *p_bvh_array, int &p_idx); @@ -339,7 +370,7 @@ public: virtual bool intersect_point(const Vector3 &p_point) const override; virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; - virtual void cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const override; + virtual void cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata) const override; virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; @@ -355,6 +386,21 @@ struct HeightMapShape3DSW : public ConcaveShape3DSW { int depth = 0; Vector3 local_origin; + // Accelerator. + struct Range { + real_t min = 0.0; + real_t max = 0.0; + }; + LocalVector<Range> bounds_grid; + int bounds_grid_width = 0; + int bounds_grid_depth = 0; + + static const int BOUNDS_CHUNK_SIZE = 16; + + _FORCE_INLINE_ const Range &_get_bounds_chunk(int p_x, int p_z) const { + return bounds_grid[(p_z * bounds_grid_width) + p_x]; + } + _FORCE_INLINE_ real_t _get_height(int p_x, int p_z) const { return heights[(p_z * width) + p_x]; } @@ -367,6 +413,11 @@ struct HeightMapShape3DSW : public ConcaveShape3DSW { void _get_cell(const Vector3 &p_point, int &r_x, int &r_y, int &r_z) const; + void _build_accelerator(); + + template <typename ProcessFunction> + bool _intersect_grid_segment(ProcessFunction &p_process, const Vector3 &p_begin, const Vector3 &p_end, int p_width, int p_depth, const Vector3 &offset, Vector3 &r_point, Vector3 &r_normal) const; + void _setup(const Vector<real_t> &p_heights, int p_width, int p_depth, real_t p_min_height, real_t p_max_height); public: @@ -382,7 +433,7 @@ public: virtual bool intersect_point(const Vector3 &p_point) const override; virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override; - virtual void cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const override; + virtual void cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata) const override; virtual Vector3 get_moment_of_inertia(real_t p_mass) const override; diff --git a/servers/physics_3d/soft_body_3d_sw.cpp b/servers/physics_3d/soft_body_3d_sw.cpp index 73b81444e1..d7e13867bf 100644 --- a/servers/physics_3d/soft_body_3d_sw.cpp +++ b/servers/physics_3d/soft_body_3d_sw.cpp @@ -38,7 +38,7 @@ /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ +Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. @@ -165,7 +165,7 @@ void SoftBody3DSW::update_rendering_server(RenderingServerHandler *p_rendering_s p_rendering_server_handler->set_aabb(bounds); } -void SoftBody3DSW::update_normals() { +void SoftBody3DSW::update_normals_and_centroids() { uint32_t i, ni; for (i = 0, ni = nodes.size(); i < ni; ++i) { @@ -180,6 +180,7 @@ void SoftBody3DSW::update_normals() { face.n[2]->n += n; face.normal = n; face.normal.normalize(); + face.centroid = 0.33333333333 * (face.n[0]->x + face.n[1]->x + face.n[2]->x); } for (i = 0, ni = nodes.size(); i < ni; ++i) { @@ -310,7 +311,7 @@ void SoftBody3DSW::apply_nodes_transform(const Transform3D &p_transform) { face_tree.clear(); - update_normals(); + update_normals_and_centroids(); update_bounds(); update_constants(); } @@ -574,7 +575,7 @@ bool SoftBody3DSW::create_from_trimesh(const Vector<int> &p_indices, const Vecto reoptimize_link_order(); update_constants(); - update_normals(); + update_normals_and_centroids(); update_bounds(); return true; @@ -898,47 +899,86 @@ void SoftBody3DSW::add_velocity(const Vector3 &p_velocity) { } } -void SoftBody3DSW::apply_forces() { - if (pressure_coefficient < CMP_EPSILON) { - return; - } +void SoftBody3DSW::apply_forces(bool p_has_wind_forces) { + int ac = areas.size(); if (nodes.is_empty()) { return; } uint32_t i, ni; + int32_t j; - // Calculate volume. real_t volume = 0.0; const Vector3 &org = nodes[0].x; + + // Iterate over faces (try not to iterate elsewhere if possible). for (i = 0, ni = faces.size(); i < ni; ++i) { + bool stopped = false; const Face &face = faces[i]; + + Vector3 wind_force(0, 0, 0); + + // Compute volume. volume += vec3_dot(face.n[0]->x - org, vec3_cross(face.n[1]->x - org, face.n[2]->x - org)); + + // Compute nodal forces from area winds. + if (ac && p_has_wind_forces) { + const AreaCMP *aa = &areas[0]; + for (j = ac - 1; j >= 0 && !stopped; j--) { + PhysicsServer3D::AreaSpaceOverrideMode mode = aa[j].area->get_space_override_mode(); + switch (mode) { + case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE: + case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: { + wind_force += _compute_area_windforce(aa[j].area, &face); + stopped = mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE; + } break; + case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE: + case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE_COMBINE: { + wind_force = _compute_area_windforce(aa[j].area, &face); + stopped = mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE; + } break; + default: { + } + } + } + + for (j = 0; j < 3; j++) { + Node *current_node = face.n[j]; + current_node->f += wind_force; + } + } } volume /= 6.0; - // Apply per node forces. - real_t ivolumetp = 1.0 / Math::abs(volume) * pressure_coefficient; - for (i = 0, ni = nodes.size(); i < ni; ++i) { - Node &node = nodes[i]; - if (node.im > 0) { - node.f += node.n * (node.area * ivolumetp); + // Apply nodal pressure forces. + if (pressure_coefficient > CMP_EPSILON) { + real_t ivolumetp = 1.0 / Math::abs(volume) * pressure_coefficient; + for (i = 0, ni = nodes.size(); i < ni; ++i) { + Node &node = nodes[i]; + if (node.im > 0) { + node.f += node.n * (node.area * ivolumetp); + } } } } void SoftBody3DSW::_compute_area_gravity(const Area3DSW *p_area) { - if (p_area->is_gravity_point()) { - if (p_area->get_gravity_distance_scale() > 0) { - Vector3 v = p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin(); - gravity += v.normalized() * (p_area->get_gravity() / Math::pow(v.length() * p_area->get_gravity_distance_scale() + 1, 2)); - } else { - gravity += (p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin()).normalized() * p_area->get_gravity(); - } - } else { - gravity += p_area->get_gravity_vector() * p_area->get_gravity(); - } + Vector3 area_gravity; + p_area->compute_gravity(get_transform().get_origin(), area_gravity); + gravity += area_gravity; +} + +Vector3 SoftBody3DSW::_compute_area_windforce(const Area3DSW *p_area, const Face *p_face) { + real_t wfm = p_area->get_wind_force_magnitude(); + real_t waf = p_area->get_wind_attenuation_factor(); + const Vector3 &wd = p_area->get_wind_direction(); + const Vector3 &ws = p_area->get_wind_source(); + real_t projection_on_tri_normal = vec3_dot(p_face->normal, wd); + real_t projection_toward_centroid = vec3_dot(p_face->centroid - ws, wd); + real_t attenuation_over_distance = pow(projection_toward_centroid, -waf); + real_t nodal_force_magnitude = wfm * 0.33333333333 * p_face->ra * projection_on_tri_normal * attenuation_over_distance; + return nodal_force_magnitude * p_face->normal; } void SoftBody3DSW::predict_motion(real_t p_delta) { @@ -952,11 +992,15 @@ void SoftBody3DSW::predict_motion(real_t p_delta) { int ac = areas.size(); bool stopped = false; + bool has_wind_forces = false; if (ac) { areas.sort(); const AreaCMP *aa = &areas[0]; for (int i = ac - 1; i >= 0 && !stopped; i--) { + // Avoids unnecessary loop in apply_forces(). + has_wind_forces = has_wind_forces || aa[i].area->get_wind_force_magnitude() > CMP_EPSILON; + PhysicsServer3D::AreaSpaceOverrideMode mode = aa[i].area->get_space_override_mode(); switch (mode) { case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE: @@ -978,7 +1022,9 @@ void SoftBody3DSW::predict_motion(real_t p_delta) { // Apply forces. add_velocity(gravity * p_delta); - apply_forces(); + if (pressure_coefficient > CMP_EPSILON || has_wind_forces) { + apply_forces(has_wind_forces); + } // Avoid soft body from 'exploding' so use some upper threshold of maximum motion // that a node can travel per frame. @@ -1057,7 +1103,7 @@ void SoftBody3DSW::solve_constraints(real_t p_delta) { node.q = node.x; } - update_normals(); + update_normals_and_centroids(); } void SoftBody3DSW::solve_links(real_t kst, real_t ti) { diff --git a/servers/physics_3d/soft_body_3d_sw.h b/servers/physics_3d/soft_body_3d_sw.h index be0620c506..58fd234fde 100644 --- a/servers/physics_3d/soft_body_3d_sw.h +++ b/servers/physics_3d/soft_body_3d_sw.h @@ -71,6 +71,7 @@ class SoftBody3DSW : public CollisionObject3DSW { }; struct Face { + Vector3 centroid; Node *n[3] = { nullptr, nullptr, nullptr }; // Node pointers Vector3 normal; // Normal real_t ra = 0.0; // Rest area @@ -114,6 +115,7 @@ class SoftBody3DSW : public CollisionObject3DSW { uint64_t island_step = 0; _FORCE_INLINE_ void _compute_area_gravity(const Area3DSW *p_area); + _FORCE_INLINE_ Vector3 _compute_area_windforce(const Area3DSW *p_area, const Face *p_face); public: SoftBody3DSW(); @@ -220,7 +222,7 @@ protected: virtual void _shapes_changed(); private: - void update_normals(); + void update_normals_and_centroids(); void update_bounds(); void update_constants(); void update_area(); @@ -231,7 +233,7 @@ private: void add_velocity(const Vector3 &p_velocity); - void apply_forces(); + void apply_forces(bool p_has_wind_forces); bool create_from_trimesh(const Vector<int> &p_indices, const Vector<Vector3> &p_vertices); void generate_bending_constraints(int p_distance); diff --git a/servers/physics_3d/space_3d_sw.cpp b/servers/physics_3d/space_3d_sw.cpp index f9e55ad525..3b08184e00 100644 --- a/servers/physics_3d/space_3d_sw.cpp +++ b/servers/physics_3d/space_3d_sw.cpp @@ -569,7 +569,7 @@ int Space3DSW::_cull_aabb_for_body(Body3DSW *p_body, const AABB &p_aabb) { return amount; } -bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, PhysicsServer3D::MotionResult *r_result, const Set<RID> &p_exclude) { +bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, PhysicsServer3D::MotionResult *r_result, bool p_collide_separation_ray, const Set<RID> &p_exclude) { //give me back regular physics engine logic //this is madness //and most people using this function will think @@ -714,9 +714,19 @@ bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform3D &p_from, co continue; } - Transform3D body_shape_xform = body_transform * p_body->get_shape_transform(j); Shape3DSW *body_shape = p_body->get_shape(j); + // Colliding separation rays allows to properly snap to the ground, + // otherwise it's not needed in regular motion. + if (!p_collide_separation_ray && (body_shape->get_type() == PhysicsServer3D::SHAPE_SEPARATION_RAY)) { + // When slide on slope is on, separation ray shape acts like a regular shape. + if (!static_cast<SeparationRayShape3DSW *>(body_shape)->get_slide_on_slope()) { + continue; + } + } + + Transform3D body_shape_xform = body_transform * p_body->get_shape_transform(j); + Transform3D body_shape_xform_inv = body_shape_xform.affine_inverse(); MotionShape3DSW mshape; mshape.shape = body_shape; @@ -967,12 +977,12 @@ void Space3DSW::body_remove_from_active_list(SelfList<Body3DSW> *p_body) { active_list.remove(p_body); } -void Space3DSW::body_add_to_inertia_update_list(SelfList<Body3DSW> *p_body) { - inertia_update_list.add(p_body); +void Space3DSW::body_add_to_mass_properties_update_list(SelfList<Body3DSW> *p_body) { + mass_properties_update_list.add(p_body); } -void Space3DSW::body_remove_from_inertia_update_list(SelfList<Body3DSW> *p_body) { - inertia_update_list.remove(p_body); +void Space3DSW::body_remove_from_mass_properties_update_list(SelfList<Body3DSW> *p_body) { + mass_properties_update_list.remove(p_body); } BroadPhase3DSW *Space3DSW::get_broadphase() { @@ -1049,9 +1059,9 @@ void Space3DSW::call_queries() { void Space3DSW::setup() { contact_debug_count = 0; - while (inertia_update_list.first()) { - inertia_update_list.first()->self()->update_inertias(); - inertia_update_list.remove(inertia_update_list.first()); + while (mass_properties_update_list.first()) { + mass_properties_update_list.first()->self()->update_mass_properties(); + mass_properties_update_list.remove(mass_properties_update_list.first()); } } diff --git a/servers/physics_3d/space_3d_sw.h b/servers/physics_3d/space_3d_sw.h index 9b5b4de069..98c335cb80 100644 --- a/servers/physics_3d/space_3d_sw.h +++ b/servers/physics_3d/space_3d_sw.h @@ -48,12 +48,12 @@ class PhysicsDirectSpaceState3DSW : public PhysicsDirectSpaceState3D { public: Space3DSW *space; - virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) override; - virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) override; - virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) override; + virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) override; + virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; virtual Vector3 get_closest_point_to_object_volume(RID p_object, const Vector3 p_point) const override; PhysicsDirectSpaceState3DSW(); @@ -79,7 +79,7 @@ private: BroadPhase3DSW *broadphase; SelfList<Body3DSW>::List active_list; - SelfList<Body3DSW>::List inertia_update_list; + SelfList<Body3DSW>::List mass_properties_update_list; SelfList<Body3DSW>::List state_query_list; SelfList<Area3DSW>::List monitor_query_list; SelfList<Area3DSW>::List area_moved_list; @@ -112,6 +112,8 @@ private: bool locked; + real_t last_step = 0.001; + int island_count; int active_objects; int collision_pairs; @@ -135,8 +137,8 @@ public: const SelfList<Body3DSW>::List &get_active_body_list() const; void body_add_to_active_list(SelfList<Body3DSW> *p_body); void body_remove_from_active_list(SelfList<Body3DSW> *p_body); - void body_add_to_inertia_update_list(SelfList<Body3DSW> *p_body); - void body_remove_from_inertia_update_list(SelfList<Body3DSW> *p_body); + void body_add_to_mass_properties_update_list(SelfList<Body3DSW> *p_body); + void body_remove_from_mass_properties_update_list(SelfList<Body3DSW> *p_body); void body_add_to_state_query_list(SelfList<Body3DSW> *p_body); void body_remove_from_state_query_list(SelfList<Body3DSW> *p_body); @@ -174,6 +176,9 @@ public: void lock(); void unlock(); + real_t get_last_step() const { return last_step; } + void set_last_step(real_t p_step) { last_step = p_step; } + void set_param(PhysicsServer3D::SpaceParameter p_param, real_t p_value); real_t get_param(PhysicsServer3D::SpaceParameter p_param) const; @@ -203,7 +208,7 @@ public: void set_elapsed_time(ElapsedTime p_time, uint64_t p_msec) { elapsed_time[p_time] = p_msec; } uint64_t get_elapsed_time(ElapsedTime p_time) const { return elapsed_time[p_time]; } - bool test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, PhysicsServer3D::MotionResult *r_result, const Set<RID> &p_exclude = Set<RID>()); + bool test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, PhysicsServer3D::MotionResult *r_result, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()); Space3DSW(); ~Space3DSW(); diff --git a/servers/physics_3d/step_3d_sw.cpp b/servers/physics_3d/step_3d_sw.cpp index ba18bac611..d0604b0aa0 100644 --- a/servers/physics_3d/step_3d_sw.cpp +++ b/servers/physics_3d/step_3d_sw.cpp @@ -185,6 +185,8 @@ void Step3DSW::step(Space3DSW *p_space, real_t p_delta, int p_iterations) { p_space->setup(); //update inertias, etc + p_space->set_last_step(p_delta); + iterations = p_iterations; delta = p_delta; diff --git a/servers/physics_server_2d.cpp b/servers/physics_server_2d.cpp index c96f85446c..2656ef1d6d 100644 --- a/servers/physics_server_2d.cpp +++ b/servers/physics_server_2d.cpp @@ -77,6 +77,7 @@ void PhysicsDirectBodyState2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_total_linear_damp"), &PhysicsDirectBodyState2D::get_total_linear_damp); ClassDB::bind_method(D_METHOD("get_total_angular_damp"), &PhysicsDirectBodyState2D::get_total_angular_damp); + ClassDB::bind_method(D_METHOD("get_center_of_mass"), &PhysicsDirectBodyState2D::get_center_of_mass); ClassDB::bind_method(D_METHOD("get_inverse_mass"), &PhysicsDirectBodyState2D::get_inverse_mass); ClassDB::bind_method(D_METHOD("get_inverse_inertia"), &PhysicsDirectBodyState2D::get_inverse_inertia); @@ -123,6 +124,7 @@ void PhysicsDirectBodyState2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "total_angular_damp"), "", "get_total_angular_damp"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "total_linear_damp"), "", "get_total_linear_damp"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "total_gravity"), "", "get_total_gravity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "center_of_mass"), "", "get_center_of_mass"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_velocity"), "set_angular_velocity", "get_angular_velocity"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sleeping"), "set_sleep_state", "is_sleeping"); @@ -257,13 +259,6 @@ void PhysicsShapeQueryParameters2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas"), "set_collide_with_areas", "is_collide_with_areas_enabled"); } -PhysicsShapeQueryParameters2D::PhysicsShapeQueryParameters2D() { - margin = 0; - collision_mask = 0x7FFFFFFF; - collide_with_bodies = true; - collide_with_areas = false; -} - Dictionary PhysicsDirectSpaceState2D::_intersect_ray(const Vector2 &p_from, const Vector2 &p_to, const Vector<RID> &p_exclude, uint32_t p_layers, bool p_collide_with_bodies, bool p_collide_with_areas) { RayResult inters; Set<RID> exclude; @@ -411,9 +406,9 @@ PhysicsDirectSpaceState2D::PhysicsDirectSpaceState2D() { } void PhysicsDirectSpaceState2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("intersect_point", "point", "max_results", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_point, DEFVAL(32), DEFVAL(Array()), DEFVAL(0x7FFFFFFF), DEFVAL(true), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("intersect_point_on_canvas", "point", "canvas_instance_id", "max_results", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_point_on_canvas, DEFVAL(32), DEFVAL(Array()), DEFVAL(0x7FFFFFFF), DEFVAL(true), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("intersect_ray", "from", "to", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_ray, DEFVAL(Array()), DEFVAL(0x7FFFFFFF), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("intersect_point", "point", "max_results", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_point, DEFVAL(32), DEFVAL(Array()), DEFVAL(UINT32_MAX), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("intersect_point_on_canvas", "point", "canvas_instance_id", "max_results", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_point_on_canvas, DEFVAL(32), DEFVAL(Array()), DEFVAL(UINT32_MAX), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("intersect_ray", "from", "to", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_ray, DEFVAL(Array()), DEFVAL(UINT32_MAX), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("intersect_shape", "shape", "max_results"), &PhysicsDirectSpaceState2D::_intersect_shape, DEFVAL(32)); ClassDB::bind_method(D_METHOD("cast_motion", "shape"), &PhysicsDirectSpaceState2D::_cast_motion); ClassDB::bind_method(D_METHOD("collide_shape", "shape", "max_results"), &PhysicsDirectSpaceState2D::_collide_shape, DEFVAL(32)); @@ -500,7 +495,7 @@ void PhysicsTestMotionResult2D::_bind_methods() { /////////////////////////////////////// -bool PhysicsServer2D::_body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, const Ref<PhysicsTestMotionResult2D> &p_result, const Vector<RID> &p_exclude) { +bool PhysicsServer2D::_body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, const Ref<PhysicsTestMotionResult2D> &p_result, bool p_collide_separation_ray, const Vector<RID> &p_exclude) { MotionResult *r = nullptr; if (p_result.is_valid()) { r = p_result->get_result_ptr(); @@ -509,11 +504,12 @@ bool PhysicsServer2D::_body_test_motion(RID p_body, const Transform2D &p_from, c for (int i = 0; i < p_exclude.size(); i++) { exclude.insert(p_exclude[i]); } - return body_test_motion(p_body, p_from, p_motion, p_margin, r, exclude); + return body_test_motion(p_body, p_from, p_motion, p_margin, r, p_collide_separation_ray, exclude); } void PhysicsServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("world_margin_shape_create"), &PhysicsServer2D::world_margin_shape_create); + ClassDB::bind_method(D_METHOD("separation_ray_shape_create"), &PhysicsServer2D::separation_ray_shape_create); ClassDB::bind_method(D_METHOD("segment_shape_create"), &PhysicsServer2D::segment_shape_create); ClassDB::bind_method(D_METHOD("circle_shape_create"), &PhysicsServer2D::circle_shape_create); ClassDB::bind_method(D_METHOD("rectangle_shape_create"), &PhysicsServer2D::rectangle_shape_create); @@ -613,6 +609,8 @@ void PhysicsServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("body_set_param", "body", "param", "value"), &PhysicsServer2D::body_set_param); ClassDB::bind_method(D_METHOD("body_get_param", "body", "param"), &PhysicsServer2D::body_get_param); + ClassDB::bind_method(D_METHOD("body_reset_mass_properties", "body"), &PhysicsServer2D::body_reset_mass_properties); + ClassDB::bind_method(D_METHOD("body_set_state", "body", "state", "value"), &PhysicsServer2D::body_set_state); ClassDB::bind_method(D_METHOD("body_get_state", "body", "state"), &PhysicsServer2D::body_get_state); @@ -635,7 +633,7 @@ void PhysicsServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("body_set_force_integration_callback", "body", "callable", "userdata"), &PhysicsServer2D::body_set_force_integration_callback, DEFVAL(Variant())); - ClassDB::bind_method(D_METHOD("body_test_motion", "body", "from", "motion", "margin", "result", "exclude"), &PhysicsServer2D::_body_test_motion, DEFVAL(0.08), DEFVAL(Variant()), DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("body_test_motion", "body", "from", "motion", "margin", "result", "collide_separation_ray", "exclude"), &PhysicsServer2D::_body_test_motion, DEFVAL(0.08), DEFVAL(Variant()), DEFVAL(false), DEFVAL(Array())); ClassDB::bind_method(D_METHOD("body_get_direct_state", "body"), &PhysicsServer2D::body_get_direct_state); @@ -675,6 +673,7 @@ void PhysicsServer2D::_bind_methods() { BIND_ENUM_CONSTANT(SPACE_PARAM_TEST_MOTION_MIN_CONTACT_DEPTH); BIND_ENUM_CONSTANT(SHAPE_WORLD_MARGIN); + BIND_ENUM_CONSTANT(SHAPE_SEPARATION_RAY); BIND_ENUM_CONSTANT(SHAPE_SEGMENT); BIND_ENUM_CONSTANT(SHAPE_CIRCLE); BIND_ENUM_CONSTANT(SHAPE_RECTANGLE); @@ -707,6 +706,7 @@ void PhysicsServer2D::_bind_methods() { BIND_ENUM_CONSTANT(BODY_PARAM_FRICTION); BIND_ENUM_CONSTANT(BODY_PARAM_MASS); BIND_ENUM_CONSTANT(BODY_PARAM_INERTIA); + BIND_ENUM_CONSTANT(BODY_PARAM_CENTER_OF_MASS); BIND_ENUM_CONSTANT(BODY_PARAM_GRAVITY_SCALE); BIND_ENUM_CONSTANT(BODY_PARAM_LINEAR_DAMP); BIND_ENUM_CONSTANT(BODY_PARAM_ANGULAR_DAMP); diff --git a/servers/physics_server_2d.h b/servers/physics_server_2d.h index 4c559dd7bd..1145bb8b91 100644 --- a/servers/physics_server_2d.h +++ b/servers/physics_server_2d.h @@ -48,6 +48,7 @@ public: virtual real_t get_total_linear_damp() const = 0; // get density of this body space/area virtual real_t get_total_angular_damp() const = 0; // get density of this body space/area + virtual Vector2 get_center_of_mass() const = 0; virtual real_t get_inverse_mass() const = 0; // get the mass virtual real_t get_inverse_inertia() const = 0; // get density of this body space @@ -103,12 +104,12 @@ class PhysicsShapeQueryParameters2D : public RefCounted { RID shape; Transform2D transform; Vector2 motion; - real_t margin; + real_t margin = 0.0; Set<RID> exclude; - uint32_t collision_mask; + uint32_t collision_mask = UINT32_MAX; - bool collide_with_bodies; - bool collide_with_areas; + bool collide_with_bodies = true; + bool collide_with_areas = false; protected: static void _bind_methods(); @@ -139,8 +140,6 @@ public: void set_exclude(const Vector<RID> &p_exclude); Vector<RID> get_exclude() const; - - PhysicsShapeQueryParameters2D(); }; class PhysicsDirectSpaceState2D : public Object { @@ -169,7 +168,7 @@ public: Variant metadata; }; - virtual bool intersect_ray(const Vector2 &p_from, const Vector2 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool intersect_ray(const Vector2 &p_from, const Vector2 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; struct ShapeResult { RID rid; @@ -179,14 +178,14 @@ public: Variant metadata; }; - virtual int intersect_point(const Vector2 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) = 0; - virtual int intersect_point_on_canvas(const Vector2 &p_point, ObjectID p_canvas_instance_id, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) = 0; + virtual int intersect_point(const Vector2 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) = 0; + virtual int intersect_point_on_canvas(const Vector2 &p_point, ObjectID p_canvas_instance_id, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) = 0; - virtual int intersect_shape(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual int intersect_shape(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; - virtual bool cast_motion(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool cast_motion(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; - virtual bool collide_shape(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, Vector2 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool collide_shape(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, Vector2 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; struct ShapeRestInfo { Vector2 point; @@ -198,7 +197,7 @@ public: Variant metadata; }; - virtual bool rest_info(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool rest_info(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; PhysicsDirectSpaceState2D(); }; @@ -210,7 +209,7 @@ class PhysicsServer2D : public Object { static PhysicsServer2D *singleton; - virtual bool _body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, const Ref<PhysicsTestMotionResult2D> &p_result = Ref<PhysicsTestMotionResult2D>(), const Vector<RID> &p_exclude = Vector<RID>()); + virtual bool _body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, const Ref<PhysicsTestMotionResult2D> &p_result = Ref<PhysicsTestMotionResult2D>(), bool p_collide_separation_ray = false, const Vector<RID> &p_exclude = Vector<RID>()); protected: static void _bind_methods(); @@ -220,6 +219,7 @@ public: enum ShapeType { SHAPE_WORLD_MARGIN, ///< plane:"plane" + SHAPE_SEPARATION_RAY, ///< float:"length" SHAPE_SEGMENT, ///< float:"length" SHAPE_CIRCLE, ///< float:"radius" SHAPE_RECTANGLE, ///< vec3:"extents" @@ -230,6 +230,7 @@ public: }; virtual RID world_margin_shape_create() = 0; + virtual RID separation_ray_shape_create() = 0; virtual RID segment_shape_create() = 0; virtual RID circle_shape_create() = 0; virtual RID rectangle_shape_create() = 0; @@ -402,15 +403,18 @@ public: BODY_PARAM_BOUNCE, BODY_PARAM_FRICTION, BODY_PARAM_MASS, ///< unused for static, always infinite - BODY_PARAM_INERTIA, // read-only: computed from mass & shapes + BODY_PARAM_INERTIA, + BODY_PARAM_CENTER_OF_MASS, BODY_PARAM_GRAVITY_SCALE, BODY_PARAM_LINEAR_DAMP, BODY_PARAM_ANGULAR_DAMP, BODY_PARAM_MAX, }; - virtual void body_set_param(RID p_body, BodyParameter p_param, real_t p_value) = 0; - virtual real_t body_get_param(RID p_body, BodyParameter p_param) const = 0; + virtual void body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) = 0; + virtual Variant body_get_param(RID p_body, BodyParameter p_param) const = 0; + + virtual void body_reset_mass_properties(RID p_body) = 0; //state enum BodyState { @@ -455,6 +459,10 @@ public: virtual void body_set_omit_force_integration(RID p_body, bool p_omit) = 0; virtual bool body_is_omitting_force_integration(RID p_body) const = 0; + // Callback for C++ use only. + typedef void (*BodyStateCallback)(void *p_instance, PhysicsDirectBodyState2D *p_state); + virtual void body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) = 0; + virtual void body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata = Variant()) = 0; virtual bool body_collide_shape(RID p_body, int p_body_shape, RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, Vector2 *r_results, int p_result_max, int &r_result_count) = 0; @@ -485,7 +493,7 @@ public: } }; - virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) = 0; + virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) = 0; struct SeparationResult { real_t collision_depth; diff --git a/servers/physics_server_3d.cpp b/servers/physics_server_3d.cpp index 53863cea72..0c487b83ea 100644 --- a/servers/physics_server_3d.cpp +++ b/servers/physics_server_3d.cpp @@ -249,13 +249,6 @@ void PhysicsShapeQueryParameters3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas"), "set_collide_with_areas", "is_collide_with_areas_enabled"); } -PhysicsShapeQueryParameters3D::PhysicsShapeQueryParameters3D() { - margin = 0; - collision_mask = 0x7FFFFFFF; - collide_with_bodies = true; - collide_with_areas = false; -} - ///////////////////////////////////// Dictionary PhysicsDirectSpaceState3D::_intersect_ray(const Vector3 &p_from, const Vector3 &p_to, const Vector<RID> &p_exclude, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas) { @@ -360,7 +353,7 @@ PhysicsDirectSpaceState3D::PhysicsDirectSpaceState3D() { } void PhysicsDirectSpaceState3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("intersect_ray", "from", "to", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState3D::_intersect_ray, DEFVAL(Array()), DEFVAL(0x7FFFFFFF), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("intersect_ray", "from", "to", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState3D::_intersect_ray, DEFVAL(Array()), DEFVAL(UINT32_MAX), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("intersect_shape", "shape", "max_results"), &PhysicsDirectSpaceState3D::_intersect_shape, DEFVAL(32)); ClassDB::bind_method(D_METHOD("cast_motion", "shape", "motion"), &PhysicsDirectSpaceState3D::_cast_motion); ClassDB::bind_method(D_METHOD("collide_shape", "shape", "max_results"), &PhysicsDirectSpaceState3D::_collide_shape, DEFVAL(32)); @@ -447,7 +440,7 @@ void PhysicsTestMotionResult3D::_bind_methods() { /////////////////////////////////////// -bool PhysicsServer3D::_body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, const Ref<PhysicsTestMotionResult3D> &p_result, const Vector<RID> &p_exclude) { +bool PhysicsServer3D::_body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, const Ref<PhysicsTestMotionResult3D> &p_result, bool p_collide_separation_ray, const Vector<RID> &p_exclude) { MotionResult *r = nullptr; if (p_result.is_valid()) { r = p_result->get_result_ptr(); @@ -456,13 +449,15 @@ bool PhysicsServer3D::_body_test_motion(RID p_body, const Transform3D &p_from, c for (int i = 0; i < p_exclude.size(); i++) { exclude.insert(p_exclude[i]); } - return body_test_motion(p_body, p_from, p_motion, p_margin, r, exclude); + return body_test_motion(p_body, p_from, p_motion, p_margin, r, p_collide_separation_ray, exclude); } RID PhysicsServer3D::shape_create(ShapeType p_shape) { switch (p_shape) { case SHAPE_PLANE: return plane_shape_create(); + case SHAPE_SEPARATION_RAY: + return separation_ray_shape_create(); case SHAPE_SPHERE: return sphere_shape_create(); case SHAPE_BOX: @@ -488,6 +483,7 @@ void PhysicsServer3D::_bind_methods() { #ifndef _3D_DISABLED ClassDB::bind_method(D_METHOD("plane_shape_create"), &PhysicsServer3D::plane_shape_create); + ClassDB::bind_method(D_METHOD("separation_ray_shape_create"), &PhysicsServer3D::separation_ray_shape_create); ClassDB::bind_method(D_METHOD("sphere_shape_create"), &PhysicsServer3D::sphere_shape_create); ClassDB::bind_method(D_METHOD("box_shape_create"), &PhysicsServer3D::box_shape_create); ClassDB::bind_method(D_METHOD("capsule_shape_create"), &PhysicsServer3D::capsule_shape_create); @@ -581,6 +577,8 @@ void PhysicsServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("body_set_param", "body", "param", "value"), &PhysicsServer3D::body_set_param); ClassDB::bind_method(D_METHOD("body_get_param", "body", "param"), &PhysicsServer3D::body_get_param); + ClassDB::bind_method(D_METHOD("body_reset_mass_properties", "body"), &PhysicsServer3D::body_reset_mass_properties); + ClassDB::bind_method(D_METHOD("body_set_state", "body", "state", "value"), &PhysicsServer3D::body_set_state); ClassDB::bind_method(D_METHOD("body_get_state", "body", "state"), &PhysicsServer3D::body_get_state); @@ -609,7 +607,7 @@ void PhysicsServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("body_set_ray_pickable", "body", "enable"), &PhysicsServer3D::body_set_ray_pickable); - ClassDB::bind_method(D_METHOD("body_test_motion", "body", "from", "motion", "margin", "result", "exclude"), &PhysicsServer3D::_body_test_motion, DEFVAL(0.001), DEFVAL(Variant()), DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("body_test_motion", "body", "from", "motion", "margin", "result", "collide_separation_ray", "exclude"), &PhysicsServer3D::_body_test_motion, DEFVAL(0.001), DEFVAL(Variant()), DEFVAL(false), DEFVAL(Array())); ClassDB::bind_method(D_METHOD("body_get_direct_state", "body"), &PhysicsServer3D::body_get_direct_state); @@ -748,6 +746,7 @@ void PhysicsServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_process_info", "process_info"), &PhysicsServer3D::get_process_info); BIND_ENUM_CONSTANT(SHAPE_PLANE); + BIND_ENUM_CONSTANT(SHAPE_SEPARATION_RAY); BIND_ENUM_CONSTANT(SHAPE_SPHERE); BIND_ENUM_CONSTANT(SHAPE_BOX); BIND_ENUM_CONSTANT(SHAPE_CAPSULE); @@ -766,6 +765,10 @@ void PhysicsServer3D::_bind_methods() { BIND_ENUM_CONSTANT(AREA_PARAM_LINEAR_DAMP); BIND_ENUM_CONSTANT(AREA_PARAM_ANGULAR_DAMP); BIND_ENUM_CONSTANT(AREA_PARAM_PRIORITY); + BIND_ENUM_CONSTANT(AREA_PARAM_WIND_FORCE_MAGNITUDE); + BIND_ENUM_CONSTANT(AREA_PARAM_WIND_SOURCE); + BIND_ENUM_CONSTANT(AREA_PARAM_WIND_DIRECTION); + BIND_ENUM_CONSTANT(AREA_PARAM_WIND_ATTENUATION_FACTOR); BIND_ENUM_CONSTANT(AREA_SPACE_OVERRIDE_DISABLED); BIND_ENUM_CONSTANT(AREA_SPACE_OVERRIDE_COMBINE); @@ -781,6 +784,8 @@ void PhysicsServer3D::_bind_methods() { BIND_ENUM_CONSTANT(BODY_PARAM_BOUNCE); BIND_ENUM_CONSTANT(BODY_PARAM_FRICTION); BIND_ENUM_CONSTANT(BODY_PARAM_MASS); + BIND_ENUM_CONSTANT(BODY_PARAM_INERTIA); + BIND_ENUM_CONSTANT(BODY_PARAM_CENTER_OF_MASS); BIND_ENUM_CONSTANT(BODY_PARAM_GRAVITY_SCALE); BIND_ENUM_CONSTANT(BODY_PARAM_LINEAR_DAMP); BIND_ENUM_CONSTANT(BODY_PARAM_ANGULAR_DAMP); diff --git a/servers/physics_server_3d.h b/servers/physics_server_3d.h index f806fd55be..5677604682 100644 --- a/servers/physics_server_3d.h +++ b/servers/physics_server_3d.h @@ -103,12 +103,12 @@ class PhysicsShapeQueryParameters3D : public RefCounted { RES shape_ref; RID shape; Transform3D transform; - real_t margin; + real_t margin = 0.0; Set<RID> exclude; - uint32_t collision_mask; + uint32_t collision_mask = UINT32_MAX; - bool collide_with_bodies; - bool collide_with_areas; + bool collide_with_bodies = true; + bool collide_with_areas = false; protected: static void _bind_methods(); @@ -136,15 +136,13 @@ public: void set_collide_with_areas(bool p_enable); bool is_collide_with_areas_enabled() const; - - PhysicsShapeQueryParameters3D(); }; class PhysicsDirectSpaceState3D : public Object { GDCLASS(PhysicsDirectSpaceState3D, Object); private: - Dictionary _intersect_ray(const Vector3 &p_from, const Vector3 &p_to, const Vector<RID> &p_exclude = Vector<RID>(), uint32_t p_collision_mask = 0, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); + Dictionary _intersect_ray(const Vector3 &p_from, const Vector3 &p_to, const Vector<RID> &p_exclude = Vector<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); Array _intersect_shape(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query, int p_max_results = 32); Array _cast_motion(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query, const Vector3 &p_motion); Array _collide_shape(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query, int p_max_results = 32); @@ -161,7 +159,7 @@ public: int shape = 0; }; - virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; struct RayResult { Vector3 position; @@ -172,9 +170,9 @@ public: int shape = 0; }; - virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) = 0; + virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) = 0; - virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; struct ShapeRestInfo { Vector3 point; @@ -185,11 +183,11 @@ public: Vector3 linear_velocity; //velocity at contact point }; - virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) = 0; + virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) = 0; - virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; - virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; virtual Vector3 get_closest_point_to_object_volume(RID p_object, const Vector3 p_point) const = 0; @@ -212,7 +210,7 @@ class PhysicsServer3D : public Object { static PhysicsServer3D *singleton; - virtual bool _body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, const Ref<PhysicsTestMotionResult3D> &p_result = Ref<PhysicsTestMotionResult3D>(), const Vector<RID> &p_exclude = Vector<RID>()); + virtual bool _body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, const Ref<PhysicsTestMotionResult3D> &p_result = Ref<PhysicsTestMotionResult3D>(), bool p_collide_separation_ray = false, const Vector<RID> &p_exclude = Vector<RID>()); protected: static void _bind_methods(); @@ -222,6 +220,7 @@ public: enum ShapeType { SHAPE_PLANE, ///< plane:"plane" + SHAPE_SEPARATION_RAY, ///< float:"length" SHAPE_SPHERE, ///< float:"radius" SHAPE_BOX, ///< vec3:"extents" SHAPE_CAPSULE, ///< dict( float:"radius", float:"height"):capsule @@ -236,6 +235,7 @@ public: RID shape_create(ShapeType p_shape); virtual RID plane_shape_create() = 0; + virtual RID separation_ray_shape_create() = 0; virtual RID sphere_shape_create() = 0; virtual RID box_shape_create() = 0; virtual RID capsule_shape_create() = 0; @@ -298,7 +298,11 @@ public: AREA_PARAM_GRAVITY_POINT_ATTENUATION, AREA_PARAM_LINEAR_DAMP, AREA_PARAM_ANGULAR_DAMP, - AREA_PARAM_PRIORITY + AREA_PARAM_PRIORITY, + AREA_PARAM_WIND_FORCE_MAGNITUDE, + AREA_PARAM_WIND_SOURCE, + AREA_PARAM_WIND_DIRECTION, + AREA_PARAM_WIND_ATTENUATION_FACTOR, }; virtual RID area_create() = 0; @@ -401,14 +405,18 @@ public: BODY_PARAM_BOUNCE, BODY_PARAM_FRICTION, BODY_PARAM_MASS, ///< unused for static, always infinite + BODY_PARAM_INERTIA, + BODY_PARAM_CENTER_OF_MASS, BODY_PARAM_GRAVITY_SCALE, BODY_PARAM_LINEAR_DAMP, BODY_PARAM_ANGULAR_DAMP, BODY_PARAM_MAX, }; - virtual void body_set_param(RID p_body, BodyParameter p_param, real_t p_value) = 0; - virtual real_t body_get_param(RID p_body, BodyParameter p_param) const = 0; + virtual void body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) = 0; + virtual Variant body_get_param(RID p_body, BodyParameter p_param) const = 0; + + virtual void body_reset_mass_properties(RID p_body) = 0; //state enum BodyState { @@ -465,6 +473,10 @@ public: virtual void body_set_omit_force_integration(RID p_body, bool p_omit) = 0; virtual bool body_is_omitting_force_integration(RID p_body) const = 0; + // Callback for C++ use only. + typedef void (*BodyStateCallback)(void *p_instance, PhysicsDirectBodyState3D *p_state); + virtual void body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) = 0; + virtual void body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata = Variant()) = 0; virtual void body_set_ray_pickable(RID p_body, bool p_enable) = 0; @@ -493,7 +505,7 @@ public: } }; - virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) = 0; + virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) = 0; /* SOFT BODY */ diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index 857f112102..41c8b45113 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -72,6 +72,7 @@ #include "servers/rendering/shader_types.h" #include "text_server.h" #include "xr/xr_interface.h" +#include "xr/xr_interface_extension.h" #include "xr/xr_positional_tracker.h" #include "xr_server.h" @@ -138,10 +139,11 @@ void register_server_types() { GDREGISTER_VIRTUAL_CLASS(RenderingDevice); GDREGISTER_VIRTUAL_CLASS(XRInterface); + GDREGISTER_CLASS(XRInterfaceExtension); // can't register this as virtual because we need a creation function for our extensions. GDREGISTER_CLASS(XRPositionalTracker); - GDREGISTER_VIRTUAL_CLASS(AudioStream); - GDREGISTER_VIRTUAL_CLASS(AudioStreamPlayback); + GDREGISTER_CLASS(AudioStream); + GDREGISTER_CLASS(AudioStreamPlayback); GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackResampled); GDREGISTER_CLASS(AudioStreamMicrophone); GDREGISTER_CLASS(AudioStreamRandomPitch); diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index fd7d5b91fa..efa3a457d3 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -806,6 +806,40 @@ void RendererCanvasCull::canvas_item_add_texture_rect(RID p_item, const Rect2 &p rect->texture = p_texture; } +void RendererCanvasCull::canvas_item_add_msdf_texture_rect_region(RID p_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate, int p_outline_size, float p_px_range) { + Item *canvas_item = canvas_item_owner.getornull(p_item); + ERR_FAIL_COND(!canvas_item); + + Item::CommandRect *rect = canvas_item->alloc_command<Item::CommandRect>(); + ERR_FAIL_COND(!rect); + rect->modulate = p_modulate; + rect->rect = p_rect; + + rect->texture = p_texture; + + rect->source = p_src_rect; + rect->flags = RendererCanvasRender::CANVAS_RECT_REGION | RendererCanvasRender::CANVAS_RECT_MSDF; + + if (p_rect.size.x < 0) { + rect->flags |= RendererCanvasRender::CANVAS_RECT_FLIP_H; + rect->rect.size.x = -rect->rect.size.x; + } + if (p_src_rect.size.x < 0) { + rect->flags ^= RendererCanvasRender::CANVAS_RECT_FLIP_H; + rect->source.size.x = -rect->source.size.x; + } + if (p_rect.size.y < 0) { + rect->flags |= RendererCanvasRender::CANVAS_RECT_FLIP_V; + rect->rect.size.y = -rect->rect.size.y; + } + if (p_src_rect.size.y < 0) { + rect->flags ^= RendererCanvasRender::CANVAS_RECT_FLIP_V; + rect->source.size.y = -rect->source.size.y; + } + rect->outline = p_outline_size; + rect->px_range = p_px_range; +} + void RendererCanvasCull::canvas_item_add_texture_rect_region(RID p_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) { Item *canvas_item = canvas_item_owner.getornull(p_item); ERR_FAIL_COND(!canvas_item); diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h index 79b5450d14..5e343dcf02 100644 --- a/servers/rendering/renderer_canvas_cull.h +++ b/servers/rendering/renderer_canvas_cull.h @@ -222,6 +222,7 @@ public: void canvas_item_add_circle(RID p_item, const Point2 &p_pos, float p_radius, const Color &p_color); void canvas_item_add_texture_rect(RID p_item, const Rect2 &p_rect, RID p_texture, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false); void canvas_item_add_texture_rect_region(RID p_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = false); + void canvas_item_add_msdf_texture_rect_region(RID p_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, float p_px_range = 1.0); void canvas_item_add_nine_patch(RID p_item, const Rect2 &p_rect, const Rect2 &p_source, RID p_texture, const Vector2 &p_topleft, const Vector2 &p_bottomright, RS::NinePatchAxisMode p_x_axis_mode = RS::NINE_PATCH_STRETCH, RS::NinePatchAxisMode p_y_axis_mode = RS::NINE_PATCH_STRETCH, bool p_draw_center = true, const Color &p_modulate = Color(1, 1, 1)); void canvas_item_add_primitive(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs, RID p_texture, float p_width = 1.0); void canvas_item_add_polygon(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), RID p_texture = RID()); diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h index 8afe9ef410..04ddae4089 100644 --- a/servers/rendering/renderer_canvas_render.h +++ b/servers/rendering/renderer_canvas_render.h @@ -45,6 +45,7 @@ public: CANVAS_RECT_TRANSPOSE = 16, CANVAS_RECT_CLIP_UV = 32, CANVAS_RECT_IS_GROUP = 64, + CANVAS_RECT_MSDF = 128, }; struct Light { @@ -193,11 +194,15 @@ public: Color modulate; Rect2 source; uint8_t flags; + float outline; + float px_range; RID texture; CommandRect() { flags = 0; + outline = 0; + px_range = 1; type = TYPE_RECT; } }; diff --git a/servers/rendering/renderer_compositor.h b/servers/rendering/renderer_compositor.h index 6206849b66..1971c3e781 100644 --- a/servers/rendering/renderer_compositor.h +++ b/servers/rendering/renderer_compositor.h @@ -41,7 +41,8 @@ class RendererSceneRender; struct BlitToScreen { RID render_target; - Rect2i rect; + Rect2 src_rect = Rect2(0.0, 0.0, 1.0, 1.0); + Rect2i dst_rect; struct { bool use_layer = false; diff --git a/servers/rendering/renderer_rd/effects_rd.cpp b/servers/rendering/renderer_rd/effects_rd.cpp index 3683622d3e..236eb5e596 100644 --- a/servers/rendering/renderer_rd/effects_rd.cpp +++ b/servers/rendering/renderer_rd/effects_rd.cpp @@ -812,6 +812,7 @@ void EffectsRD::tonemapper(RID p_source_color, RID p_dst_framebuffer, const Tone tonemap.push_constant.exposure = p_settings.exposure; tonemap.push_constant.white = p_settings.white; tonemap.push_constant.auto_exposure_grey = p_settings.auto_exposure_grey; + tonemap.push_constant.luminance_multiplier = p_settings.luminance_multiplier; tonemap.push_constant.use_color_correction = p_settings.use_color_correction; @@ -864,6 +865,7 @@ void EffectsRD::tonemapper(RD::DrawListID p_subpass_draw_list, RID p_source_colo tonemap.push_constant.use_color_correction = p_settings.use_color_correction; tonemap.push_constant.use_debanding = p_settings.use_debanding; + tonemap.push_constant.luminance_multiplier = p_settings.luminance_multiplier; RD::get_singleton()->draw_list_bind_render_pipeline(p_subpass_draw_list, tonemap.pipelines[mode].get_render_pipeline(RD::INVALID_ID, p_dst_format_id, false, RD::get_singleton()->draw_list_get_current_pass())); RD::get_singleton()->draw_list_bind_uniform_set(p_subpass_draw_list, _get_uniform_set_for_input(p_source_color), 0); @@ -2171,10 +2173,8 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) { for (int pass = 0; pass < 4; pass++) { for (int subPass = 0; subPass < sub_pass_count; subPass++) { int a = pass; - int b = subPass; - int spmap[5]{ 0, 1, 4, 3, 2 }; - b = spmap[subPass]; + int b = spmap[subPass]; float ca, sa; float angle0 = (float(a) + float(b) / float(sub_pass_count)) * Math_PI * 0.5f; diff --git a/servers/rendering/renderer_rd/effects_rd.h b/servers/rendering/renderer_rd/effects_rd.h index c8d4cb7ad4..0db0919dbc 100644 --- a/servers/rendering/renderer_rd/effects_rd.h +++ b/servers/rendering/renderer_rd/effects_rd.h @@ -255,7 +255,7 @@ private: float exposure; // 4 - 84 float white; // 4 - 88 float auto_exposure_grey; // 4 - 92 - uint32_t pad2; // 4 - 96 + float luminance_multiplier; // 4 - 96 float pixel_size[2]; // 8 - 104 uint32_t use_fxaa; // 4 - 108 @@ -308,7 +308,7 @@ private: float exposure_adjust; float min_luminance; float max_luminance; - float pad[1]; + uint32_t pad1; }; struct LuminanceReduceFragment { @@ -818,6 +818,7 @@ public: bool use_auto_exposure = false; float auto_exposure_grey = 0.5; RID exposure_texture; + float luminance_multiplier = 1.0; bool use_bcs = false; float brightness = 1.0; diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 611f7c6494..1b730567d9 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -484,7 +484,7 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p if (material_uniform_set != prev_material_uniform_set) { // Update uniform set. - if (RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set. + if (material_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set. RD::get_singleton()->draw_list_bind_uniform_set(draw_list, material_uniform_set, MATERIAL_UNIFORM_SET); } diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index be18a73989..d0f02b44cb 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp @@ -601,10 +601,10 @@ void SceneShaderForwardClustered::init(RendererStorageRD *p_storage, const Strin actions.usage_defines["UV2"] = "#define UV2_USED\n"; actions.usage_defines["BONE_INDICES"] = "#define BONES_USED\n"; actions.usage_defines["BONE_WEIGHTS"] = "#define WEIGHTS_USED\n"; - actions.usage_defines["CUSTOM0"] = "#define CUSTOM0\n"; - actions.usage_defines["CUSTOM1"] = "#define CUSTOM1\n"; - actions.usage_defines["CUSTOM2"] = "#define CUSTOM2\n"; - actions.usage_defines["CUSTOM3"] = "#define CUSTOM3\n"; + actions.usage_defines["CUSTOM0"] = "#define CUSTOM0_USED\n"; + actions.usage_defines["CUSTOM1"] = "#define CUSTOM1_USED\n"; + actions.usage_defines["CUSTOM2"] = "#define CUSTOM2_USED\n"; + actions.usage_defines["CUSTOM3"] = "#define CUSTOM3_USED\n"; actions.usage_defines["NORMAL_MAP"] = "#define NORMAL_MAP_USED\n"; actions.usage_defines["NORMAL_MAP_DEPTH"] = "@NORMAL_MAP"; actions.usage_defines["COLOR"] = "#define COLOR_USED\n"; @@ -683,6 +683,8 @@ void SceneShaderForwardClustered::init(RendererStorageRD *p_storage, const Strin default_shader = storage->shader_allocate(); storage->shader_initialize(default_shader); storage->shader_set_code(default_shader, R"( +// Default 3D material shader (clustered). + shader_type spatial; void vertex() { @@ -712,6 +714,8 @@ void fragment() { storage->shader_initialize(overdraw_material_shader); // Use relatively low opacity so that more "layers" of overlapping objects can be distinguished. storage->shader_set_code(overdraw_material_shader, R"( +// 3D editor Overdraw debug draw mode shader (clustered). + shader_type spatial; render_mode blend_add, unshaded; diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 2064d9c5c5..276a44bc27 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -86,12 +86,13 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::clear() { void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_buffer, RID p_depth_buffer, RID p_target_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa, uint32_t p_view_count) { clear(); - bool is_half_resolution = false; // Set this once we support this feature. - msaa = p_msaa; + Size2i target_size = RD::get_singleton()->texture_size(p_target_buffer); + width = p_width; height = p_height; + bool is_scaled = (target_size.width != p_width) || (target_size.height != p_height); view_count = p_view_count; color = p_color_buffer; @@ -124,7 +125,7 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_b passes.push_back(pass); color_fbs[FB_CONFIG_THREE_SUBPASSES] = RD::get_singleton()->framebuffer_create_multipass(fb, passes, RenderingDevice::INVALID_ID, view_count); - if (!is_half_resolution) { + if (!is_scaled) { // - add blit to 2D pass fb.push_back(p_target_buffer); // 2 - target buffer @@ -211,7 +212,7 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_b color_fbs[FB_CONFIG_ONE_PASS] = RD::get_singleton()->framebuffer_create_multipass(fb, one_pass_with_resolve, RenderingDevice::INVALID_ID, view_count); } - if (!is_half_resolution) { + if (!is_scaled) { // - add blit to 2D pass fb.push_back(p_target_buffer); // 3 - target buffer RD::FramebufferPass blit_pass; @@ -271,6 +272,12 @@ bool RenderForwardMobile::free(RID p_rid) { /* Render functions */ +float RenderForwardMobile::_render_buffers_get_luminance_multiplier() { + // On mobile renderer we need to multiply source colors by 2 due to using a UNORM buffer + // and multiplying by the output color during 3D rendering by 0.5 + return 2.0; +} + RD::DataFormat RenderForwardMobile::_render_buffers_get_color_format() { // Using 32bit buffers enables AFBC on mobile devices which should have a definite performance improvement (MALI G710 and newer support this on 64bit RTs) return RD::DATA_FORMAT_A2B10G10R10_UNORM_PACK32; @@ -491,7 +498,6 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color bool using_subpass_transparent = true; bool using_subpass_post_process = true; - bool is_half_resolution = false; // Set this once we support this feature. bool using_ssr = false; // I don't think we support this in our mobile renderer so probably should phase it out bool using_sss = false; // I don't think we support this in our mobile renderer so probably should phase it out @@ -512,7 +518,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color screen_size.x = render_buffer->width; screen_size.y = render_buffer->height; - if (is_half_resolution) { + if (render_buffer->color_fbs[FB_CONFIG_FOUR_SUBPASSES].is_null()) { // can't do blit subpass using_subpass_post_process = false; } else if (env && (env->glow_enabled || env->auto_exposure || camera_effects_uses_dof(p_render_data->camera_effects))) { @@ -631,7 +637,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color RID sky_rid = env->sky; if (sky_rid.is_valid()) { - sky.update(env, projection, p_render_data->cam_transform, time); + sky.update(env, projection, p_render_data->cam_transform, time, _render_buffers_get_luminance_multiplier()); radiance_texture = sky.sky_get_radiance_texture_rd(sky_rid); } else { // do not try to draw sky if invalid @@ -750,9 +756,9 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color CameraMatrix correction; correction.set_depth_correction(true); CameraMatrix projection = correction * p_render_data->cam_projection; - sky.draw(draw_list, env, framebuffer, 1, &projection, p_render_data->cam_transform, time); + sky.draw(draw_list, env, framebuffer, 1, &projection, p_render_data->cam_transform, time, _render_buffers_get_luminance_multiplier()); } else { - sky.draw(draw_list, env, framebuffer, p_render_data->view_count, p_render_data->view_projection, p_render_data->cam_transform, time); + sky.draw(draw_list, env, framebuffer, p_render_data->view_count, p_render_data->view_projection, p_render_data->cam_transform, time, _render_buffers_get_luminance_multiplier()); } RD::get_singleton()->draw_command_end_label(); // Draw Sky Subpass @@ -1976,7 +1982,7 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr if (material_uniform_set != prev_material_uniform_set) { // Update uniform set. - if (RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set. + if (material_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set. RD::get_singleton()->draw_list_bind_uniform_set(draw_list, material_uniform_set, MATERIAL_UNIFORM_SET); } diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h index 764d8e80df..38f80c5347 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h @@ -202,6 +202,7 @@ protected: } }; + virtual float _render_buffers_get_luminance_multiplier() override; virtual RD::DataFormat _render_buffers_get_color_format() override; virtual bool _render_buffers_can_be_storage() override; diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index 735014a2ec..cd314d8c56 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -593,10 +593,10 @@ void SceneShaderForwardMobile::init(RendererStorageRD *p_storage, const String p actions.usage_defines["UV2"] = "#define UV2_USED\n"; actions.usage_defines["BONE_INDICES"] = "#define BONES_USED\n"; actions.usage_defines["BONE_WEIGHTS"] = "#define WEIGHTS_USED\n"; - actions.usage_defines["CUSTOM0"] = "#define CUSTOM0\n"; - actions.usage_defines["CUSTOM1"] = "#define CUSTOM1\n"; - actions.usage_defines["CUSTOM2"] = "#define CUSTOM2\n"; - actions.usage_defines["CUSTOM3"] = "#define CUSTOM3\n"; + actions.usage_defines["CUSTOM0"] = "#define CUSTOM0_USED\n"; + actions.usage_defines["CUSTOM1"] = "#define CUSTOM1_USED\n"; + actions.usage_defines["CUSTOM2"] = "#define CUSTOM2_USED\n"; + actions.usage_defines["CUSTOM3"] = "#define CUSTOM3_USED\n"; actions.usage_defines["NORMAL_MAP"] = "#define NORMAL_MAP_USED\n"; actions.usage_defines["NORMAL_MAP_DEPTH"] = "@NORMAL_MAP"; actions.usage_defines["COLOR"] = "#define COLOR_USED\n"; @@ -665,6 +665,8 @@ void SceneShaderForwardMobile::init(RendererStorageRD *p_storage, const String p actions.global_buffer_array_variable = "global_variables.data"; actions.instance_uniform_index_variable = "draw_call.instance_uniforms_ofs"; + actions.apply_luminance_multiplier = true; // apply luminance multiplier to screen texture + compiler.initialize(actions); } @@ -673,6 +675,8 @@ void SceneShaderForwardMobile::init(RendererStorageRD *p_storage, const String p default_shader = storage->shader_allocate(); storage->shader_initialize(default_shader); storage->shader_set_code(default_shader, R"( +// Default 3D material shader (mobile). + shader_type spatial; void vertex() { @@ -701,6 +705,8 @@ void fragment() { storage->shader_initialize(overdraw_material_shader); // Use relatively low opacity so that more "layers" of overlapping objects can be distinguished. storage->shader_set_code(overdraw_material_shader, R"( +// 3D editor Overdraw debug draw mode shader (mobile). + shader_type spatial; render_mode blend_add, unshaded; diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index f8aefdb29c..3c66fadbe9 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -541,6 +541,14 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend src_rect = Rect2(0, 0, 1, 1); } + if (rect->flags & CANVAS_RECT_MSDF) { + push_constant.flags |= FLAGS_USE_MSDF; + push_constant.msdf[0] = rect->px_range; // Pixel range. + push_constant.msdf[1] = rect->outline; // Outline size. + push_constant.msdf[2] = 0.f; // Reserved. + push_constant.msdf[3] = 0.f; // Reserved. + } + push_constant.modulation[0] = rect->modulate.r * base_color.r; push_constant.modulation[1] = rect->modulate.g * base_color.g; push_constant.modulation[2] = rect->modulate.b * base_color.b; @@ -1078,7 +1086,7 @@ void RendererCanvasRenderRD::_render_items(RID p_to_render_target, int p_item_co } } - RID material = ci->material; + RID material = ci->material_owner == nullptr ? ci->material : ci->material_owner->material; if (material.is_null() && ci->canvas_group != nullptr) { material = default_canvas_group_material; @@ -1094,7 +1102,7 @@ void RendererCanvasRenderRD::_render_items(RID p_to_render_target, int p_item_co if (material_data->shader_data->version.is_valid() && material_data->shader_data->valid) { pipeline_variants = &material_data->shader_data->pipeline_variants; // Update uniform set. - if (RD::get_singleton()->uniform_set_is_valid(material_data->uniform_set)) { // Material may not have a uniform set. + if (material_data->uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(material_data->uniform_set)) { // Material may not have a uniform set. RD::get_singleton()->draw_list_bind_uniform_set(draw_list, material_data->uniform_set, MATERIAL_UNIFORM_SET); } } else { @@ -1346,8 +1354,10 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p } } - if (ci->material.is_valid()) { - MaterialData *md = (MaterialData *)storage->material_get_data(ci->material, RendererStorageRD::SHADER_TYPE_2D); + RID material = ci->material_owner == nullptr ? ci->material : ci->material_owner->material; + + if (material.is_valid()) { + MaterialData *md = (MaterialData *)storage->material_get_data(material, RendererStorageRD::SHADER_TYPE_2D); if (md && md->shader_data->valid) { if (md->shader_data->uses_screen_texture && canvas_group_owner == nullptr) { if (!material_screen_texture_found) { @@ -1367,7 +1377,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p if (!RD::get_singleton()->uniform_set_is_valid(md->uniform_set)) { // uniform set may be gone because a dependency was erased. In this case, it will happen // if a texture is deleted, so just re-create it. - storage->material_force_update_textures(ci->material, RendererStorageRD::SHADER_TYPE_2D); + storage->material_force_update_textures(material, RendererStorageRD::SHADER_TYPE_2D); } } } @@ -2575,6 +2585,8 @@ RendererCanvasRenderRD::RendererCanvasRenderRD(RendererStorageRD *p_storage) { storage->shader_initialize(default_canvas_group_shader); storage->shader_set_code(default_canvas_group_shader, R"( +// Default CanvasGroup shader. + shader_type canvas_item; void fragment() { diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h index 7c4f62832c..ec7d7e2854 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h @@ -84,8 +84,9 @@ class RendererCanvasRenderRD : public RendererCanvasRender { FLAGS_LIGHT_COUNT_SHIFT = 20, FLAGS_DEFAULT_NORMAL_MAP_USED = (1 << 26), - FLAGS_DEFAULT_SPECULAR_MAP_USED = (1 << 27) + FLAGS_DEFAULT_SPECULAR_MAP_USED = (1 << 27), + FLAGS_USE_MSDF = (1 << 28), }; enum { @@ -388,7 +389,10 @@ class RendererCanvasRenderRD : public RendererCanvasRender { //rect struct { float modulation[4]; - float ninepatch_margins[4]; + union { + float msdf[4]; + float ninepatch_margins[4]; + }; float dst_rect[4]; float src_rect[4]; float pad[2]; diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp index 62e9386f95..c53c202bab 100644 --- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp @@ -46,6 +46,8 @@ void RendererCompositorRD::blit_render_targets_to_screen(DisplayServer::WindowID RID rd_texture = storage->texture_get_rd_texture(texture); ERR_CONTINUE(rd_texture.is_null()); + // TODO if keep_3d_linear was set when rendering to this render target we need to add a linear->sRGB conversion in. + if (!render_target_descriptors.has(rd_texture) || !RD::get_singleton()->uniform_set_is_valid(render_target_descriptors[rd_texture])) { Vector<RD::Uniform> uniforms; RD::Uniform u; @@ -65,10 +67,14 @@ void RendererCompositorRD::blit_render_targets_to_screen(DisplayServer::WindowID RD::get_singleton()->draw_list_bind_index_array(draw_list, blit.array); RD::get_singleton()->draw_list_bind_uniform_set(draw_list, render_target_descriptors[rd_texture], 0); - blit.push_constant.rect[0] = p_render_targets[i].rect.position.x / screen_size.width; - blit.push_constant.rect[1] = p_render_targets[i].rect.position.y / screen_size.height; - blit.push_constant.rect[2] = p_render_targets[i].rect.size.width / screen_size.width; - blit.push_constant.rect[3] = p_render_targets[i].rect.size.height / screen_size.height; + blit.push_constant.src_rect[0] = p_render_targets[i].src_rect.position.x; + blit.push_constant.src_rect[1] = p_render_targets[i].src_rect.position.y; + blit.push_constant.src_rect[2] = p_render_targets[i].src_rect.size.width; + blit.push_constant.src_rect[3] = p_render_targets[i].src_rect.size.height; + blit.push_constant.dst_rect[0] = p_render_targets[i].dst_rect.position.x / screen_size.width; + blit.push_constant.dst_rect[1] = p_render_targets[i].dst_rect.position.y / screen_size.height; + blit.push_constant.dst_rect[2] = p_render_targets[i].dst_rect.size.width / screen_size.width; + blit.push_constant.dst_rect[3] = p_render_targets[i].dst_rect.size.height / screen_size.height; blit.push_constant.layer = p_render_targets[i].multi_view.layer; blit.push_constant.eye_center[0] = p_render_targets[i].lens_distortion.eye_center.x; blit.push_constant.eye_center[1] = p_render_targets[i].lens_distortion.eye_center.y; @@ -203,10 +209,14 @@ void RendererCompositorRD::set_boot_image(const Ref<Image> &p_image, const Color RD::get_singleton()->draw_list_bind_index_array(draw_list, blit.array); RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uset, 0); - blit.push_constant.rect[0] = screenrect.position.x; - blit.push_constant.rect[1] = screenrect.position.y; - blit.push_constant.rect[2] = screenrect.size.width; - blit.push_constant.rect[3] = screenrect.size.height; + blit.push_constant.src_rect[0] = 0.0; + blit.push_constant.src_rect[1] = 0.0; + blit.push_constant.src_rect[2] = 1.0; + blit.push_constant.src_rect[3] = 1.0; + blit.push_constant.dst_rect[0] = screenrect.position.x; + blit.push_constant.dst_rect[1] = screenrect.position.y; + blit.push_constant.dst_rect[2] = screenrect.size.width; + blit.push_constant.dst_rect[3] = screenrect.size.height; blit.push_constant.layer = 0; blit.push_constant.eye_center[0] = 0; blit.push_constant.eye_center[1] = 0; diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.h b/servers/rendering/renderer_rd/renderer_compositor_rd.h index 8639362da9..0230c46800 100644 --- a/servers/rendering/renderer_rd/renderer_compositor_rd.h +++ b/servers/rendering/renderer_rd/renderer_compositor_rd.h @@ -55,7 +55,8 @@ protected: }; struct BlitPushConstant { - float rect[4]; + float src_rect[4]; + float dst_rect[4]; float eye_center[2]; float k1; diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp index 098e2a5c87..36943c5e5c 100644 --- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp @@ -1469,7 +1469,7 @@ void RendererSceneGIRD::SDFGI::pre_process_gi(const Transform3D &p_transform, Re lights[idx].color[1] = color.g; lights[idx].color[2] = color.b; lights[idx].type = RS::LIGHT_DIRECTIONAL; - lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY); + lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY) * storage->light_get_param(li->light, RS::LIGHT_PARAM_INDIRECT_ENERGY); lights[idx].has_shadow = storage->light_has_shadow(li->light); idx++; @@ -1514,7 +1514,7 @@ void RendererSceneGIRD::SDFGI::pre_process_gi(const Transform3D &p_transform, Re lights[idx].color[1] = color.g; lights[idx].color[2] = color.b; lights[idx].type = storage->light_get_type(li->light); - lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY); + lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY) * storage->light_get_param(li->light, RS::LIGHT_PARAM_INDIRECT_ENERGY); lights[idx].has_shadow = storage->light_has_shadow(li->light); lights[idx].attenuation = storage->light_get_param(li->light, RS::LIGHT_PARAM_ATTENUATION); lights[idx].radius = storage->light_get_param(li->light, RS::LIGHT_PARAM_RANGE); @@ -1953,7 +1953,7 @@ void RendererSceneGIRD::SDFGI::render_static_lights(RID p_render_buffers, uint32 lights[idx].color[0] = color.r; lights[idx].color[1] = color.g; lights[idx].color[2] = color.b; - lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY); + lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY) * storage->light_get_param(li->light, RS::LIGHT_PARAM_INDIRECT_ENERGY); lights[idx].has_shadow = storage->light_has_shadow(li->light); lights[idx].attenuation = storage->light_get_param(li->light, RS::LIGHT_PARAM_ATTENUATION); lights[idx].radius = storage->light_get_param(li->light, RS::LIGHT_PARAM_RANGE); diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 8496ef631b..fa66ed85a9 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -2237,6 +2237,7 @@ void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(const Rende } } + tonemap.luminance_multiplier = _render_buffers_get_luminance_multiplier(); tonemap.view_count = p_render_data->view_count; storage->get_effects()->tonemapper(rb->texture, storage->render_target_get_rd_framebuffer(rb->render_target), tonemap); @@ -2301,6 +2302,7 @@ void RendererSceneRenderRD::_post_process_subpass(RID p_source_texture, RID p_fr tonemap.use_debanding = rb->use_debanding; tonemap.texture_size = Vector2i(rb->width, rb->height); + tonemap.luminance_multiplier = _render_buffers_get_luminance_multiplier(); tonemap.view_count = p_render_data->view_count; storage->get_effects()->tonemapper(draw_list, p_source_texture, RD::get_singleton()->framebuffer_get_format(p_framebuffer), tonemap); @@ -2573,6 +2575,10 @@ float RendererSceneRenderRD::render_buffers_get_volumetric_fog_detail_spread(RID return rb->volumetric_fog->spread; } +float RendererSceneRenderRD::_render_buffers_get_luminance_multiplier() { + return 1.0; +} + RD::DataFormat RendererSceneRenderRD::_render_buffers_get_color_format() { return RD::DATA_FORMAT_R16G16B16A16_SFLOAT; } @@ -2585,6 +2591,8 @@ void RendererSceneRenderRD::render_buffers_configure(RID p_render_buffers, RID p ERR_FAIL_COND_MSG(p_view_count == 0, "Must have at least 1 view"); RenderBuffers *rb = render_buffers_owner.getornull(p_render_buffers); + + // Should we add an overrule per viewport? rb->width = p_width; rb->height = p_height; rb->render_target = p_render_target; @@ -2631,8 +2639,8 @@ void RendererSceneRenderRD::render_buffers_configure(RID p_render_buffers, RID p tf.format = RD::DATA_FORMAT_R32_SFLOAT; } - tf.width = p_width; - tf.height = p_height; + tf.width = rb->width; + tf.height = rb->height; tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT; tf.array_layers = rb->view_count; // create a layer for every view @@ -2654,10 +2662,10 @@ void RendererSceneRenderRD::render_buffers_configure(RID p_render_buffers, RID p } RID target_texture = storage->render_target_get_rd_texture(rb->render_target); - rb->data->configure(rb->texture, rb->depth_texture, target_texture, p_width, p_height, p_msaa, p_view_count); + rb->data->configure(rb->texture, rb->depth_texture, target_texture, rb->width, rb->height, p_msaa, p_view_count); if (is_clustered_enabled()) { - rb->cluster_builder->setup(Size2i(p_width, p_height), max_cluster_elements, rb->depth_texture, storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED), rb->texture); + rb->cluster_builder->setup(Size2i(rb->width, rb->height), max_cluster_elements, rb->depth_texture, storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED), rb->texture); } } diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h index 37533baecf..eb61af517a 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h @@ -1190,6 +1190,7 @@ public: /* render buffers */ + virtual float _render_buffers_get_luminance_multiplier(); virtual RD::DataFormat _render_buffers_get_color_format(); virtual bool _render_buffers_can_be_storage(); virtual RID render_buffers_create() override; diff --git a/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp index 9e85608f1e..c388da755c 100644 --- a/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp @@ -259,7 +259,7 @@ static _FORCE_INLINE_ void store_transform_3x3(const Basis &p_basis, float *p_ar p_array[11] = 0; } -void RendererSceneSkyRD::_render_sky(RD::DrawListID p_list, float p_time, RID p_fb, PipelineCacheRD *p_pipeline, RID p_uniform_set, RID p_texture_set, uint32_t p_view_count, const CameraMatrix *p_projections, const Basis &p_orientation, float p_multiplier, const Vector3 &p_position) { +void RendererSceneSkyRD::_render_sky(RD::DrawListID p_list, float p_time, RID p_fb, PipelineCacheRD *p_pipeline, RID p_uniform_set, RID p_texture_set, uint32_t p_view_count, const CameraMatrix *p_projections, const Basis &p_orientation, float p_multiplier, const Vector3 &p_position, float p_luminance_multiplier) { SkyPushConstant sky_push_constant; memset(&sky_push_constant, 0, sizeof(SkyPushConstant)); @@ -276,6 +276,7 @@ void RendererSceneSkyRD::_render_sky(RD::DrawListID p_list, float p_time, RID p_ sky_push_constant.position[2] = p_position.z; sky_push_constant.multiplier = p_multiplier; sky_push_constant.time = p_time; + sky_push_constant.luminance_multiplier = p_luminance_multiplier; store_transform_3x3(p_orientation, sky_push_constant.orientation); RenderingDevice::FramebufferFormatID fb_format = RD::get_singleton()->framebuffer_get_format(p_fb); @@ -287,7 +288,7 @@ void RendererSceneSkyRD::_render_sky(RD::DrawListID p_list, float p_time, RID p_ // Update uniform sets. { RD::get_singleton()->draw_list_bind_uniform_set(draw_list, sky_scene_state.uniform_set, 0); - if (RD::get_singleton()->uniform_set_is_valid(p_uniform_set)) { // Material may not have a uniform set. + if (p_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(p_uniform_set)) { // Material may not have a uniform set. RD::get_singleton()->draw_list_bind_uniform_set(draw_list, p_uniform_set, 1); } RD::get_singleton()->draw_list_bind_uniform_set(draw_list, p_texture_set, 2); @@ -855,6 +856,8 @@ void RendererSceneSkyRD::init(RendererStorageRD *p_storage) { storage->shader_initialize(sky_shader.default_shader); storage->shader_set_code(sky_shader.default_shader, R"( +// Default sky shader. + shader_type sky; void sky() { @@ -942,6 +945,8 @@ void sky() { storage->shader_initialize(sky_scene_state.fog_shader); storage->shader_set_code(sky_scene_state.fog_shader, R"( +// Default clear color sky shader. + shader_type sky; uniform vec4 clear_color; @@ -1191,7 +1196,7 @@ void RendererSceneSkyRD::setup(RendererSceneEnvironmentRD *p_env, RID p_render_b RD::get_singleton()->buffer_update(sky_scene_state.uniform_buffer, 0, sizeof(SkySceneState::UBO), &sky_scene_state.ubo); } -void RendererSceneSkyRD::update(RendererSceneEnvironmentRD *p_env, const CameraMatrix &p_projection, const Transform3D &p_transform, double p_time) { +void RendererSceneSkyRD::update(RendererSceneEnvironmentRD *p_env, const CameraMatrix &p_projection, const Transform3D &p_transform, double p_time, float p_luminance_multiplier) { ERR_FAIL_COND(!p_env); Sky *sky = get_sky(p_env->sky); @@ -1283,7 +1288,7 @@ void RendererSceneSkyRD::update(RendererSceneEnvironmentRD *p_env, const CameraM RID texture_uniform_set = sky->get_textures(storage, SKY_TEXTURE_SET_CUBEMAP_QUARTER_RES, sky_shader.default_shader_rd); cubemap_draw_list = RD::get_singleton()->draw_list_begin(sky->reflection.layers[0].mipmaps[2].framebuffers[i], RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD); - _render_sky(cubemap_draw_list, p_time, sky->reflection.layers[0].mipmaps[2].framebuffers[i], pipeline, material->uniform_set, texture_uniform_set, 1, &cm, local_view, multiplier, p_transform.origin); + _render_sky(cubemap_draw_list, p_time, sky->reflection.layers[0].mipmaps[2].framebuffers[i], pipeline, material->uniform_set, texture_uniform_set, 1, &cm, local_view, multiplier, p_transform.origin, p_luminance_multiplier); RD::get_singleton()->draw_list_end(); } RD::get_singleton()->draw_command_end_label(); @@ -1302,7 +1307,7 @@ void RendererSceneSkyRD::update(RendererSceneEnvironmentRD *p_env, const CameraM RID texture_uniform_set = sky->get_textures(storage, SKY_TEXTURE_SET_CUBEMAP_HALF_RES, sky_shader.default_shader_rd); cubemap_draw_list = RD::get_singleton()->draw_list_begin(sky->reflection.layers[0].mipmaps[1].framebuffers[i], RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD); - _render_sky(cubemap_draw_list, p_time, sky->reflection.layers[0].mipmaps[1].framebuffers[i], pipeline, material->uniform_set, texture_uniform_set, 1, &cm, local_view, multiplier, p_transform.origin); + _render_sky(cubemap_draw_list, p_time, sky->reflection.layers[0].mipmaps[1].framebuffers[i], pipeline, material->uniform_set, texture_uniform_set, 1, &cm, local_view, multiplier, p_transform.origin, p_luminance_multiplier); RD::get_singleton()->draw_list_end(); } RD::get_singleton()->draw_command_end_label(); @@ -1317,7 +1322,7 @@ void RendererSceneSkyRD::update(RendererSceneEnvironmentRD *p_env, const CameraM RID texture_uniform_set = sky->get_textures(storage, SKY_TEXTURE_SET_CUBEMAP, sky_shader.default_shader_rd); cubemap_draw_list = RD::get_singleton()->draw_list_begin(sky->reflection.layers[0].mipmaps[0].framebuffers[i], RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD); - _render_sky(cubemap_draw_list, p_time, sky->reflection.layers[0].mipmaps[0].framebuffers[i], pipeline, material->uniform_set, texture_uniform_set, 1, &cm, local_view, multiplier, p_transform.origin); + _render_sky(cubemap_draw_list, p_time, sky->reflection.layers[0].mipmaps[0].framebuffers[i], pipeline, material->uniform_set, texture_uniform_set, 1, &cm, local_view, multiplier, p_transform.origin, p_luminance_multiplier); RD::get_singleton()->draw_list_end(); } RD::get_singleton()->draw_command_end_label(); @@ -1435,7 +1440,7 @@ void RendererSceneSkyRD::draw(RendererSceneEnvironmentRD *p_env, bool p_can_cont clear_colors.push_back(Color(0.0, 0.0, 0.0)); RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(sky->quarter_res_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); - _render_sky(draw_list, p_time, sky->quarter_res_framebuffer, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin); + _render_sky(draw_list, p_time, sky->quarter_res_framebuffer, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin, 1.0); RD::get_singleton()->draw_list_end(); } @@ -1448,7 +1453,7 @@ void RendererSceneSkyRD::draw(RendererSceneEnvironmentRD *p_env, bool p_can_cont clear_colors.push_back(Color(0.0, 0.0, 0.0)); RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(sky->half_res_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); - _render_sky(draw_list, p_time, sky->half_res_framebuffer, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin); + _render_sky(draw_list, p_time, sky->half_res_framebuffer, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin, 1.0); RD::get_singleton()->draw_list_end(); } @@ -1462,11 +1467,11 @@ void RendererSceneSkyRD::draw(RendererSceneEnvironmentRD *p_env, bool p_can_cont } RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_fb, RD::INITIAL_ACTION_CONTINUE, p_can_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CONTINUE, p_can_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ); - _render_sky(draw_list, p_time, p_fb, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin); + _render_sky(draw_list, p_time, p_fb, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin, 1.0); RD::get_singleton()->draw_list_end(); } -void RendererSceneSkyRD::update_res_buffers(RendererSceneEnvironmentRD *p_env, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time) { +void RendererSceneSkyRD::update_res_buffers(RendererSceneEnvironmentRD *p_env, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time, float p_luminance_multiplier) { ERR_FAIL_COND(!p_env); ERR_FAIL_COND(p_view_count == 0); @@ -1542,7 +1547,7 @@ void RendererSceneSkyRD::update_res_buffers(RendererSceneEnvironmentRD *p_env, u clear_colors.push_back(Color(0.0, 0.0, 0.0)); RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(sky->quarter_res_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); - _render_sky(draw_list, p_time, sky->quarter_res_framebuffer, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin); + _render_sky(draw_list, p_time, sky->quarter_res_framebuffer, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin, p_luminance_multiplier); RD::get_singleton()->draw_list_end(); } @@ -1555,12 +1560,12 @@ void RendererSceneSkyRD::update_res_buffers(RendererSceneEnvironmentRD *p_env, u clear_colors.push_back(Color(0.0, 0.0, 0.0)); RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(sky->half_res_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); - _render_sky(draw_list, p_time, sky->half_res_framebuffer, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin); + _render_sky(draw_list, p_time, sky->half_res_framebuffer, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin, p_luminance_multiplier); RD::get_singleton()->draw_list_end(); } } -void RendererSceneSkyRD::draw(RD::DrawListID p_draw_list, RendererSceneEnvironmentRD *p_env, RID p_fb, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time) { +void RendererSceneSkyRD::draw(RD::DrawListID p_draw_list, RendererSceneEnvironmentRD *p_env, RID p_fb, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time, float p_luminance_multiplier) { ERR_FAIL_COND(!p_env); ERR_FAIL_COND(p_view_count == 0); @@ -1636,7 +1641,7 @@ void RendererSceneSkyRD::draw(RD::DrawListID p_draw_list, RendererSceneEnvironme texture_uniform_set = sky_scene_state.fog_only_texture_uniform_set; } - _render_sky(p_draw_list, p_time, p_fb, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin); + _render_sky(p_draw_list, p_time, p_fb, pipeline, material->uniform_set, texture_uniform_set, view_count, projections, sky_transform, multiplier, p_transform.origin, p_luminance_multiplier); } void RendererSceneSkyRD::invalidate_sky(Sky *p_sky) { diff --git a/servers/rendering/renderer_rd/renderer_scene_sky_rd.h b/servers/rendering/renderer_rd/renderer_scene_sky_rd.h index 7b670bddd5..7f563c9bc4 100644 --- a/servers/rendering/renderer_rd/renderer_scene_sky_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_sky_rd.h @@ -100,7 +100,8 @@ private: float position[3]; // 12 - 92 float multiplier; // 4 - 96 float time; // 4 - 100 - float pad[3]; // 12 - 112 // Using pad to align on 16 bytes + float luminance_multiplier; // 4 - 104 + float pad[2]; // 8 - 112 // Using pad to align on 16 bytes // 128 is the max size of a push constant. We can replace "pad" but we can't add any more. }; @@ -138,7 +139,7 @@ private: virtual ~SkyShaderData(); }; - void _render_sky(RD::DrawListID p_list, float p_time, RID p_fb, PipelineCacheRD *p_pipeline, RID p_uniform_set, RID p_texture_set, uint32_t p_view_count, const CameraMatrix *p_projections, const Basis &p_orientation, float p_multiplier, const Vector3 &p_position); + void _render_sky(RD::DrawListID p_list, float p_time, RID p_fb, PipelineCacheRD *p_pipeline, RID p_uniform_set, RID p_texture_set, uint32_t p_view_count, const CameraMatrix *p_projections, const Basis &p_orientation, float p_multiplier, const Vector3 &p_position, float p_luminance_multiplier); public: struct SkySceneState { @@ -293,10 +294,10 @@ public: ~RendererSceneSkyRD(); void setup(RendererSceneEnvironmentRD *p_env, RID p_render_buffers, const CameraMatrix &p_projection, const Transform3D &p_transform, const Size2i p_screen_size, RendererSceneRenderRD *p_scene_render); - void update(RendererSceneEnvironmentRD *p_env, const CameraMatrix &p_projection, const Transform3D &p_transform, double p_time); - void draw(RendererSceneEnvironmentRD *p_env, bool p_can_continue_color, bool p_can_continue_depth, RID p_fb, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time); - void update_res_buffers(RendererSceneEnvironmentRD *p_env, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time); - void draw(RD::DrawListID p_draw_list, RendererSceneEnvironmentRD *p_env, RID p_fb, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time); + void update(RendererSceneEnvironmentRD *p_env, const CameraMatrix &p_projection, const Transform3D &p_transform, double p_time, float p_luminance_multiplier = 1.0); + void draw(RendererSceneEnvironmentRD *p_env, bool p_can_continue_color, bool p_can_continue_depth, RID p_fb, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time); // only called by clustered renderer + void update_res_buffers(RendererSceneEnvironmentRD *p_env, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time, float p_luminance_multiplier = 1.0); + void draw(RD::DrawListID p_draw_list, RendererSceneEnvironmentRD *p_env, RID p_fb, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time, float p_luminance_multiplier = 1.0); void invalidate_sky(Sky *p_sky); void update_dirty_skys(); diff --git a/servers/rendering/renderer_rd/renderer_storage_rd.cpp b/servers/rendering/renderer_rd/renderer_storage_rd.cpp index 8cc20618fc..ec0d25376f 100644 --- a/servers/rendering/renderer_rd/renderer_storage_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_storage_rd.cpp @@ -4848,7 +4848,7 @@ void RendererStorageRD::_particles_process(Particles *p_particles, double p_delt RD::get_singleton()->compute_list_bind_uniform_set(compute_list, p_particles->particles_material_uniform_set, 1); RD::get_singleton()->compute_list_bind_uniform_set(compute_list, p_particles->collision_textures_uniform_set, 2); - if (m->uniform_set.is_valid()) { + if (m->uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(m->uniform_set)) { RD::get_singleton()->compute_list_bind_uniform_set(compute_list, m->uniform_set, 3); } @@ -9398,6 +9398,8 @@ RendererStorageRD::RendererStorageRD() { particles_shader.default_shader = shader_allocate(); shader_initialize(particles_shader.default_shader); shader_set_code(particles_shader.default_shader, R"( +// Default particles shader. + shader_type particles; void process() { diff --git a/servers/rendering/renderer_rd/shader_compiler_rd.cpp b/servers/rendering/renderer_rd/shader_compiler_rd.cpp index bad37f5c25..b95d4b642c 100644 --- a/servers/rendering/renderer_rd/shader_compiler_rd.cpp +++ b/servers/rendering/renderer_rd/shader_compiler_rd.cpp @@ -1165,6 +1165,7 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge SL::VariableNode *vnode = (SL::VariableNode *)onode->arguments[0]; bool is_texture_func = false; + bool is_screen_texture = false; if (onode->op == SL::OP_STRUCT) { code += _mkid(vnode->name); } else if (onode->op == SL::OP_CONSTRUCT) { @@ -1197,6 +1198,7 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge const SL::VariableNode *varnode = static_cast<const SL::VariableNode *>(onode->arguments[i]); StringName texture_uniform = varnode->name; + is_screen_texture = (texture_uniform == "SCREEN_TEXTURE"); String sampler_name; @@ -1236,6 +1238,9 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge } } code += ")"; + if (is_screen_texture && actions.apply_luminance_multiplier) { + code = "(" + code + " * vec4(vec3(sc_luminance_multiplier), 1.0))"; + } } break; case SL::OP_INDEX: { code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning); diff --git a/servers/rendering/renderer_rd/shader_compiler_rd.h b/servers/rendering/renderer_rd/shader_compiler_rd.h index 2da127ffa3..0fe9047967 100644 --- a/servers/rendering/renderer_rd/shader_compiler_rd.h +++ b/servers/rendering/renderer_rd/shader_compiler_rd.h @@ -95,6 +95,7 @@ public: String global_buffer_array_variable; String instance_uniform_index_variable; uint32_t base_varying_index = 0; + bool apply_luminance_multiplier = false; }; private: diff --git a/servers/rendering/renderer_rd/shaders/blit.glsl b/servers/rendering/renderer_rd/shaders/blit.glsl index 967da1e6e4..8051f96738 100644 --- a/servers/rendering/renderer_rd/shaders/blit.glsl +++ b/servers/rendering/renderer_rd/shaders/blit.glsl @@ -5,6 +5,7 @@ #VERSION_DEFINES layout(push_constant, binding = 0, std140) uniform Pos { + vec4 src_rect; vec4 dst_rect; vec2 eye_center; @@ -22,8 +23,8 @@ layout(location = 0) out vec2 uv; void main() { vec2 base_arr[4] = vec2[](vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), vec2(1.0, 0.0)); - uv = base_arr[gl_VertexIndex]; - vec2 vtx = data.dst_rect.xy + uv * data.dst_rect.zw; + uv = data.src_rect.xy + base_arr[gl_VertexIndex] * data.src_rect.zw; + vec2 vtx = data.dst_rect.xy + base_arr[gl_VertexIndex] * data.dst_rect.zw; gl_Position = vec4(vtx * 2.0 - 1.0, 0.0, 1.0); } @@ -34,6 +35,7 @@ void main() { #VERSION_DEFINES layout(push_constant, binding = 0, std140) uniform Pos { + vec4 src_rect; vec4 dst_rect; vec2 eye_center; diff --git a/servers/rendering/renderer_rd/shaders/blur_raster.glsl b/servers/rendering/renderer_rd/shaders/blur_raster.glsl index 0789a4b396..f8b4e3f610 100644 --- a/servers/rendering/renderer_rd/shaders/blur_raster.glsl +++ b/servers/rendering/renderer_rd/shaders/blur_raster.glsl @@ -38,6 +38,8 @@ layout(set = 1, binding = 0) uniform sampler2D source_auto_exposure; layout(location = 0) out vec4 frag_color; void main() { + // We do not apply our color scale for our mobile renderer here, we'll leave our colors at half brightness and apply scale in the tonemap raster. + #ifdef MODE_MIPMAP vec2 pix_size = blur.pixel_size; diff --git a/servers/rendering/renderer_rd/shaders/bokeh_dof_raster.glsl b/servers/rendering/renderer_rd/shaders/bokeh_dof_raster.glsl index 43a2a29616..a3b3938ee9 100644 --- a/servers/rendering/renderer_rd/shaders/bokeh_dof_raster.glsl +++ b/servers/rendering/renderer_rd/shaders/bokeh_dof_raster.glsl @@ -47,7 +47,7 @@ layout(set = 2, binding = 0) uniform sampler2D original_weight; #endif //DOF -// Bokeh single pass implementation based on http://tuxedolabs.blogspot.com/2018/05/bokeh-depth-of-field-in-single-pass.html +// Bokeh single pass implementation based on https://tuxedolabs.blogspot.com/2018/05/bokeh-depth-of-field-in-single-pass.html #ifdef MODE_GEN_BLUR_SIZE diff --git a/servers/rendering/renderer_rd/shaders/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl index a443bcdcb8..2911e8b731 100644 --- a/servers/rendering/renderer_rd/shaders/canvas.glsl +++ b/servers/rendering/renderer_rd/shaders/canvas.glsl @@ -458,6 +458,14 @@ void light_blend_compute(uint light_base, vec4 light_color, inout vec3 color) { #endif +float msdf_median(float r, float g, float b, float a) { + return min(max(min(r, g), min(max(r, g), b)), a); +} + +vec2 msdf_map(vec2 value, vec2 in_min, vec2 in_max, vec2 out_min, vec2 out_max) { + return out_min + (out_max - out_min) * (value - in_min) / (in_max - in_min); +} + void main() { vec4 color = color_interp; vec2 uv = uv_interp; @@ -485,7 +493,34 @@ void main() { #endif - color *= texture(sampler2D(color_texture, texture_sampler), uv); +#ifndef USE_PRIMITIVE + if (bool(draw_data.flags & FLAGS_USE_MSDF)) { + float px_range = draw_data.ninepatch_margins.x; + float outline_thickness = draw_data.ninepatch_margins.y; + //float reserved1 = draw_data.ninepatch_margins.z; + //float reserved2 = draw_data.ninepatch_margins.w; + + vec4 msdf_sample = texture(sampler2D(color_texture, texture_sampler), uv); + vec2 msdf_size = vec2(textureSize(sampler2D(color_texture, texture_sampler), 0)); + vec2 dest_size = vec2(1.0) / fwidth(uv); + float px_size = max(0.5 * dot((vec2(px_range) / msdf_size), dest_size), 1.0); + float d = msdf_median(msdf_sample.r, msdf_sample.g, msdf_sample.b, msdf_sample.a) - 0.5; + + if (outline_thickness > 0) { + float cr = clamp(outline_thickness, 0.0, px_range / 2) / px_range; + float a = clamp((d + cr) * px_size, 0.0, 1.0); + color.a = a * color.a; + } else { + float a = clamp(d * px_size + 0.5, 0.0, 1.0); + color.a = a * color.a; + } + + } else { +#else + { +#endif + color *= texture(sampler2D(color_texture, texture_sampler), uv); + } uint light_count = (draw_data.flags >> FLAGS_LIGHT_COUNT_SHIFT) & 0xF; //max 16 lights bool using_light = light_count > 0 || canvas_data.directional_light_count > 0; diff --git a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl index 451f9b0089..0cff505cae 100644 --- a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl @@ -24,6 +24,8 @@ #define FLAGS_DEFAULT_NORMAL_MAP_USED (1 << 26) #define FLAGS_DEFAULT_SPECULAR_MAP_USED (1 << 27) +#define FLAGS_USE_MSDF (1 << 28) + #define SAMPLER_NEAREST_CLAMP 0 #define SAMPLER_LINEAR_CLAMP 1 #define SAMPLER_NEAREST_WITH_MIPMAPS_CLAMP 2 diff --git a/servers/rendering/renderer_rd/shaders/cluster_render.glsl b/servers/rendering/renderer_rd/shaders/cluster_render.glsl index da7d189281..6d95722a57 100644 --- a/servers/rendering/renderer_rd/shaders/cluster_render.glsl +++ b/servers/rendering/renderer_rd/shaders/cluster_render.glsl @@ -117,7 +117,7 @@ void main() { uint cluster_thread_group_index; if (!gl_HelperInvocation) { - //http://advances.realtimerendering.com/s2017/2017_Sig_Improved_Culling_final.pdf + //https://advances.realtimerendering.com/s2017/2017_Sig_Improved_Culling_final.pdf uvec4 mask; diff --git a/servers/rendering/renderer_rd/shaders/cubemap_roughness_inc.glsl b/servers/rendering/renderer_rd/shaders/cubemap_roughness_inc.glsl index 80c0ac4fb4..be12be5dec 100644 --- a/servers/rendering/renderer_rd/shaders/cubemap_roughness_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/cubemap_roughness_inc.glsl @@ -69,13 +69,13 @@ vec3 ImportanceSampleGGX(vec2 Xi, float Roughness, vec3 N) { return TangentX * H.x + TangentY * H.y + N * H.z; } -// http://graphicrants.blogspot.com.au/2013/08/specular-brdf-reference.html +// https://graphicrants.blogspot.com.au/2013/08/specular-brdf-reference.html float GGX(float NdotV, float a) { float k = a / 2.0; return NdotV / (NdotV * (1.0 - k) + k); } -// http://graphicrants.blogspot.com.au/2013/08/specular-brdf-reference.html +// https://graphicrants.blogspot.com.au/2013/08/specular-brdf-reference.html float G_Smith(float a, float nDotV, float nDotL) { return GGX(nDotL, a * a) * GGX(nDotV, a * a); } diff --git a/servers/rendering/renderer_rd/shaders/giprobe_write.glsl b/servers/rendering/renderer_rd/shaders/giprobe_write.glsl index 5dc2d08a3b..25d87ca45d 100644 --- a/servers/rendering/renderer_rd/shaders/giprobe_write.glsl +++ b/servers/rendering/renderer_rd/shaders/giprobe_write.glsl @@ -202,12 +202,7 @@ void main() { vec3 emission = vec3(ivec3(cell_data.data[cell_index].emission & 0x3FF, (cell_data.data[cell_index].emission >> 10) & 0x7FF, cell_data.data[cell_index].emission >> 21)) * params.emission_scale; vec4 normal = unpackSnorm4x8(cell_data.data[cell_index].normal); -#ifdef MODE_ANISOTROPIC - vec3 accum[6] = vec3[](vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0)); - const vec3 accum_dirs[6] = vec3[](vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0)); -#else vec3 accum = vec3(0.0); -#endif for (uint i = 0; i < params.light_count; i++) { float attenuation; @@ -242,77 +237,35 @@ void main() { vec3 light = lights.data[i].color * albedo.rgb * attenuation * lights.data[i].energy; -#ifdef MODE_ANISOTROPIC - for (uint j = 0; j < 6; j++) { - accum[j] += max(0.0, dot(accum_dir, -light_dir)) * light + emission; - } -#else if (length(normal.xyz) > 0.2) { accum += max(0.0, dot(normal.xyz, -light_dir)) * light + emission; } else { //all directions accum += light + emission; } -#endif } -#ifdef MODE_ANISOTROPIC - - output.data[cell_index * 6 + 0] = vec4(accum[0], 0.0); - output.data[cell_index * 6 + 1] = vec4(accum[1], 0.0); - output.data[cell_index * 6 + 2] = vec4(accum[2], 0.0); - output.data[cell_index * 6 + 3] = vec4(accum[3], 0.0); - output.data[cell_index * 6 + 4] = vec4(accum[4], 0.0); - output.data[cell_index * 6 + 5] = vec4(accum[5], 0.0); -#else output.data[cell_index] = vec4(accum, 0.0); -#endif - #endif //MODE_COMPUTE_LIGHT #ifdef MODE_UPDATE_MIPMAPS { -#ifdef MODE_ANISOTROPIC - vec3 light_accum[6] = vec3[](vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0)); -#else vec3 light_accum = vec3(0.0); -#endif float count = 0.0; for (uint i = 0; i < 8; i++) { uint child_index = cell_children.data[cell_index].children[i]; if (child_index == NO_CHILDREN) { continue; } -#ifdef MODE_ANISOTROPIC - light_accum[1] += output.data[child_index * 6 + 0].rgb; - light_accum[2] += output.data[child_index * 6 + 1].rgb; - light_accum[3] += output.data[child_index * 6 + 2].rgb; - light_accum[4] += output.data[child_index * 6 + 3].rgb; - light_accum[5] += output.data[child_index * 6 + 4].rgb; - light_accum[6] += output.data[child_index * 6 + 5].rgb; - -#else light_accum += output.data[child_index].rgb; -#endif - count += 1.0; } float divisor = mix(8.0, count, params.propagation); -#ifdef MODE_ANISOTROPIC - output.data[cell_index * 6 + 0] = vec4(light_accum[0] / divisor, 0.0); - output.data[cell_index * 6 + 1] = vec4(light_accum[1] / divisor, 0.0); - output.data[cell_index * 6 + 2] = vec4(light_accum[2] / divisor, 0.0); - output.data[cell_index * 6 + 3] = vec4(light_accum[3] / divisor, 0.0); - output.data[cell_index * 6 + 4] = vec4(light_accum[4] / divisor, 0.0); - output.data[cell_index * 6 + 5] = vec4(light_accum[5] / divisor, 0.0); - -#else output.data[cell_index] = vec4(light_accum / divisor, 0.0); -#endif } #endif diff --git a/servers/rendering/renderer_rd/shaders/luminance_reduce_raster_inc.glsl b/servers/rendering/renderer_rd/shaders/luminance_reduce_raster_inc.glsl index ed389ffe56..3cde9923fa 100644 --- a/servers/rendering/renderer_rd/shaders/luminance_reduce_raster_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/luminance_reduce_raster_inc.glsl @@ -6,6 +6,6 @@ layout(push_constant, binding = 1, std430) uniform PushConstant { float exposure_adjust; float min_luminance; float max_luminance; - float pad; + uint pad1; } settings; diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl index adf9f20618..8cb56fbc83 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl @@ -118,7 +118,7 @@ void main() { mat3 world_normal_matrix; if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_NON_UNIFORM_SCALE)) { - world_normal_matrix = inverse(mat3(world_matrix)); + world_normal_matrix = transpose(inverse(mat3(world_matrix))); } else { world_normal_matrix = mat3(world_matrix); } @@ -374,6 +374,9 @@ layout(constant_id = 9) const uint sc_directional_penumbra_shadow_samples = 4; layout(constant_id = 10) const bool sc_decal_use_mipmaps = true; layout(constant_id = 11) const bool sc_projector_use_mipmaps = true; +// not used in clustered renderer but we share some code with the mobile renderer that requires this. +const float sc_luminance_multiplier = 1.0; + #include "scene_forward_clustered_inc.glsl" /* Varyings */ @@ -881,7 +884,7 @@ void main() { #ifdef NORMAL_USED if (scene_data.roughness_limiter_enabled) { - //http://www.jp.square-enix.com/tech/library/pdf/ImprovedGeometricSpecularAA.pdf + //https://www.jp.square-enix.com/tech/library/pdf/ImprovedGeometricSpecularAA.pdf float roughness2 = roughness * roughness; vec3 dndu = dFdx(normal), dndv = dFdy(normal); float variance = scene_data.roughness_limiter_amount * (dot(dndu, dndu) + dot(dndv, dndv)); @@ -900,6 +903,7 @@ void main() { if (scene_data.use_reflection_cubemap) { vec3 ref_vec = reflect(-view, normal); + float horizon = min(1.0 + dot(ref_vec, normal), 1.0); ref_vec = scene_data.radiance_inverse_xform * ref_vec; #ifdef USE_RADIANCE_CUBEMAP_ARRAY @@ -912,7 +916,6 @@ void main() { specular_light = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ref_vec, roughness * MAX_ROUGHNESS_LOD).rgb; #endif //USE_RADIANCE_CUBEMAP_ARRAY - float horizon = min(1.0 + dot(ref_vec, normal), 1.0); specular_light *= horizon * horizon; specular_light *= scene_data.ambient_light_color_energy.a; } diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl index ef2fde7516..f3db4abe3b 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl @@ -288,7 +288,7 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, float atte #ifndef USE_NO_SHADOWS // Interleaved Gradient Noise -// http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare +// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare float quick_hash(vec2 pos) { const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f); return fract(magic.z * fract(dot(pos, magic.xy))); @@ -969,7 +969,7 @@ void reflection_process(uint ref_index, vec3 vertex, vec3 normal, float roughnes vec4 reflection; - reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(local_ref_vec, reflections.data[ref_index].index), roughness * MAX_ROUGHNESS_LOD).rgb; + reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(local_ref_vec, reflections.data[ref_index].index), roughness * MAX_ROUGHNESS_LOD).rgb * sc_luminance_multiplier; if (reflections.data[ref_index].exterior) { reflection.rgb = mix(specular_light, reflection.rgb, blend); diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl index a2a54a0511..c3c4139450 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl @@ -124,7 +124,7 @@ void main() { mat3 world_normal_matrix; if (bool(draw_call.flags & INSTANCE_FLAGS_NON_UNIFORM_SCALE)) { - world_normal_matrix = inverse(mat3(world_matrix)); + world_normal_matrix = transpose(inverse(mat3(world_matrix))); } else { world_normal_matrix = mat3(world_matrix); } @@ -401,6 +401,8 @@ layout(constant_id = 14) const bool sc_disable_fog = false; #endif //!MODE_RENDER_DEPTH +layout(constant_id = 15) const float sc_luminance_multiplier = 2.0; + /* Include our forward mobile UBOs definitions etc. */ #include "scene_forward_mobile_inc.glsl" @@ -847,7 +849,7 @@ void main() { #ifdef NORMAL_USED if (scene_data.roughness_limiter_enabled) { - //http://www.jp.square-enix.com/tech/library/pdf/ImprovedGeometricSpecularAA.pdf + //https://www.jp.square-enix.com/tech/library/pdf/ImprovedGeometricSpecularAA.pdf float roughness2 = roughness * roughness; vec3 dndu = dFdx(normal), dndv = dFdy(normal); float variance = scene_data.roughness_limiter_amount * (dot(dndu, dndu) + dot(dndv, dndv)); @@ -866,6 +868,7 @@ void main() { if (scene_data.use_reflection_cubemap) { vec3 ref_vec = reflect(-view, normal); + float horizon = min(1.0 + dot(ref_vec, normal), 1.0); ref_vec = scene_data.radiance_inverse_xform * ref_vec; #ifdef USE_RADIANCE_CUBEMAP_ARRAY @@ -878,7 +881,6 @@ void main() { specular_light = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ref_vec, roughness * MAX_ROUGHNESS_LOD).rgb; #endif //USE_RADIANCE_CUBEMAP_ARRAY - float horizon = min(1.0 + dot(ref_vec, normal), 1.0); specular_light *= horizon * horizon; specular_light *= scene_data.ambient_light_color_energy.a; } @@ -1551,12 +1553,15 @@ void main() { frag_color = vec4(albedo, alpha); #else // MODE_UNSHADED frag_color = vec4(emission + ambient_light + diffuse_light + specular_light, alpha); - //frag_color = vec4(1.0); #endif // MODE_UNSHADED // Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky. frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a); + // On mobile we use a UNORM buffer with 10bpp which results in a range from 0.0 - 1.0 resulting in HDR breaking + // We divide by sc_luminance_multiplier to support a range from 0.0 - 2.0 both increasing precision on bright and darker images + frag_color.rgb = frag_color.rgb / sc_luminance_multiplier; + #endif //MODE_MULTIPLE_RENDER_TARGETS #endif //MODE_RENDER_DEPTH diff --git a/servers/rendering/renderer_rd/shaders/sdfgi_debug_probes.glsl b/servers/rendering/renderer_rd/shaders/sdfgi_debug_probes.glsl index 0eacbc5363..4290d5b869 100644 --- a/servers/rendering/renderer_rd/shaders/sdfgi_debug_probes.glsl +++ b/servers/rendering/renderer_rd/shaders/sdfgi_debug_probes.glsl @@ -24,7 +24,7 @@ layout(push_constant, binding = 0, std430) uniform Params { } params; -// http://in4k.untergrund.net/html_articles/hugi_27_-_coding_corner_polaris_sphere_tessellation_101.htm +// https://in4k.untergrund.net/html_articles/hugi_27_-_coding_corner_polaris_sphere_tessellation_101.htm vec3 get_sphere_vertex(uint p_vertex_id) { float x_angle = float(p_vertex_id & 1u) + (p_vertex_id >> params.band_power); diff --git a/servers/rendering/renderer_rd/shaders/sky.glsl b/servers/rendering/renderer_rd/shaders/sky.glsl index 41c6325bc5..d07a454ade 100644 --- a/servers/rendering/renderer_rd/shaders/sky.glsl +++ b/servers/rendering/renderer_rd/shaders/sky.glsl @@ -17,6 +17,8 @@ layout(push_constant, binding = 1, std430) uniform Params { vec4 projections[MAX_VIEWS]; vec4 position_multiplier; float time; + float luminance_multiplier; + float pad[2]; } params; @@ -55,6 +57,8 @@ layout(push_constant, binding = 1, std430) uniform Params { vec4 projections[MAX_VIEWS]; vec4 position_multiplier; float time; + float luminance_multiplier; + float pad[2]; } params; @@ -199,17 +203,17 @@ void main() { vec3 inverted_cube_normal = cube_normal; inverted_cube_normal.z *= -1.0; #ifdef USES_HALF_RES_COLOR - half_res_color = texture(samplerCube(half_res, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), inverted_cube_normal); + half_res_color = texture(samplerCube(half_res, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), inverted_cube_normal) * params.luminance_multiplier; #endif #ifdef USES_QUARTER_RES_COLOR - quarter_res_color = texture(samplerCube(quarter_res, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), inverted_cube_normal); + quarter_res_color = texture(samplerCube(quarter_res, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), inverted_cube_normal) * params.luminance_multiplier; #endif #else #ifdef USES_HALF_RES_COLOR - half_res_color = textureLod(sampler2D(half_res, material_samplers[SAMPLER_LINEAR_CLAMP]), uv, 0.0); + half_res_color = textureLod(sampler2D(half_res, material_samplers[SAMPLER_LINEAR_CLAMP]), uv, 0.0) * params.luminance_multiplier; #endif #ifdef USES_QUARTER_RES_COLOR - quarter_res_color = textureLod(sampler2D(quarter_res, material_samplers[SAMPLER_LINEAR_CLAMP]), uv, 0.0); + quarter_res_color = textureLod(sampler2D(quarter_res, material_samplers[SAMPLER_LINEAR_CLAMP]), uv, 0.0) * params.luminance_multiplier; #endif #endif @@ -246,4 +250,7 @@ void main() { if (!AT_CUBEMAP_PASS && !AT_HALF_RES_PASS && !AT_QUARTER_RES_PASS) { frag_color.a = 0.0; } + + // For mobile renderer we're dividing by 2.0 as we're using a UNORM buffer + frag_color.rgb = frag_color.rgb / params.luminance_multiplier; } diff --git a/servers/rendering/renderer_rd/shaders/tonemap.glsl b/servers/rendering/renderer_rd/shaders/tonemap.glsl index 3c685c25b9..1ce3e04421 100644 --- a/servers/rendering/renderer_rd/shaders/tonemap.glsl +++ b/servers/rendering/renderer_rd/shaders/tonemap.glsl @@ -37,15 +37,15 @@ layout(location = 0) in vec2 uv_interp; #ifdef SUBPASS layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput input_color; -#else -#if MULTIVIEW +#elif defined(MULTIVIEW) layout(set = 0, binding = 0) uniform sampler2DArray source_color; #else layout(set = 0, binding = 0) uniform sampler2D source_color; #endif -#endif + layout(set = 1, binding = 0) uniform sampler2D source_auto_exposure; layout(set = 2, binding = 0) uniform sampler2D source_glow; + #ifdef USE_1D_LUT layout(set = 3, binding = 0) uniform sampler2D source_color_correction; #else @@ -71,7 +71,7 @@ layout(push_constant, binding = 1, std430) uniform Params { float exposure; float white; float auto_exposure_grey; - uint pad2; + float luminance_multiplier; vec2 pixel_size; bool use_fxaa; @@ -169,16 +169,33 @@ vec3 tonemap_filmic(vec3 color, float white) { return color_tonemapped / white_tonemapped; } +// Adapted from https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl +// (MIT License). vec3 tonemap_aces(vec3 color, float white) { - const float exposure_bias = 0.85f; - const float A = 2.51f * exposure_bias * exposure_bias; - const float B = 0.03f * exposure_bias; - const float C = 2.43f * exposure_bias * exposure_bias; - const float D = 0.59f * exposure_bias; - const float E = 0.14f; - - vec3 color_tonemapped = (color * (A * color + B)) / (color * (C * color + D) + E); - float white_tonemapped = (white * (A * white + B)) / (white * (C * white + D) + E); + const float exposure_bias = 1.8f; + const float A = 0.0245786f; + const float B = 0.000090537f; + const float C = 0.983729f; + const float D = 0.432951f; + const float E = 0.238081f; + + // Exposure bias baked into transform to save shader instructions. Equivalent to `color *= exposure_bias` + const mat3 rgb_to_rrt = mat3( + vec3(0.59719f * exposure_bias, 0.35458f * exposure_bias, 0.04823f * exposure_bias), + vec3(0.07600f * exposure_bias, 0.90834f * exposure_bias, 0.01566f * exposure_bias), + vec3(0.02840f * exposure_bias, 0.13383f * exposure_bias, 0.83777f * exposure_bias)); + + const mat3 odt_to_rgb = mat3( + vec3(1.60475f, -0.53108f, -0.07367f), + vec3(-0.10208f, 1.10813f, -0.00605f), + vec3(-0.00327f, -0.07276f, 1.07602f)); + + color *= rgb_to_rrt; + vec3 color_tonemapped = (color * (color + A) - B) / (color * (C * color + D) + E); + color_tonemapped *= odt_to_rgb; + + white *= exposure_bias; + float white_tonemapped = (white * (white + A) - B) / (white * (C * white + D) + E); return color_tonemapped / white_tonemapped; } @@ -200,15 +217,16 @@ vec3 linear_to_srgb(vec3 color) { #define TONEMAPPER_ACES 3 vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR, always outputs clamped [0;1] color - + // Ensure color values passed to tonemappers are positive. + // They can be negative in the case of negative lights, which leads to undesired behavior. if (params.tonemapper == TONEMAPPER_LINEAR) { return color; } else if (params.tonemapper == TONEMAPPER_REINHARD) { - return tonemap_reinhard(color, white); + return tonemap_reinhard(max(vec3(0.0f), color), white); } else if (params.tonemapper == TONEMAPPER_FILMIC) { - return tonemap_filmic(color, white); + return tonemap_filmic(max(vec3(0.0f), color), white); } else { // TONEMAPPER_ACES - return tonemap_aces(color, white); + return tonemap_aces(max(vec3(0.0f), color), white); } } @@ -298,15 +316,15 @@ vec3 do_fxaa(vec3 color, float exposure, vec2 uv_interp) { const float FXAA_SPAN_MAX = 8.0; #ifdef MULTIVIEW - vec3 rgbNW = textureLod(source_color, vec3(uv_interp + vec2(-1.0, -1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure; - vec3 rgbNE = textureLod(source_color, vec3(uv_interp + vec2(1.0, -1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure; - vec3 rgbSW = textureLod(source_color, vec3(uv_interp + vec2(-1.0, 1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure; - vec3 rgbSE = textureLod(source_color, vec3(uv_interp + vec2(1.0, 1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure; + vec3 rgbNW = textureLod(source_color, vec3(uv_interp + vec2(-1.0, -1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure * params.luminance_multiplier; + vec3 rgbNE = textureLod(source_color, vec3(uv_interp + vec2(1.0, -1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure * params.luminance_multiplier; + vec3 rgbSW = textureLod(source_color, vec3(uv_interp + vec2(-1.0, 1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure * params.luminance_multiplier; + vec3 rgbSE = textureLod(source_color, vec3(uv_interp + vec2(1.0, 1.0) * params.pixel_size, ViewIndex), 0.0).xyz * exposure * params.luminance_multiplier; #else - vec3 rgbNW = textureLod(source_color, uv_interp + vec2(-1.0, -1.0) * params.pixel_size, 0.0).xyz * exposure; - vec3 rgbNE = textureLod(source_color, uv_interp + vec2(1.0, -1.0) * params.pixel_size, 0.0).xyz * exposure; - vec3 rgbSW = textureLod(source_color, uv_interp + vec2(-1.0, 1.0) * params.pixel_size, 0.0).xyz * exposure; - vec3 rgbSE = textureLod(source_color, uv_interp + vec2(1.0, 1.0) * params.pixel_size, 0.0).xyz * exposure; + vec3 rgbNW = textureLod(source_color, uv_interp + vec2(-1.0, -1.0) * params.pixel_size, 0.0).xyz * exposure * params.luminance_multiplier; + vec3 rgbNE = textureLod(source_color, uv_interp + vec2(1.0, -1.0) * params.pixel_size, 0.0).xyz * exposure * params.luminance_multiplier; + vec3 rgbSW = textureLod(source_color, uv_interp + vec2(-1.0, 1.0) * params.pixel_size, 0.0).xyz * exposure * params.luminance_multiplier; + vec3 rgbSE = textureLod(source_color, uv_interp + vec2(1.0, 1.0) * params.pixel_size, 0.0).xyz * exposure * params.luminance_multiplier; #endif vec3 rgbM = color; vec3 luma = vec3(0.299, 0.587, 0.114); @@ -333,11 +351,11 @@ vec3 do_fxaa(vec3 color, float exposure, vec2 uv_interp) { params.pixel_size; #ifdef MULTIVIEW - vec3 rgbA = 0.5 * exposure * (textureLod(source_color, vec3(uv_interp + dir * (1.0 / 3.0 - 0.5), ViewIndex), 0.0).xyz + textureLod(source_color, vec3(uv_interp + dir * (2.0 / 3.0 - 0.5), ViewIndex), 0.0).xyz); - vec3 rgbB = rgbA * 0.5 + 0.25 * exposure * (textureLod(source_color, vec3(uv_interp + dir * -0.5, ViewIndex), 0.0).xyz + textureLod(source_color, vec3(uv_interp + dir * 0.5, ViewIndex), 0.0).xyz); + vec3 rgbA = 0.5 * exposure * (textureLod(source_color, vec3(uv_interp + dir * (1.0 / 3.0 - 0.5), ViewIndex), 0.0).xyz + textureLod(source_color, vec3(uv_interp + dir * (2.0 / 3.0 - 0.5), ViewIndex), 0.0).xyz) * params.luminance_multiplier; + vec3 rgbB = rgbA * 0.5 + 0.25 * exposure * (textureLod(source_color, vec3(uv_interp + dir * -0.5, ViewIndex), 0.0).xyz + textureLod(source_color, vec3(uv_interp + dir * 0.5, ViewIndex), 0.0).xyz) * params.luminance_multiplier; #else - vec3 rgbA = 0.5 * exposure * (textureLod(source_color, uv_interp + dir * (1.0 / 3.0 - 0.5), 0.0).xyz + textureLod(source_color, uv_interp + dir * (2.0 / 3.0 - 0.5), 0.0).xyz); - vec3 rgbB = rgbA * 0.5 + 0.25 * exposure * (textureLod(source_color, uv_interp + dir * -0.5, 0.0).xyz + textureLod(source_color, uv_interp + dir * 0.5, 0.0).xyz); + vec3 rgbA = 0.5 * exposure * (textureLod(source_color, uv_interp + dir * (1.0 / 3.0 - 0.5), 0.0).xyz + textureLod(source_color, uv_interp + dir * (2.0 / 3.0 - 0.5), 0.0).xyz) * params.luminance_multiplier; + vec3 rgbB = rgbA * 0.5 + 0.25 * exposure * (textureLod(source_color, uv_interp + dir * -0.5, 0.0).xyz + textureLod(source_color, uv_interp + dir * 0.5, 0.0).xyz) * params.luminance_multiplier; #endif float lumaB = dot(rgbB, luma); @@ -349,7 +367,7 @@ vec3 do_fxaa(vec3 color, float exposure, vec2 uv_interp) { } #endif // !SUBPASS -// From http://alex.vlachos.com/graphics/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf +// From https://alex.vlachos.com/graphics/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf // and https://www.shadertoy.com/view/MslGR8 (5th one starting from the bottom) // NOTE: `frag_coord` is in pixels (i.e. not normalized UV). vec3 screen_space_dither(vec2 frag_coord) { @@ -364,11 +382,11 @@ vec3 screen_space_dither(vec2 frag_coord) { void main() { #ifdef SUBPASS // SUBPASS and MULTIVIEW can be combined but in that case we're already reading from the correct layer - vec3 color = subpassLoad(input_color).rgb; -#elif MULTIVIEW - vec3 color = textureLod(source_color, vec3(uv_interp, ViewIndex), 0.0f).rgb; + vec3 color = subpassLoad(input_color).rgb * params.luminance_multiplier; +#elif defined(MULTIVIEW) + vec3 color = textureLod(source_color, vec3(uv_interp, ViewIndex), 0.0f).rgb * params.luminance_multiplier; #else - vec3 color = textureLod(source_color, uv_interp, 0.0f).rgb; + vec3 color = textureLod(source_color, uv_interp, 0.0f).rgb * params.luminance_multiplier; #endif // Exposure @@ -377,7 +395,7 @@ void main() { #ifndef SUBPASS if (params.use_auto_exposure) { - exposure *= 1.0 / (texelFetch(source_auto_exposure, ivec2(0, 0), 0).r / params.auto_exposure_grey); + exposure *= 1.0 / (texelFetch(source_auto_exposure, ivec2(0, 0), 0).r * params.luminance_multiplier / params.auto_exposure_grey); } #endif @@ -386,7 +404,7 @@ void main() { // Early Tonemap & SRGB Conversion #ifndef SUBPASS if (params.use_glow && params.glow_mode == GLOW_MODE_MIX) { - vec3 glow = gather_glow(source_glow, uv_interp); + vec3 glow = gather_glow(source_glow, uv_interp) * params.luminance_multiplier; color.rgb = mix(color.rgb, glow, params.glow_intensity); } @@ -401,9 +419,7 @@ void main() { color += screen_space_dither(gl_FragCoord.xy); } - // Ensure color values passed to tonemappers are positive. - // They can be negative in the case of negative lights, which leads to undesired behavior. - color = apply_tonemapping(max(vec3(0.0), color), params.white); + color = apply_tonemapping(color, params.white); color = linear_to_srgb(color); // regular linear -> SRGB conversion @@ -411,7 +427,7 @@ void main() { // Glow if (params.use_glow && params.glow_mode != GLOW_MODE_MIX) { - vec3 glow = gather_glow(source_glow, uv_interp) * params.glow_intensity; + vec3 glow = gather_glow(source_glow, uv_interp) * params.glow_intensity * params.luminance_multiplier; // high dynamic range -> SRGB glow = apply_tonemapping(glow, params.white); diff --git a/servers/rendering/renderer_rd/shaders/voxel_gi.glsl b/servers/rendering/renderer_rd/shaders/voxel_gi.glsl index 49a493cdc7..779f04ed35 100644 --- a/servers/rendering/renderer_rd/shaders/voxel_gi.glsl +++ b/servers/rendering/renderer_rd/shaders/voxel_gi.glsl @@ -71,11 +71,6 @@ lights; layout(set = 0, binding = 5) uniform texture3D color_texture; -#ifdef MODE_ANISOTROPIC -layout(set = 0, binding = 7) uniform texture3D aniso_pos_texture; -layout(set = 0, binding = 8) uniform texture3D aniso_neg_texture; -#endif // MODE ANISOTROPIC - #endif // MODE_SECOND_BOUNCE #ifndef MODE_DYNAMIC @@ -110,13 +105,6 @@ layout(set = 0, binding = 10) uniform sampler texture_sampler; layout(rgba8, set = 0, binding = 5) uniform restrict writeonly image3D color_tex; -#ifdef MODE_ANISOTROPIC - -layout(r16ui, set = 0, binding = 6) uniform restrict writeonly uimage3D aniso_pos_tex; -layout(r16ui, set = 0, binding = 7) uniform restrict writeonly uimage3D aniso_neg_tex; - -#endif - #endif #ifdef MODE_DYNAMIC @@ -170,13 +158,6 @@ layout(r32f, set = 0, binding = 8) uniform restrict writeonly image2D depth; layout(rgba8, set = 0, binding = 11) uniform restrict image3D color_texture; -#ifdef MODE_ANISOTROPIC - -layout(r16ui, set = 0, binding = 12) uniform restrict writeonly uimage3D aniso_pos_texture; -layout(r16ui, set = 0, binding = 13) uniform restrict writeonly uimage3D aniso_neg_texture; - -#endif // MODE ANISOTROPIC - #endif //MODE_DYNAMIC_SHRINK_PLOT #endif // MODE_DYNAMIC_SHRINK @@ -374,12 +355,7 @@ void main() { vec3 emission = vec3(uvec3(cell_data.data[cell_index].emission & 0x1ff, (cell_data.data[cell_index].emission >> 9) & 0x1ff, (cell_data.data[cell_index].emission >> 18) & 0x1ff)) * pow(2.0, float(cell_data.data[cell_index].emission >> 27) - 15.0 - 9.0); vec3 normal = unpackSnorm4x8(cell_data.data[cell_index].normal).xyz; -#ifdef MODE_ANISOTROPIC - vec3 accum[6] = vec3[](vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0)); - const vec3 accum_dirs[6] = vec3[](vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0)); -#else vec3 accum = vec3(0.0); -#endif for (uint i = 0; i < params.light_count; i++) { vec3 light; @@ -390,38 +366,16 @@ void main() { light *= albedo.rgb; -#ifdef MODE_ANISOTROPIC - for (uint j = 0; j < 6; j++) { - accum[j] += max(0.0, dot(accum_dirs[j], -light_dir)) * light; - } -#else if (length(normal) > 0.2) { accum += max(0.0, dot(normal, -light_dir)) * light; } else { //all directions accum += light; } -#endif } -#ifdef MODE_ANISOTROPIC - - for (uint i = 0; i < 6; i++) { - vec3 light = accum[i]; - if (length(normal) > 0.2) { - light += max(0.0, dot(accum_dirs[i], -normal)) * emission; - } else { - light += emission; - } - - outputs.data[cell_index * 6 + i] = vec4(light, 0.0); - } - -#else outputs.data[cell_index] = vec4(accum + emission, 0.0); -#endif - #endif //MODE_COMPUTE_LIGHT /////////////////SECOND BOUNCE/////////////////////////////// @@ -431,32 +385,8 @@ void main() { ivec3 ipos = ivec3(posu); vec4 normal = unpackSnorm4x8(cell_data.data[cell_index].normal); -#ifdef MODE_ANISOTROPIC - vec3 accum[6]; - const vec3 accum_dirs[6] = vec3[](vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0)); - - /*vec3 src_color = texelFetch(sampler3D(color_texture,texture_sampler),ipos,0).rgb * params.dynamic_range; - vec3 src_aniso_pos = texelFetch(sampler3D(aniso_pos_texture,texture_sampler),ipos,0).rgb; - vec3 src_anisp_neg = texelFetch(sampler3D(anisp_neg_texture,texture_sampler),ipos,0).rgb; - accum[0]=src_col * src_aniso_pos.x; - accum[1]=src_col * src_aniso_neg.x; - accum[2]=src_col * src_aniso_pos.y; - accum[3]=src_col * src_aniso_neg.y; - accum[4]=src_col * src_aniso_pos.z; - accum[5]=src_col * src_aniso_neg.z;*/ - - accum[0] = outputs.data[cell_index * 6 + 0].rgb; - accum[1] = outputs.data[cell_index * 6 + 1].rgb; - accum[2] = outputs.data[cell_index * 6 + 2].rgb; - accum[3] = outputs.data[cell_index * 6 + 3].rgb; - accum[4] = outputs.data[cell_index * 6 + 4].rgb; - accum[5] = outputs.data[cell_index * 6 + 5].rgb; - -#else vec3 accum = outputs.data[cell_index].rgb; -#endif - if (length(normal.xyz) > 0.2) { vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0); vec3 tangent = normalize(cross(v0, normal.xyz)); @@ -484,9 +414,6 @@ void main() { float max_distance = length(vec3(params.limits)); vec3 cell_size = 1.0 / vec3(params.limits); -#ifdef MODE_ANISOTROPIC - vec3 aniso_normal = mix(direction, normal.xyz, params.aniso_strength); -#endif while (dist < max_distance && color.a < 0.95) { float diameter = max(1.0, 2.0 * tan_half_angle * dist); vec3 uvw_pos = (pos + dist * direction) * cell_size; @@ -498,42 +425,18 @@ void main() { float log2_diameter = log2(diameter); vec4 scolor = textureLod(sampler3D(color_texture, texture_sampler), uvw_pos, log2_diameter); -#ifdef MODE_ANISOTROPIC - - vec3 aniso_neg = textureLod(sampler3D(aniso_neg_texture, texture_sampler), uvw_pos, log2_diameter).rgb; - vec3 aniso_pos = textureLod(sampler3D(aniso_pos_texture, texture_sampler), uvw_pos, log2_diameter).rgb; - - scolor.rgb *= dot(max(vec3(0.0), (aniso_normal * aniso_pos)), vec3(1.0)) + dot(max(vec3(0.0), (-aniso_normal * aniso_neg)), vec3(1.0)); -#endif float a = (1.0 - color.a); color += a * scolor; dist += half_diameter; } } color *= cone_weights[i] * vec4(albedo.rgb, 1.0) * params.dynamic_range; //restore range -#ifdef MODE_ANISOTROPIC - for (uint j = 0; j < 6; j++) { - accum[j] += max(0.0, dot(accum_dirs[j], direction)) * color.rgb; - } -#else accum += color.rgb; -#endif } } -#ifdef MODE_ANISOTROPIC - - outputs.data[cell_index * 6 + 0] = vec4(accum[0], 0.0); - outputs.data[cell_index * 6 + 1] = vec4(accum[1], 0.0); - outputs.data[cell_index * 6 + 2] = vec4(accum[2], 0.0); - outputs.data[cell_index * 6 + 3] = vec4(accum[3], 0.0); - outputs.data[cell_index * 6 + 4] = vec4(accum[4], 0.0); - outputs.data[cell_index * 6 + 5] = vec4(accum[5], 0.0); -#else outputs.data[cell_index] = vec4(accum, 0.0); -#endif - #endif // MODE_SECOND_BOUNCE /////////////////UPDATE MIPMAPS/////////////////////////////// @@ -541,45 +444,20 @@ void main() { #ifdef MODE_UPDATE_MIPMAPS { -#ifdef MODE_ANISOTROPIC - vec3 light_accum[6] = vec3[](vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0)); -#else vec3 light_accum = vec3(0.0); -#endif float count = 0.0; for (uint i = 0; i < 8; i++) { uint child_index = cell_children.data[cell_index].children[i]; if (child_index == NO_CHILDREN) { continue; } -#ifdef MODE_ANISOTROPIC - light_accum[0] += outputs.data[child_index * 6 + 0].rgb; - light_accum[1] += outputs.data[child_index * 6 + 1].rgb; - light_accum[2] += outputs.data[child_index * 6 + 2].rgb; - light_accum[3] += outputs.data[child_index * 6 + 3].rgb; - light_accum[4] += outputs.data[child_index * 6 + 4].rgb; - light_accum[5] += outputs.data[child_index * 6 + 5].rgb; - -#else light_accum += outputs.data[child_index].rgb; -#endif - count += 1.0; } float divisor = mix(8.0, count, params.propagation); -#ifdef MODE_ANISOTROPIC - outputs.data[cell_index * 6 + 0] = vec4(light_accum[0] / divisor, 0.0); - outputs.data[cell_index * 6 + 1] = vec4(light_accum[1] / divisor, 0.0); - outputs.data[cell_index * 6 + 2] = vec4(light_accum[2] / divisor, 0.0); - outputs.data[cell_index * 6 + 3] = vec4(light_accum[3] / divisor, 0.0); - outputs.data[cell_index * 6 + 4] = vec4(light_accum[4] / divisor, 0.0); - outputs.data[cell_index * 6 + 5] = vec4(light_accum[5] / divisor, 0.0); - -#else outputs.data[cell_index] = vec4(light_accum / divisor, 0.0); -#endif } #endif @@ -587,40 +465,7 @@ void main() { #ifdef MODE_WRITE_TEXTURE { -#ifdef MODE_ANISOTROPIC - vec3 accum_total = vec3(0.0); - accum_total += outputs.data[cell_index * 6 + 0].rgb; - accum_total += outputs.data[cell_index * 6 + 1].rgb; - accum_total += outputs.data[cell_index * 6 + 2].rgb; - accum_total += outputs.data[cell_index * 6 + 3].rgb; - accum_total += outputs.data[cell_index * 6 + 4].rgb; - accum_total += outputs.data[cell_index * 6 + 5].rgb; - - float accum_total_energy = max(dot(accum_total, GREY_VEC), 0.00001); - vec3 iso_positive = vec3(dot(outputs.data[cell_index * 6 + 0].rgb, GREY_VEC), dot(outputs.data[cell_index * 6 + 2].rgb, GREY_VEC), dot(outputs.data[cell_index * 6 + 4].rgb, GREY_VEC)) / vec3(accum_total_energy); - vec3 iso_negative = vec3(dot(outputs.data[cell_index * 6 + 1].rgb, GREY_VEC), dot(outputs.data[cell_index * 6 + 3].rgb, GREY_VEC), dot(outputs.data[cell_index * 6 + 5].rgb, GREY_VEC)) / vec3(accum_total_energy); - - { - uint aniso_pos = uint(clamp(iso_positive.b * 31.0, 0.0, 31.0)); - aniso_pos |= uint(clamp(iso_positive.g * 63.0, 0.0, 63.0)) << 5; - aniso_pos |= uint(clamp(iso_positive.r * 31.0, 0.0, 31.0)) << 11; - imageStore(aniso_pos_tex, ivec3(posu), uvec4(aniso_pos)); - } - - { - uint aniso_neg = uint(clamp(iso_negative.b * 31.0, 0.0, 31.0)); - aniso_neg |= uint(clamp(iso_negative.g * 63.0, 0.0, 63.0)) << 5; - aniso_neg |= uint(clamp(iso_negative.r * 31.0, 0.0, 31.0)) << 11; - imageStore(aniso_neg_tex, ivec3(posu), uvec4(aniso_neg)); - } - - imageStore(color_tex, ivec3(posu), vec4(accum_total / params.dynamic_range, albedo.a)); - -#else - imageStore(color_tex, ivec3(posu), vec4(outputs.data[cell_index].rgb / params.dynamic_range, albedo.a)); - -#endif } #endif @@ -763,13 +608,6 @@ void main() { color.rgb /= params.dynamic_range; imageStore(color_texture, pos3d, color); //imageStore(color_texture,pos3d,vec4(1,1,1,1)); - -#ifdef MODE_ANISOTROPIC - //do not care about anisotropy for dynamic objects, just store full lit in all directions - imageStore(aniso_pos_texture, pos3d, uvec4(0xFFFF)); - imageStore(aniso_neg_texture, pos3d, uvec4(0xFFFF)); - -#endif // ANISOTROPIC } #endif // MODE_DYNAMIC_SHRINK_PLOT } diff --git a/servers/rendering/renderer_rd/shaders/voxel_gi_debug.glsl b/servers/rendering/renderer_rd/shaders/voxel_gi_debug.glsl index 7d4d72967a..281c496df3 100644 --- a/servers/rendering/renderer_rd/shaders/voxel_gi_debug.glsl +++ b/servers/rendering/renderer_rd/shaders/voxel_gi_debug.glsl @@ -20,11 +20,6 @@ layout(set = 0, binding = 2) uniform texture3D color_tex; layout(set = 0, binding = 3) uniform sampler tex_sampler; -#ifdef USE_ANISOTROPY -layout(set = 0, binding = 4) uniform texture3D aniso_pos_tex; -layout(set = 0, binding = 5) uniform texture3D aniso_neg_tex; -#endif - layout(push_constant, binding = 0, std430) uniform Params { mat4 projection; uint cell_offset; diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index 15ce1dbe63..8af2049ab3 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -71,6 +71,44 @@ static Transform2D _canvas_get_transform(RendererViewport::Viewport *p_viewport, return xf; } +void RendererViewport::_configure_3d_render_buffers(Viewport *p_viewport) { + if (p_viewport->render_buffers.is_valid()) { + if (p_viewport->size.width == 0 || p_viewport->size.height == 0) { + RSG::scene->free(p_viewport->render_buffers); + p_viewport->render_buffers = RID(); + } else { + RS::ViewportScale3D scale_3d = p_viewport->scale_3d; + if (Engine::get_singleton()->is_editor_hint()) { // ignore this inside of the editor + scale_3d = RS::VIEWPORT_SCALE_3D_DISABLED; + } + + int width = p_viewport->size.width; + int height = p_viewport->size.height; + switch (scale_3d) { + case RS::VIEWPORT_SCALE_3D_75_PERCENT: { + width = (width * 3) / 4; + height = (height * 3) / 4; + }; break; + case RS::VIEWPORT_SCALE_3D_50_PERCENT: { + width = width >> 1; + height = height >> 1; + }; break; + case RS::VIEWPORT_SCALE_3D_33_PERCENT: { + width = width / 3; + height = height / 3; + }; break; + case RS::VIEWPORT_SCALE_3D_25_PERCENT: { + width = width >> 2; + height = height >> 2; + }; break; + default: + break; + } + RSG::scene->render_buffers_configure(p_viewport->render_buffers, p_viewport->render_target, width, height, p_viewport->msaa, p_viewport->screen_space_aa, p_viewport->use_debanding, p_viewport->get_view_count()); + } + } +} + void RendererViewport::_draw_3d(Viewport *p_viewport) { RENDER_TIMESTAMP(">Begin Rendering 3D Scene"); @@ -100,7 +138,7 @@ void RendererViewport::_draw_3d(Viewport *p_viewport) { RENDER_TIMESTAMP("<End Rendering 3D Scene"); } -void RendererViewport::_draw_viewport(Viewport *p_viewport, uint32_t p_view_count) { +void RendererViewport::_draw_viewport(Viewport *p_viewport) { if (p_viewport->measure_render_time) { String rt_id = "vp_begin_" + itos(p_viewport->self.get_id()); RSG::storage->capture_timestamp(rt_id); @@ -142,7 +180,8 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport, uint32_t p_view_coun if ((scenario_draw_canvas_bg || can_draw_3d) && !p_viewport->render_buffers.is_valid()) { //wants to draw 3D but there is no render buffer, create p_viewport->render_buffers = RSG::scene->render_buffers_create(); - RSG::scene->render_buffers_configure(p_viewport->render_buffers, p_viewport->render_target, p_viewport->size.width, p_viewport->size.height, p_viewport->msaa, p_viewport->screen_space_aa, p_viewport->use_debanding, p_view_count); + + _configure_3d_render_buffers(p_viewport); } RSG::storage->render_target_request_clear(p_viewport->render_target, bgcolor); @@ -544,7 +583,7 @@ void RendererViewport::draw_viewports() { RSG::storage->render_target_set_as_unused(vp->render_target); if (vp->use_xr && xr_interface.is_valid()) { // override our size, make sure it matches our required size and is created as a stereo target - vp->size = xr_interface->get_render_targetsize(); + vp->size = xr_interface->get_render_target_size(); uint32_t view_count = xr_interface->get_view_count(); RSG::storage->render_target_set_size(vp->render_target, vp->size.x, vp->size.y, view_count); @@ -556,7 +595,7 @@ void RendererViewport::draw_viewports() { RSG::scene->set_debug_draw_mode(vp->debug_draw); // and draw viewport - _draw_viewport(vp, view_count); + _draw_viewport(vp); // measure @@ -580,17 +619,17 @@ void RendererViewport::draw_viewports() { RSG::scene->set_debug_draw_mode(vp->debug_draw); // render standard mono camera - _draw_viewport(vp, 1); + _draw_viewport(vp); if (vp->viewport_to_screen != DisplayServer::INVALID_WINDOW_ID && (!vp->viewport_render_direct_to_screen || !RSG::rasterizer->is_low_end())) { //copy to screen if set as such BlitToScreen blit; blit.render_target = vp->render_target; if (vp->viewport_to_screen_rect != Rect2()) { - blit.rect = vp->viewport_to_screen_rect; + blit.dst_rect = vp->viewport_to_screen_rect; } else { - blit.rect.position = Vector2(); - blit.rect.size = vp->size; + blit.dst_rect.position = Vector2(); + blit.dst_rect.size = vp->size; } if (!blit_to_screen_list.has(vp->viewport_to_screen)) { @@ -648,9 +687,19 @@ void RendererViewport::viewport_set_use_xr(RID p_viewport, bool p_use_xr) { } viewport->use_xr = p_use_xr; - if (viewport->render_buffers.is_valid()) { - RSG::scene->render_buffers_configure(viewport->render_buffers, viewport->render_target, viewport->size.width, viewport->size.height, viewport->msaa, viewport->screen_space_aa, viewport->use_debanding, viewport->get_view_count()); + _configure_3d_render_buffers(viewport); +} + +void RendererViewport::viewport_set_scale_3d(RID p_viewport, RenderingServer::ViewportScale3D p_scale_3d) { + Viewport *viewport = viewport_owner.getornull(p_viewport); + ERR_FAIL_COND(!viewport); + + if (viewport->scale_3d == p_scale_3d) { + return; } + + viewport->scale_3d = p_scale_3d; + _configure_3d_render_buffers(viewport); } uint32_t RendererViewport::Viewport::get_view_count() { @@ -677,14 +726,7 @@ void RendererViewport::viewport_set_size(RID p_viewport, int p_width, int p_heig viewport->size = Size2(p_width, p_height); uint32_t view_count = viewport->get_view_count(); RSG::storage->render_target_set_size(viewport->render_target, p_width, p_height, view_count); - if (viewport->render_buffers.is_valid()) { - if (p_width == 0 || p_height == 0) { - RSG::scene->free(viewport->render_buffers); - viewport->render_buffers = RID(); - } else { - RSG::scene->render_buffers_configure(viewport->render_buffers, viewport->render_target, viewport->size.width, viewport->size.height, viewport->msaa, viewport->screen_space_aa, viewport->use_debanding, view_count); - } - } + _configure_3d_render_buffers(viewport); viewport->occlusion_buffer_dirty = true; } @@ -915,9 +957,7 @@ void RendererViewport::viewport_set_msaa(RID p_viewport, RS::ViewportMSAA p_msaa return; } viewport->msaa = p_msaa; - if (viewport->render_buffers.is_valid()) { - RSG::scene->render_buffers_configure(viewport->render_buffers, viewport->render_target, viewport->size.width, viewport->size.height, p_msaa, viewport->screen_space_aa, viewport->use_debanding, viewport->get_view_count()); - } + _configure_3d_render_buffers(viewport); } void RendererViewport::viewport_set_screen_space_aa(RID p_viewport, RS::ViewportScreenSpaceAA p_mode) { @@ -928,9 +968,7 @@ void RendererViewport::viewport_set_screen_space_aa(RID p_viewport, RS::Viewport return; } viewport->screen_space_aa = p_mode; - if (viewport->render_buffers.is_valid()) { - RSG::scene->render_buffers_configure(viewport->render_buffers, viewport->render_target, viewport->size.width, viewport->size.height, viewport->msaa, p_mode, viewport->use_debanding, viewport->get_view_count()); - } + _configure_3d_render_buffers(viewport); } void RendererViewport::viewport_set_use_debanding(RID p_viewport, bool p_use_debanding) { @@ -941,9 +979,7 @@ void RendererViewport::viewport_set_use_debanding(RID p_viewport, bool p_use_deb return; } viewport->use_debanding = p_use_debanding; - if (viewport->render_buffers.is_valid()) { - RSG::scene->render_buffers_configure(viewport->render_buffers, viewport->render_target, viewport->size.width, viewport->size.height, viewport->msaa, viewport->screen_space_aa, p_use_debanding, viewport->get_view_count()); - } + _configure_3d_render_buffers(viewport); } void RendererViewport::viewport_set_use_occlusion_culling(RID p_viewport, bool p_use_occlusion_culling) { diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h index ac7a35f97d..f6095e18d7 100644 --- a/servers/rendering/renderer_viewport.h +++ b/servers/rendering/renderer_viewport.h @@ -49,6 +49,8 @@ public: bool use_xr; /* use xr interface to override camera positioning and projection matrices and control output */ + RS::ViewportScale3D scale_3d = RenderingServer::VIEWPORT_SCALE_3D_DISABLED; + Size2i size; RID camera; RID scenario; @@ -192,8 +194,9 @@ public: int total_draw_calls_used = 0; private: + void _configure_3d_render_buffers(Viewport *p_viewport); void _draw_3d(Viewport *p_viewport); - void _draw_viewport(Viewport *p_viewport, uint32_t p_view_count = 1); + void _draw_viewport(Viewport *p_viewport); int occlusion_rays_per_thread = 512; @@ -204,6 +207,7 @@ public: void viewport_initialize(RID p_rid); void viewport_set_use_xr(RID p_viewport, bool p_use_xr); + void viewport_set_scale_3d(RID p_viewport, RenderingServer::ViewportScale3D p_scale_3d); void viewport_set_size(RID p_viewport, int p_width, int p_height); diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index b302c6b793..70f676e5ac 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -478,12 +478,28 @@ void RenderingDevice::_bind_methods() { ClassDB::bind_method(D_METHOD("get_memory_usage"), &RenderingDevice::get_memory_usage); + ClassDB::bind_method(D_METHOD("get_driver_resource", "resource", "rid", "index"), &RenderingDevice::get_driver_resource); + BIND_CONSTANT(BARRIER_MASK_RASTER); BIND_CONSTANT(BARRIER_MASK_COMPUTE); BIND_CONSTANT(BARRIER_MASK_TRANSFER); BIND_CONSTANT(BARRIER_MASK_ALL); BIND_CONSTANT(BARRIER_MASK_NO_BARRIER); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_DEVICE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_PHYSICAL_DEVICE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_INSTANCE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_QUEUE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_QUEUE_FAMILY_INDEX); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_IMAGE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_IMAGE_VIEW); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_IMAGE_NATIVE_TEXTURE_FORMAT); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_SAMPLER); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_DESCRIPTOR_SET); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_BUFFER); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_COMPUTE_PIPELINE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_RENDER_PIPELINE); + BIND_ENUM_CONSTANT(DATA_FORMAT_R4G4_UNORM_PACK8); BIND_ENUM_CONSTANT(DATA_FORMAT_R4G4B4A4_UNORM_PACK16); BIND_ENUM_CONSTANT(DATA_FORMAT_B4G4R4A4_UNORM_PACK16); diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index e2d207dab2..5eb8f1cead 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -60,6 +60,23 @@ public: DEVICE_DIRECTX }; + enum DriverResource { + DRIVER_RESOURCE_VULKAN_DEVICE = 0, + DRIVER_RESOURCE_VULKAN_PHYSICAL_DEVICE, + DRIVER_RESOURCE_VULKAN_INSTANCE, + DRIVER_RESOURCE_VULKAN_QUEUE, + DRIVER_RESOURCE_VULKAN_QUEUE_FAMILY_INDEX, + DRIVER_RESOURCE_VULKAN_IMAGE, + DRIVER_RESOURCE_VULKAN_IMAGE_VIEW, + DRIVER_RESOURCE_VULKAN_IMAGE_NATIVE_TEXTURE_FORMAT, + DRIVER_RESOURCE_VULKAN_SAMPLER, + DRIVER_RESOURCE_VULKAN_DESCRIPTOR_SET, + DRIVER_RESOURCE_VULKAN_BUFFER, + DRIVER_RESOURCE_VULKAN_COMPUTE_PIPELINE, + DRIVER_RESOURCE_VULKAN_RENDER_PIPELINE, + //next driver continue enum from 1000 to keep order + }; + enum ShaderStage { SHADER_STAGE_VERTEX, SHADER_STAGE_FRAGMENT, @@ -495,6 +512,7 @@ public: virtual bool texture_is_format_supported_for_usage(DataFormat p_format, uint32_t p_usage) const = 0; virtual bool texture_is_shared(RID p_texture) = 0; virtual bool texture_is_valid(RID p_texture) = 0; + virtual Size2i texture_size(RID p_texture) = 0; virtual Error texture_copy(RID p_from_texture, RID p_to_texture, const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_size, uint32_t p_src_mipmap, uint32_t p_dst_mipmap, uint32_t p_src_layer, uint32_t p_dst_layer, uint32_t p_post_barrier = BARRIER_MASK_ALL) = 0; virtual Error texture_clear(RID p_texture, const Color &p_color, uint32_t p_base_mipmap, uint32_t p_mipmaps, uint32_t p_base_layer, uint32_t p_layers, uint32_t p_post_barrier = BARRIER_MASK_ALL) = 0; @@ -1182,6 +1200,8 @@ public: virtual String get_device_name() const = 0; virtual String get_device_pipeline_cache_uuid() const = 0; + virtual uint64_t get_driver_resource(DriverResource p_resource, RID p_rid = RID(), uint64_t p_index = 0) = 0; + static RenderingDevice *get_singleton(); RenderingDevice(); @@ -1216,6 +1236,7 @@ protected: Vector<int64_t> _draw_list_switch_to_next_pass_split(uint32_t p_splits); }; +VARIANT_ENUM_CAST(RenderingDevice::DriverResource) VARIANT_ENUM_CAST(RenderingDevice::ShaderStage) VARIANT_ENUM_CAST(RenderingDevice::ShaderLanguage) VARIANT_ENUM_CAST(RenderingDevice::CompareOperator) diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index c1336ee42d..56e79b62f2 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -526,6 +526,7 @@ public: FUNCRIDSPLIT(viewport) FUNC2(viewport_set_use_xr, RID, bool) + FUNC2(viewport_set_scale_3d, RID, ViewportScale3D) FUNC3(viewport_set_size, RID, int, int) FUNC2(viewport_set_active, RID, bool) @@ -762,6 +763,7 @@ public: FUNC4(canvas_item_add_circle, RID, const Point2 &, float, const Color &) FUNC6(canvas_item_add_texture_rect, RID, const Rect2 &, RID, bool, const Color &, bool) FUNC7(canvas_item_add_texture_rect_region, RID, const Rect2 &, RID, const Rect2 &, const Color &, bool, bool) + FUNC7(canvas_item_add_msdf_texture_rect_region, RID, const Rect2 &, RID, const Rect2 &, const Color &, int, float) FUNC10(canvas_item_add_nine_patch, RID, const Rect2 &, const Rect2 &, RID, const Vector2 &, const Vector2 &, NinePatchAxisMode, NinePatchAxisMode, bool, const Color &) FUNC6(canvas_item_add_primitive, RID, const Vector<Point2> &, const Vector<Color> &, const Vector<Point2> &, RID, float) FUNC5(canvas_item_add_polygon, RID, const Vector<Point2> &, const Vector<Color> &, const Vector<Point2> &, RID) diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 222ea9e622..5f349e5e33 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -500,7 +500,7 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint case RS::ARRAY_CUSTOM1: case RS::ARRAY_CUSTOM2: case RS::ARRAY_CUSTOM3: { - uint32_t type = (p_format >> (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * (RS::ARRAY_CUSTOM0 - ai))) & ARRAY_FORMAT_CUSTOM_MASK; + uint32_t type = (p_format >> (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * (ai - RS::ARRAY_CUSTOM0))) & ARRAY_FORMAT_CUSTOM_MASK; switch (type) { case ARRAY_CUSTOM_RGBA8_UNORM: case ARRAY_CUSTOM_RGBA8_SNORM: @@ -541,14 +541,14 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint ERR_FAIL_COND_V(p_arrays[ai].get_type() != Variant::PACKED_FLOAT32_ARRAY, ERR_INVALID_PARAMETER); Vector<float> array = p_arrays[ai]; - int32_t s = ARRAY_CUSTOM_R_FLOAT - ai + 1; + int32_t s = type - ARRAY_CUSTOM_R_FLOAT + 1; ERR_FAIL_COND_V(array.size() != p_vertex_array_len * s, ERR_INVALID_PARAMETER); const float *src = array.ptr(); for (int i = 0; i < p_vertex_array_len; i++) { - memcpy(&aw[p_offsets[ai] + i * p_attrib_stride], &src[i * s], 4 * s); + memcpy(&aw[p_offsets[ai] + i * p_attrib_stride], &src[i * s], sizeof(float) * s); } } break; default: { @@ -938,6 +938,13 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa } } + for (uint32_t i = 0; i < RS::ARRAY_CUSTOM_COUNT; ++i) { + // include custom array format type. + if (format & (1 << (ARRAY_CUSTOM0 + i))) { + format |= (RS::ARRAY_FORMAT_CUSTOM_MASK << (RS::ARRAY_FORMAT_CUSTOM_BASE + i * RS::ARRAY_FORMAT_CUSTOM_BITS)) & p_compress_format; + } + } + uint32_t offsets[RS::ARRAY_MAX]; uint32_t vertex_element_size; @@ -1191,7 +1198,7 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t case RS::ARRAY_CUSTOM1: case RS::ARRAY_CUSTOM2: case RS::ARRAY_CUSTOM3: { - uint32_t type = (p_format >> (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * (RS::ARRAY_CUSTOM0 - i))) & ARRAY_FORMAT_CUSTOM_MASK; + uint32_t type = (p_format >> (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * (i - RS::ARRAY_CUSTOM0))) & ARRAY_FORMAT_CUSTOM_MASK; switch (type) { case ARRAY_CUSTOM_RGBA8_UNORM: case ARRAY_CUSTOM_RGBA8_SNORM: @@ -1219,6 +1226,8 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t uint32_t s = type - ARRAY_CUSTOM_R_FLOAT + 1; Vector<float> arr; + arr.resize(s * p_vertex_len); + float *w = arr.ptrw(); for (int j = 0; j < p_vertex_len; j++) { @@ -1467,6 +1476,11 @@ ShaderLanguage::DataType RenderingServer::global_variable_type_get_shader_dataty } } +RenderingDevice *RenderingServer::get_rendering_device() const { + // return the rendering device we're using globally + return RenderingDevice::get_singleton(); +} + RenderingDevice *RenderingServer::create_local_rendering_device() const { return RenderingDevice::get_singleton()->create_local_device(); } @@ -2138,6 +2152,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("viewport_create"), &RenderingServer::viewport_create); ClassDB::bind_method(D_METHOD("viewport_set_use_xr", "viewport", "use_xr"), &RenderingServer::viewport_set_use_xr); + ClassDB::bind_method(D_METHOD("viewport_set_scale_3d", "viewport", "scale"), &RenderingServer::viewport_set_scale_3d); ClassDB::bind_method(D_METHOD("viewport_set_size", "viewport", "width", "height"), &RenderingServer::viewport_set_size); ClassDB::bind_method(D_METHOD("viewport_set_active", "viewport", "active"), &RenderingServer::viewport_set_active); ClassDB::bind_method(D_METHOD("viewport_set_parent_viewport", "viewport", "parent_viewport"), &RenderingServer::viewport_set_parent_viewport); @@ -2255,6 +2270,12 @@ void RenderingServer::_bind_methods() { BIND_ENUM_CONSTANT(VIEWPORT_DEBUG_DRAW_CLUSTER_REFLECTION_PROBES); BIND_ENUM_CONSTANT(VIEWPORT_DEBUG_DRAW_OCCLUDERS); + BIND_ENUM_CONSTANT(VIEWPORT_SCALE_3D_DISABLED); + BIND_ENUM_CONSTANT(VIEWPORT_SCALE_3D_75_PERCENT); + BIND_ENUM_CONSTANT(VIEWPORT_SCALE_3D_50_PERCENT); + BIND_ENUM_CONSTANT(VIEWPORT_SCALE_3D_33_PERCENT); + BIND_ENUM_CONSTANT(VIEWPORT_SCALE_3D_25_PERCENT); + /* SKY API */ ClassDB::bind_method(D_METHOD("sky_create"), &RenderingServer::sky_create); @@ -2707,6 +2728,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("force_sync"), &RenderingServer::sync); ClassDB::bind_method(D_METHOD("force_draw", "swap_buffers", "frame_step"), &RenderingServer::draw, DEFVAL(true), DEFVAL(0.0)); + ClassDB::bind_method(D_METHOD("get_rendering_device"), &RenderingServer::get_rendering_device); ClassDB::bind_method(D_METHOD("create_local_rendering_device"), &RenderingServer::create_local_rendering_device); } @@ -2795,6 +2817,12 @@ RenderingServer::RenderingServer() { "rendering/vulkan/rendering/back_end", PROPERTY_HINT_ENUM, "Forward Clustered (Supports Desktop Only),Forward Mobile (Supports Desktop and Mobile)")); + GLOBAL_DEF("rendering/3d/viewport/scale", 0); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/3d/viewport/scale", + PropertyInfo(Variant::INT, + "rendering/3d/viewport/scale", + PROPERTY_HINT_ENUM, "Disabled,75%,50%,33%,25%")); + GLOBAL_DEF("rendering/shader_compiler/shader_cache/enabled", true); GLOBAL_DEF("rendering/shader_compiler/shader_cache/compress", true); GLOBAL_DEF("rendering/shader_compiler/shader_cache/use_zstd_compression", true); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 545270dcd9..b79aaefab4 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -752,9 +752,19 @@ public: CANVAS_ITEM_TEXTURE_REPEAT_MAX, }; + enum ViewportScale3D { + VIEWPORT_SCALE_3D_DISABLED, + VIEWPORT_SCALE_3D_75_PERCENT, + VIEWPORT_SCALE_3D_50_PERCENT, + VIEWPORT_SCALE_3D_33_PERCENT, + VIEWPORT_SCALE_3D_25_PERCENT, + VIEWPORT_SCALE_3D_MAX, + }; + virtual RID viewport_create() = 0; virtual void viewport_set_use_xr(RID p_viewport, bool p_use_xr) = 0; + virtual void viewport_set_scale_3d(RID p_viewport, ViewportScale3D p_scale_3d) = 0; virtual void viewport_set_size(RID p_viewport, int p_width, int p_height) = 0; virtual void viewport_set_active(RID p_viewport, bool p_active) = 0; virtual void viewport_set_parent_viewport(RID p_viewport, RID p_parent_viewport) = 0; @@ -1248,6 +1258,7 @@ public: virtual void canvas_item_add_circle(RID p_item, const Point2 &p_pos, float p_radius, const Color &p_color) = 0; virtual void canvas_item_add_texture_rect(RID p_item, const Rect2 &p_rect, RID p_texture, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) = 0; virtual void canvas_item_add_texture_rect_region(RID p_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = false) = 0; + virtual void canvas_item_add_msdf_texture_rect_region(RID p_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, float p_px_range = 1.0) = 0; virtual void canvas_item_add_nine_patch(RID p_item, const Rect2 &p_rect, const Rect2 &p_source, RID p_texture, const Vector2 &p_topleft, const Vector2 &p_bottomright, NinePatchAxisMode p_x_axis_mode = NINE_PATCH_STRETCH, NinePatchAxisMode p_y_axis_mode = NINE_PATCH_STRETCH, bool p_draw_center = true, const Color &p_modulate = Color(1, 1, 1)) = 0; virtual void canvas_item_add_primitive(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs, RID p_texture, float p_width = 1.0) = 0; virtual void canvas_item_add_polygon(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), RID p_texture = RID()) = 0; @@ -1482,6 +1493,7 @@ public: virtual void set_print_gpu_profile(bool p_enable) = 0; + RenderingDevice *get_rendering_device() const; RenderingDevice *create_local_rendering_device() const; bool is_render_loop_enabled() const; @@ -1542,6 +1554,7 @@ VARIANT_ENUM_CAST(RenderingServer::ViewportDebugDraw); VARIANT_ENUM_CAST(RenderingServer::ViewportOcclusionCullingBuildQuality); VARIANT_ENUM_CAST(RenderingServer::ViewportSDFOversize); VARIANT_ENUM_CAST(RenderingServer::ViewportSDFScale); +VARIANT_ENUM_CAST(RenderingServer::ViewportScale3D); VARIANT_ENUM_CAST(RenderingServer::SkyMode); VARIANT_ENUM_CAST(RenderingServer::EnvironmentBG); VARIANT_ENUM_CAST(RenderingServer::EnvironmentAmbientSource); diff --git a/servers/text_server.cpp b/servers/text_server.cpp index 6bace8cf9e..2f343e8f80 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -216,83 +216,130 @@ void TextServer::_bind_methods() { ClassDB::bind_method(D_METHOD("free_rid", "rid"), &TextServer::free); // shouldn't conflict with Object::free() /* Font Interface */ - ClassDB::bind_method(D_METHOD("create_font_system", "name", "base_size"), &TextServer::create_font_system, DEFVAL(16)); - ClassDB::bind_method(D_METHOD("create_font_resource", "filename", "base_size"), &TextServer::create_font_resource, DEFVAL(16)); - ClassDB::bind_method(D_METHOD("create_font_memory", "data", "type", "base_size"), &TextServer::_create_font_memory, DEFVAL(16)); - ClassDB::bind_method(D_METHOD("create_font_bitmap", "height", "ascent", "base_size"), &TextServer::create_font_bitmap); - ClassDB::bind_method(D_METHOD("font_bitmap_add_texture", "font", "texture"), &TextServer::font_bitmap_add_texture); - ClassDB::bind_method(D_METHOD("font_bitmap_add_char", "font", "char", "texture_idx", "rect", "align", "advance"), &TextServer::font_bitmap_add_char); - ClassDB::bind_method(D_METHOD("font_bitmap_add_kerning_pair", "font", "A", "B", "kerning"), &TextServer::font_bitmap_add_kerning_pair); + ClassDB::bind_method(D_METHOD("create_font"), &TextServer::create_font); - ClassDB::bind_method(D_METHOD("font_get_height", "font", "size"), &TextServer::font_get_height); - ClassDB::bind_method(D_METHOD("font_get_ascent", "font", "size"), &TextServer::font_get_ascent); - ClassDB::bind_method(D_METHOD("font_get_descent", "font", "size"), &TextServer::font_get_descent); + ClassDB::bind_method(D_METHOD("font_set_data", "data"), &TextServer::font_set_data); - ClassDB::bind_method(D_METHOD("font_get_underline_position", "font", "size"), &TextServer::font_get_underline_position); - ClassDB::bind_method(D_METHOD("font_get_underline_thickness", "font", "size"), &TextServer::font_get_underline_thickness); + ClassDB::bind_method(D_METHOD("font_set_antialiased", "font_rid", "antialiased"), &TextServer::font_set_antialiased); + ClassDB::bind_method(D_METHOD("font_is_antialiased", "font_rid"), &TextServer::font_is_antialiased); - ClassDB::bind_method(D_METHOD("font_get_spacing_space", "font"), &TextServer::font_get_spacing_space); - ClassDB::bind_method(D_METHOD("font_set_spacing_space", "font", "value"), &TextServer::font_set_spacing_space); + ClassDB::bind_method(D_METHOD("font_set_multichannel_signed_distance_field", "font_rid", "msdf"), &TextServer::font_set_multichannel_signed_distance_field); + ClassDB::bind_method(D_METHOD("font_is_multichannel_signed_distance_field", "font_rid"), &TextServer::font_is_multichannel_signed_distance_field); - ClassDB::bind_method(D_METHOD("font_get_spacing_glyph", "font"), &TextServer::font_get_spacing_glyph); - ClassDB::bind_method(D_METHOD("font_set_spacing_glyph", "font", "value"), &TextServer::font_set_spacing_glyph); + ClassDB::bind_method(D_METHOD("font_set_msdf_pixel_range", "font_rid", "msdf_pixel_range"), &TextServer::font_set_msdf_pixel_range); + ClassDB::bind_method(D_METHOD("font_get_msdf_pixel_range", "font_rid"), &TextServer::font_get_msdf_pixel_range); - ClassDB::bind_method(D_METHOD("font_set_antialiased", "font", "antialiased"), &TextServer::font_set_antialiased); - ClassDB::bind_method(D_METHOD("font_get_antialiased", "font"), &TextServer::font_get_antialiased); + ClassDB::bind_method(D_METHOD("font_set_msdf_size", "font_rid", "msdf_size"), &TextServer::font_set_msdf_size); + ClassDB::bind_method(D_METHOD("font_get_msdf_size", "font_rid"), &TextServer::font_get_msdf_size); - ClassDB::bind_method(D_METHOD("font_get_feature_list", "font"), &TextServer::font_get_feature_list); - ClassDB::bind_method(D_METHOD("font_get_variation_list", "font"), &TextServer::font_get_variation_list); + ClassDB::bind_method(D_METHOD("font_set_fixed_size", "font_rid", "fixed_size"), &TextServer::font_set_fixed_size); + ClassDB::bind_method(D_METHOD("font_get_fixed_size", "font_rid"), &TextServer::font_get_fixed_size); - ClassDB::bind_method(D_METHOD("font_set_variation", "font", "tag", "value"), &TextServer::font_set_variation); - ClassDB::bind_method(D_METHOD("font_get_variation", "font", "tag"), &TextServer::font_get_variation); + ClassDB::bind_method(D_METHOD("font_set_force_autohinter", "font_rid", "force_autohinter"), &TextServer::font_set_force_autohinter); + ClassDB::bind_method(D_METHOD("font_is_force_autohinter", "font_rid"), &TextServer::font_is_force_autohinter); - ClassDB::bind_method(D_METHOD("font_set_hinting", "font", "hinting"), &TextServer::font_set_hinting); - ClassDB::bind_method(D_METHOD("font_get_hinting", "font"), &TextServer::font_get_hinting); + ClassDB::bind_method(D_METHOD("font_set_hinting", "font_rid", "_hinting"), &TextServer::font_set_hinting); + ClassDB::bind_method(D_METHOD("font_get_hinting", "font_rid"), &TextServer::font_get_hinting); - ClassDB::bind_method(D_METHOD("font_set_distance_field_hint", "font", "distance_field"), &TextServer::font_set_distance_field_hint); - ClassDB::bind_method(D_METHOD("font_get_distance_field_hint", "font"), &TextServer::font_get_distance_field_hint); + ClassDB::bind_method(D_METHOD("font_set_variation_coordinates", "font_rid", "variation_coordinates"), &TextServer::font_set_variation_coordinates); + ClassDB::bind_method(D_METHOD("font_get_variation_coordinates", "font_rid"), &TextServer::font_get_variation_coordinates); - ClassDB::bind_method(D_METHOD("font_set_force_autohinter", "font", "enabeld"), &TextServer::font_set_force_autohinter); - ClassDB::bind_method(D_METHOD("font_get_force_autohinter", "font"), &TextServer::font_get_force_autohinter); + ClassDB::bind_method(D_METHOD("font_set_oversampling", "font_rid", "oversampling"), &TextServer::font_set_oversampling); + ClassDB::bind_method(D_METHOD("font_get_oversampling", "font_rid"), &TextServer::font_get_oversampling); - ClassDB::bind_method(D_METHOD("font_has_char", "font", "char"), &TextServer::font_has_char); - ClassDB::bind_method(D_METHOD("font_get_supported_chars", "font"), &TextServer::font_get_supported_chars); + ClassDB::bind_method(D_METHOD("font_get_size_cache_list", "font_rid"), &TextServer::font_get_size_cache_list); + ClassDB::bind_method(D_METHOD("font_clear_size_cache", "font_rid"), &TextServer::font_clear_size_cache); + ClassDB::bind_method(D_METHOD("font_remove_size_cache", "font_rid", "size"), &TextServer::font_remove_size_cache); - ClassDB::bind_method(D_METHOD("font_has_outline", "font"), &TextServer::font_has_outline); - ClassDB::bind_method(D_METHOD("font_get_base_size", "font"), &TextServer::font_get_base_size); + ClassDB::bind_method(D_METHOD("font_set_ascent", "font_rid", "size", "ascent"), &TextServer::font_set_ascent); + ClassDB::bind_method(D_METHOD("font_get_ascent", "font_rid", "size"), &TextServer::font_get_ascent); - ClassDB::bind_method(D_METHOD("font_is_language_supported", "font", "language"), &TextServer::font_is_language_supported); - ClassDB::bind_method(D_METHOD("font_set_language_support_override", "font", "language", "supported"), &TextServer::font_set_language_support_override); + ClassDB::bind_method(D_METHOD("font_set_descent", "font_rid", "size", "descent"), &TextServer::font_set_descent); + ClassDB::bind_method(D_METHOD("font_get_descent", "font_rid", "size"), &TextServer::font_get_descent); - ClassDB::bind_method(D_METHOD("font_get_language_support_override", "font", "language"), &TextServer::font_get_language_support_override); - ClassDB::bind_method(D_METHOD("font_remove_language_support_override", "font", "language"), &TextServer::font_remove_language_support_override); - ClassDB::bind_method(D_METHOD("font_get_language_support_overrides", "font"), &TextServer::font_get_language_support_overrides); + ClassDB::bind_method(D_METHOD("font_set_underline_position", "font_rid", "size", "underline_position"), &TextServer::font_set_underline_position); + ClassDB::bind_method(D_METHOD("font_get_underline_position", "font_rid", "size"), &TextServer::font_get_underline_position); - ClassDB::bind_method(D_METHOD("font_is_script_supported", "font", "script"), &TextServer::font_is_script_supported); - ClassDB::bind_method(D_METHOD("font_set_script_support_override", "font", "script", "supported"), &TextServer::font_set_script_support_override); + ClassDB::bind_method(D_METHOD("font_set_underline_thickness", "font_rid", "size", "underline_thickness"), &TextServer::font_set_underline_thickness); + ClassDB::bind_method(D_METHOD("font_get_underline_thickness", "font_rid", "size"), &TextServer::font_get_underline_thickness); - ClassDB::bind_method(D_METHOD("font_get_script_support_override", "font", "script"), &TextServer::font_get_script_support_override); - ClassDB::bind_method(D_METHOD("font_remove_script_support_override", "font", "script"), &TextServer::font_remove_script_support_override); - ClassDB::bind_method(D_METHOD("font_get_script_support_overrides", "font"), &TextServer::font_get_script_support_overrides); + ClassDB::bind_method(D_METHOD("font_set_scale", "font_rid", "size", "scale"), &TextServer::font_set_scale); + ClassDB::bind_method(D_METHOD("font_get_scale", "font_rid", "size"), &TextServer::font_get_scale); - ClassDB::bind_method(D_METHOD("font_get_glyph_index", "font", "char", "variation_selector"), &TextServer::font_get_glyph_index, DEFVAL(0x0000)); - ClassDB::bind_method(D_METHOD("font_get_glyph_advance", "font", "index", "size"), &TextServer::font_get_glyph_advance); - ClassDB::bind_method(D_METHOD("font_get_glyph_kerning", "font", "index_a", "index_b", "size"), &TextServer::font_get_glyph_kerning); + ClassDB::bind_method(D_METHOD("font_set_spacing", "font_rid", "size", "spacing", "value"), &TextServer::font_set_spacing); + ClassDB::bind_method(D_METHOD("font_get_spacing", "font_rid", "size", "spacing"), &TextServer::font_get_spacing); - ClassDB::bind_method(D_METHOD("font_draw_glyph", "font", "canvas", "size", "pos", "index", "color"), &TextServer::font_draw_glyph, DEFVAL(Color(1, 1, 1))); - ClassDB::bind_method(D_METHOD("font_draw_glyph_outline", "font", "canvas", "size", "outline_size", "pos", "index", "color"), &TextServer::font_draw_glyph_outline, DEFVAL(Color(1, 1, 1))); + ClassDB::bind_method(D_METHOD("font_get_texture_count", "font_rid", "size"), &TextServer::font_get_texture_count); + ClassDB::bind_method(D_METHOD("font_clear_textures", "font_rid", "size"), &TextServer::font_clear_textures); + ClassDB::bind_method(D_METHOD("font_remove_texture", "font_rid", "size", "texture_index"), &TextServer::font_remove_texture); - ClassDB::bind_method(D_METHOD("font_get_oversampling"), &TextServer::font_get_oversampling); - ClassDB::bind_method(D_METHOD("font_set_oversampling", "oversampling"), &TextServer::font_set_oversampling); + ClassDB::bind_method(D_METHOD("font_set_texture_image", "font_rid", "size", "texture_index", "image"), &TextServer::font_set_texture_image); + ClassDB::bind_method(D_METHOD("font_get_texture_image", "font_rid", "size", "texture_index"), &TextServer::font_get_texture_image); - ClassDB::bind_method(D_METHOD("get_system_fonts"), &TextServer::get_system_fonts); + ClassDB::bind_method(D_METHOD("font_set_texture_offsets", "font_rid", "size", "texture_index", "offset"), &TextServer::font_set_texture_offsets); + ClassDB::bind_method(D_METHOD("font_get_texture_offsets", "font_rid", "size", "texture_index"), &TextServer::font_get_texture_offsets); - ClassDB::bind_method(D_METHOD("get_hex_code_box_size", "size", "index"), &TextServer::get_hex_code_box_size); - ClassDB::bind_method(D_METHOD("draw_hex_code_box", "canvas", "size", "pos", "index", "color"), &TextServer::draw_hex_code_box); + ClassDB::bind_method(D_METHOD("font_get_glyph_list", "font_rid", "size"), &TextServer::font_get_glyph_list); + ClassDB::bind_method(D_METHOD("font_clear_glyphs", "font_rid", "size"), &TextServer::font_clear_glyphs); + ClassDB::bind_method(D_METHOD("font_remove_glyph", "font_rid", "size", "glyph"), &TextServer::font_remove_glyph); + + ClassDB::bind_method(D_METHOD("font_get_glyph_advance", "font_rid", "size", "glyph"), &TextServer::font_get_glyph_advance); + ClassDB::bind_method(D_METHOD("font_set_glyph_advance", "font_rid", "size", "glyph", "advance"), &TextServer::font_set_glyph_advance); + + ClassDB::bind_method(D_METHOD("font_get_glyph_offset", "font_rid", "size", "glyph"), &TextServer::font_get_glyph_offset); + ClassDB::bind_method(D_METHOD("font_set_glyph_offset", "font_rid", "size", "glyph", "offset"), &TextServer::font_set_glyph_offset); + + ClassDB::bind_method(D_METHOD("font_get_glyph_size", "font_rid", "size", "glyph"), &TextServer::font_get_glyph_size); + ClassDB::bind_method(D_METHOD("font_set_glyph_size", "font_rid", "size", "glyph", "gl_size"), &TextServer::font_set_glyph_size); + + ClassDB::bind_method(D_METHOD("font_get_glyph_uv_rect", "font_rid", "size", "glyph"), &TextServer::font_get_glyph_uv_rect); + ClassDB::bind_method(D_METHOD("font_set_glyph_uv_rect", "font_rid", "size", "glyph", "uv_rect"), &TextServer::font_set_glyph_uv_rect); + + ClassDB::bind_method(D_METHOD("font_get_glyph_texture_idx", "font_rid", "size", "glyph"), &TextServer::font_get_glyph_texture_idx); + ClassDB::bind_method(D_METHOD("font_set_glyph_texture_idx", "font_rid", "size", "glyph", "texture_idx"), &TextServer::font_set_glyph_texture_idx); ClassDB::bind_method(D_METHOD("font_get_glyph_contours", "font", "size", "index"), &TextServer::_font_get_glyph_contours); + ClassDB::bind_method(D_METHOD("font_get_kerning_list", "font_rid", "size"), &TextServer::font_get_kerning_list); + ClassDB::bind_method(D_METHOD("font_clear_kerning_map", "font_rid", "size"), &TextServer::font_clear_kerning_map); + ClassDB::bind_method(D_METHOD("font_remove_kerning", "font_rid", "size", "glyph_pair"), &TextServer::font_remove_kerning); + + ClassDB::bind_method(D_METHOD("font_set_kerning", "font_rid", "size", "glyph_pair", "kerning"), &TextServer::font_set_kerning); + ClassDB::bind_method(D_METHOD("font_get_kerning", "font_rid", "size", "glyph_pair"), &TextServer::font_get_kerning); + + ClassDB::bind_method(D_METHOD("font_get_glyph_index", "font_rid", "size", "char", "variation_selector"), &TextServer::font_get_glyph_index); + + ClassDB::bind_method(D_METHOD("font_has_char", "font_rid", "char"), &TextServer::font_has_char); + ClassDB::bind_method(D_METHOD("font_get_supported_chars", "font_rid"), &TextServer::font_get_supported_chars); + + ClassDB::bind_method(D_METHOD("font_render_range", "font_rid", "size", "start", "end"), &TextServer::font_render_range); + ClassDB::bind_method(D_METHOD("font_render_glyph", "font_rid", "size", "index"), &TextServer::font_render_glyph); + + ClassDB::bind_method(D_METHOD("font_draw_glyph", "font_rid", "canvas", "size", "pos", "index", "color"), &TextServer::font_draw_glyph, DEFVAL(Color(1, 1, 1))); + ClassDB::bind_method(D_METHOD("font_draw_glyph_outline", "font_rid", "canvas", "size", "outline_size", "pos", "index", "color"), &TextServer::font_draw_glyph_outline, DEFVAL(Color(1, 1, 1))); + + ClassDB::bind_method(D_METHOD("font_is_language_supported", "font_rid", "language"), &TextServer::font_is_language_supported); + ClassDB::bind_method(D_METHOD("font_set_language_support_override", "font_rid", "language", "supported"), &TextServer::font_set_language_support_override); + ClassDB::bind_method(D_METHOD("font_get_language_support_override", "font_rid", "language"), &TextServer::font_get_language_support_override); + ClassDB::bind_method(D_METHOD("font_remove_language_support_override", "font_rid", "language"), &TextServer::font_remove_language_support_override); + ClassDB::bind_method(D_METHOD("font_get_language_support_overrides", "font_rid"), &TextServer::font_get_language_support_overrides); + + ClassDB::bind_method(D_METHOD("font_is_script_supported", "font_rid", "script"), &TextServer::font_is_script_supported); + ClassDB::bind_method(D_METHOD("font_set_script_support_override", "font_rid", "script", "supported"), &TextServer::font_set_script_support_override); + ClassDB::bind_method(D_METHOD("font_get_script_support_override", "font_rid", "script"), &TextServer::font_get_script_support_override); + ClassDB::bind_method(D_METHOD("font_remove_script_support_override", "font_rid", "script"), &TextServer::font_remove_script_support_override); + ClassDB::bind_method(D_METHOD("font_get_script_support_overrides", "font_rid"), &TextServer::font_get_script_support_overrides); + + ClassDB::bind_method(D_METHOD("font_supported_feature_list", "font_rid"), &TextServer::font_supported_feature_list); + ClassDB::bind_method(D_METHOD("font_supported_variation_list", "font_rid"), &TextServer::font_supported_variation_list); + + ClassDB::bind_method(D_METHOD("font_get_global_oversampling"), &TextServer::font_get_global_oversampling); + ClassDB::bind_method(D_METHOD("font_set_global_oversampling", "oversampling"), &TextServer::font_set_global_oversampling); + + ClassDB::bind_method(D_METHOD("get_hex_code_box_size", "size", "index"), &TextServer::get_hex_code_box_size); + ClassDB::bind_method(D_METHOD("draw_hex_code_box", "canvas", "size", "pos", "index", "color"), &TextServer::draw_hex_code_box); + /* Shaped text buffer interface */ ClassDB::bind_method(D_METHOD("create_shaped_text", "direction", "orientation"), &TextServer::create_shaped_text, DEFVAL(DIRECTION_AUTO), DEFVAL(ORIENTATION_HORIZONTAL)); @@ -420,6 +467,12 @@ void TextServer::_bind_methods() { BIND_ENUM_CONSTANT(CONTOUR_CURVE_TAG_ON); BIND_ENUM_CONSTANT(CONTOUR_CURVE_TAG_OFF_CONIC); BIND_ENUM_CONSTANT(CONTOUR_CURVE_TAG_OFF_CUBIC); + + /* Font Spacing*/ + BIND_ENUM_CONSTANT(SPACING_GLYPH); + BIND_ENUM_CONSTANT(SPACING_SPACE); + BIND_ENUM_CONSTANT(SPACING_TOP); + BIND_ENUM_CONSTANT(SPACING_BOTTOM); } Vector3 TextServer::hex_code_box_font_size[2] = { Vector3(5, 5, 1), Vector3(10, 10, 2) }; @@ -514,8 +567,8 @@ void TextServer::finish_hex_code_box_fonts() { Vector2 TextServer::get_hex_code_box_size(int p_size, char32_t p_index) const { int fnt = (p_size < 20) ? 0 : 1; - float w = ((p_index <= 0xFF) ? 1 : ((p_index <= 0xFFFF) ? 2 : 3)) * hex_code_box_font_size[fnt].x; - float h = 2 * hex_code_box_font_size[fnt].y; + real_t w = ((p_index <= 0xFF) ? 1 : ((p_index <= 0xFFFF) ? 2 : 3)) * hex_code_box_font_size[fnt].x; + real_t h = 2 * hex_code_box_font_size[fnt].y; return Vector2(w + 4, h + 3 + 2 * hex_code_box_font_size[fnt].z); } @@ -534,8 +587,8 @@ void TextServer::draw_hex_code_box(RID p_canvas, int p_size, const Vector2 &p_po Vector2 pos = p_pos; Rect2 dest = Rect2(Vector2(), Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y)); - float w = ((p_index <= 0xFF) ? 1 : ((p_index <= 0xFFFF) ? 2 : 3)) * hex_code_box_font_size[fnt].x; - float h = 2 * hex_code_box_font_size[fnt].y; + real_t w = ((p_index <= 0xFF) ? 1 : ((p_index <= 0xFFFF) ? 2 : 3)) * hex_code_box_font_size[fnt].x; + real_t h = 2 * hex_code_box_font_size[fnt].y; pos.y -= Math::floor((h + 3 + hex_code_box_font_size[fnt].z) * 0.75); @@ -575,7 +628,7 @@ void TextServer::draw_hex_code_box(RID p_canvas, int p_size, const Vector2 &p_po } } -Vector<Vector2i> TextServer::shaped_text_get_line_breaks_adv(RID p_shaped, const Vector<float> &p_width, int p_start, bool p_once, uint8_t /*TextBreakFlag*/ p_break_flags) const { +Vector<Vector2i> TextServer::shaped_text_get_line_breaks_adv(RID p_shaped, const Vector<real_t> &p_width, int p_start, bool p_once, uint8_t /*TextBreakFlag*/ p_break_flags) const { Vector<Vector2i> lines; ERR_FAIL_COND_V(p_width.is_empty(), lines); @@ -584,7 +637,7 @@ Vector<Vector2i> TextServer::shaped_text_get_line_breaks_adv(RID p_shaped, const const Vector<Glyph> &logical = const_cast<TextServer *>(this)->shaped_text_sort_logical(p_shaped); const Vector2i &range = shaped_text_get_range(p_shaped); - float width = 0.f; + real_t width = 0.f; int line_start = MAX(p_start, range.x); int last_safe_break = -1; int chunk = 0; @@ -646,14 +699,14 @@ Vector<Vector2i> TextServer::shaped_text_get_line_breaks_adv(RID p_shaped, const return lines; } -Vector<Vector2i> TextServer::shaped_text_get_line_breaks(RID p_shaped, float p_width, int p_start, uint8_t /*TextBreakFlag*/ p_break_flags) const { +Vector<Vector2i> TextServer::shaped_text_get_line_breaks(RID p_shaped, real_t p_width, int p_start, uint8_t /*TextBreakFlag*/ p_break_flags) const { Vector<Vector2i> lines; const_cast<TextServer *>(this)->shaped_text_update_breaks(p_shaped); const Vector<Glyph> &logical = const_cast<TextServer *>(this)->shaped_text_sort_logical(p_shaped); const Vector2i &range = shaped_text_get_range(p_shaped); - float width = 0.f; + real_t width = 0.f; int line_start = MAX(p_start, range.x); int last_safe_break = -1; int word_count = 0; @@ -665,9 +718,7 @@ Vector<Vector2i> TextServer::shaped_text_get_line_breaks(RID p_shaped, float p_w continue; } if (l_gl[i].count > 0) { - //Ignore trailing spaces. - bool is_space = (l_gl[i].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE; - if ((p_width > 0) && (width + (is_space ? 0 : l_gl[i].advance) > p_width) && (last_safe_break >= 0)) { + if ((p_width > 0) && (width + l_gl[i].advance * l_gl[i].repeat > p_width) && (last_safe_break >= 0)) { lines.push_back(Vector2i(line_start, l_gl[last_safe_break].end)); line_start = l_gl[last_safe_break].end; i = last_safe_break; @@ -698,7 +749,7 @@ Vector<Vector2i> TextServer::shaped_text_get_line_breaks(RID p_shaped, float p_w last_safe_break = i; } } - width += l_gl[i].advance; + width += l_gl[i].advance * l_gl[i].repeat; } if (l_size > 0) { @@ -749,11 +800,11 @@ void TextServer::shaped_text_get_carets(RID p_shaped, int p_position, Rect2 &p_l const Vector<TextServer::Glyph> visual = shaped_text_get_glyphs(p_shaped); TextServer::Orientation orientation = shaped_text_get_orientation(p_shaped); const Vector2 &range = shaped_text_get_range(p_shaped); - float ascent = shaped_text_get_ascent(p_shaped); - float descent = shaped_text_get_descent(p_shaped); - float height = (ascent + descent) / 2; + real_t ascent = shaped_text_get_ascent(p_shaped); + real_t descent = shaped_text_get_descent(p_shaped); + real_t height = (ascent + descent) / 2; - float off = 0.0f; + real_t off = 0.0f; p_leading_dir = DIRECTION_AUTO; p_trailing_dir = DIRECTION_AUTO; @@ -859,11 +910,11 @@ void TextServer::shaped_text_get_carets(RID p_shaped, int p_position, Rect2 &p_l } // Caret inside grapheme (middle). if (p_position > glyphs[i].start && p_position < glyphs[i].end && (glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL) { - float advance = 0.f; + real_t advance = 0.f; for (int j = 0; j < glyphs[i].count; j++) { advance += glyphs[i + j].advance * glyphs[i + j].repeat; } - float char_adv = advance / (float)(glyphs[i].end - glyphs[i].start); + real_t char_adv = advance / (real_t)(glyphs[i].end - glyphs[i].start); Rect2 cr; if (orientation == ORIENTATION_HORIZONTAL) { cr.size.x = 1.0f; @@ -942,14 +993,14 @@ Vector<Vector2> TextServer::shaped_text_get_selection(RID p_shaped, int p_start, int v_size = visual.size(); const Glyph *glyphs = visual.ptr(); - float off = 0.0f; + real_t off = 0.0f; for (int i = 0; i < v_size; i++) { for (int k = 0; k < glyphs[i].repeat; k++) { if ((glyphs[i].count > 0) && ((glyphs[i].index != 0) || ((glyphs[i].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE))) { if (glyphs[i].start < end && glyphs[i].end > start) { // Grapheme fully in selection range. if (glyphs[i].start >= start && glyphs[i].end <= end) { - float advance = 0.f; + real_t advance = 0.f; for (int j = 0; j < glyphs[i].count; j++) { advance += glyphs[i + j].advance; } @@ -957,11 +1008,11 @@ Vector<Vector2> TextServer::shaped_text_get_selection(RID p_shaped, int p_start, } // Only start of grapheme is in selection range. if (glyphs[i].start >= start && glyphs[i].end > end) { - float advance = 0.f; + real_t advance = 0.f; for (int j = 0; j < glyphs[i].count; j++) { advance += glyphs[i + j].advance; } - float char_adv = advance / (float)(glyphs[i].end - glyphs[i].start); + real_t char_adv = advance / (real_t)(glyphs[i].end - glyphs[i].start); if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) { ranges.push_back(Vector2(off + char_adv * (glyphs[i].end - end), off + advance)); } else { @@ -970,11 +1021,11 @@ Vector<Vector2> TextServer::shaped_text_get_selection(RID p_shaped, int p_start, } // Only end of grapheme is in selection range. if (glyphs[i].start < start && glyphs[i].end <= end) { - float advance = 0.f; + real_t advance = 0.f; for (int j = 0; j < glyphs[i].count; j++) { advance += glyphs[i + j].advance; } - float char_adv = advance / (float)(glyphs[i].end - glyphs[i].start); + real_t char_adv = advance / (real_t)(glyphs[i].end - glyphs[i].start); if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) { ranges.push_back(Vector2(off, off + char_adv * (start - glyphs[i].start))); } else { @@ -983,11 +1034,11 @@ Vector<Vector2> TextServer::shaped_text_get_selection(RID p_shaped, int p_start, } // Selection range is within grapheme. if (glyphs[i].start < start && glyphs[i].end > end) { - float advance = 0.f; + real_t advance = 0.f; for (int j = 0; j < glyphs[i].count; j++) { advance += glyphs[i + j].advance; } - float char_adv = advance / (float)(glyphs[i].end - glyphs[i].start); + real_t char_adv = advance / (real_t)(glyphs[i].end - glyphs[i].start); if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) { ranges.push_back(Vector2(off + char_adv * (glyphs[i].end - end), off + char_adv * (glyphs[i].end - start))); } else { @@ -1022,11 +1073,11 @@ Vector<Vector2> TextServer::shaped_text_get_selection(RID p_shaped, int p_start, return ranges; } -int TextServer::shaped_text_hit_test_grapheme(RID p_shaped, float p_coords) const { +int TextServer::shaped_text_hit_test_grapheme(RID p_shaped, real_t p_coords) const { const Vector<TextServer::Glyph> visual = shaped_text_get_glyphs(p_shaped); // Exact grapheme hit test, return -1 if missed. - float off = 0.0f; + real_t off = 0.0f; int v_size = visual.size(); const Glyph *glyphs = visual.ptr(); @@ -1042,7 +1093,7 @@ int TextServer::shaped_text_hit_test_grapheme(RID p_shaped, float p_coords) cons return -1; } -int TextServer::shaped_text_hit_test_position(RID p_shaped, float p_coords) const { +int TextServer::shaped_text_hit_test_position(RID p_shaped, real_t p_coords) const { const Vector<TextServer::Glyph> visual = shaped_text_get_glyphs(p_shaped); int v_size = visual.size(); @@ -1076,10 +1127,10 @@ int TextServer::shaped_text_hit_test_position(RID p_shaped, float p_coords) cons } } - float off = 0.0f; + real_t off = 0.0f; for (int i = 0; i < v_size; i++) { if (glyphs[i].count > 0) { - float advance = 0.f; + real_t advance = 0.f; for (int j = 0; j < glyphs[i].count; j++) { advance += glyphs[i + j].advance * glyphs[i + j].repeat; } @@ -1137,7 +1188,7 @@ int TextServer::shaped_text_prev_grapheme_pos(RID p_shaped, int p_pos) { return p_pos; } -void TextServer::shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_pos, float p_clip_l, float p_clip_r, const Color &p_color) const { +void TextServer::shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_pos, real_t p_clip_l, real_t p_clip_r, const Color &p_color) const { const Vector<TextServer::Glyph> visual = shaped_text_get_glyphs(p_shaped); TextServer::Orientation orientation = shaped_text_get_orientation(p_shaped); bool hex_codes = shaped_text_get_preserve_control(p_shaped) || shaped_text_get_preserve_invalid(p_shaped); @@ -1230,7 +1281,7 @@ void TextServer::shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_p } } -void TextServer::shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vector2 &p_pos, float p_clip_l, float p_clip_r, int p_outline_size, const Color &p_color) const { +void TextServer::shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vector2 &p_pos, real_t p_clip_l, real_t p_clip_r, int p_outline_size, const Color &p_color) const { const Vector<TextServer::Glyph> visual = shaped_text_get_glyphs(p_shaped); TextServer::Orientation orientation = shaped_text_get_orientation(p_shaped); @@ -1318,11 +1369,7 @@ void TextServer::shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vect } } -RID TextServer::_create_font_memory(const PackedByteArray &p_data, const String &p_type, int p_base_size) { - return create_font_memory(p_data.ptr(), p_data.size(), p_type, p_base_size); -} - -Dictionary TextServer::_font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index) const { +Dictionary TextServer::_font_get_glyph_contours(RID p_font, int p_size, int32_t p_index) const { Vector<Vector3> points; Vector<int32_t> contours; bool orientation; @@ -1380,7 +1427,7 @@ Array TextServer::_shaped_text_get_line_breaks_adv(RID p_shaped, const PackedFlo return ret; } -Array TextServer::_shaped_text_get_line_breaks(RID p_shaped, float p_width, int p_start, uint8_t p_break_flags) const { +Array TextServer::_shaped_text_get_line_breaks(RID p_shaped, real_t p_width, int p_start, uint8_t p_break_flags) const { Array ret; Vector<Vector2i> lines = shaped_text_get_line_breaks(p_shaped, p_width, p_start, p_break_flags); diff --git a/servers/text_server.h b/servers/text_server.h index 26993838a3..62e02e3c97 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -116,6 +116,13 @@ public: CONTOUR_CURVE_TAG_OFF_CUBIC = 0x02 }; + enum SpacingType { + SPACING_GLYPH, + SPACING_SPACE, + SPACING_TOP, + SPACING_BOTTOM, + }; + struct Glyph { int start = -1; // Start offset in the source string. int end = -1; // End offset in the source string. @@ -124,13 +131,13 @@ public: uint8_t repeat = 1; // Draw multiple times in the row. uint16_t flags = 0; // Grapheme flags (valid, rtl, virtual), set in the first glyph only. - float x_off = 0.f; // Offset from the origin of the glyph on baseline. - float y_off = 0.f; - float advance = 0.f; // Advance to the next glyph along baseline(x for horizontal layout, y for vertical). + real_t x_off = 0.f; // Offset from the origin of the glyph on baseline. + real_t y_off = 0.f; + real_t advance = 0.f; // Advance to the next glyph along baseline(x for horizontal layout, y for vertical). RID font_rid; // Font resource. int font_size = 0; // Font size; - uint32_t index = 0; // Glyph index (font specific) or UTF-32 codepoint (for the invalid glyphs). + int32_t index = 0; // Glyph index (font specific) or UTF-32 codepoint (for the invalid glyphs). bool operator==(const Glyph &p_a) const; bool operator!=(const Glyph &p_a) const; @@ -163,6 +170,8 @@ public: }; struct ShapedTextData { + Mutex mutex; + /* Source data */ RID parent; // Substring parent ShapedTextData. @@ -205,13 +214,13 @@ public: bool preserve_invalid = true; // Draw hex code box instead of missing characters. bool preserve_control = false; // Draw control characters. - float ascent = 0.f; // Ascent for horizontal layout, 1/2 of width for vertical. - float descent = 0.f; // Descent for horizontal layout, 1/2 of width for vertical. - float width = 0.f; // Width for horizontal layout, height for vertical. - float width_trimmed = 0.f; + real_t ascent = 0.f; // Ascent for horizontal layout, 1/2 of width for vertical. + real_t descent = 0.f; // Descent for horizontal layout, 1/2 of width for vertical. + real_t width = 0.f; // Width for horizontal layout, height for vertical. + real_t width_trimmed = 0.f; - float upos = 0.f; - float uthk = 0.f; + real_t upos = 0.f; + real_t uthk = 0.f; TrimData overrun_trim_data; bool fit_width_minimum_reached = false; @@ -245,85 +254,134 @@ public: virtual bool is_locale_right_to_left(const String &p_locale) = 0; - virtual int32_t name_to_tag(const String &p_name) { return 0; }; - virtual String tag_to_name(int32_t p_tag) { return ""; }; + virtual int32_t name_to_tag(const String &p_name) const { return 0; }; + virtual String tag_to_name(int32_t p_tag) const { return ""; }; /* Font interface */ - virtual RID create_font_system(const String &p_name, int p_base_size = 16) = 0; - virtual RID create_font_resource(const String &p_filename, int p_base_size = 16) = 0; - virtual RID create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size = 16) = 0; - virtual RID create_font_bitmap(float p_height, float p_ascent, int p_base_size = 16) = 0; + virtual RID create_font() = 0; + + virtual void font_set_data(RID p_font_rid, const PackedByteArray &p_data) = 0; + virtual void font_set_data_ptr(RID p_font_rid, const uint8_t *p_data_ptr, size_t p_data_size) = 0; + + virtual void font_set_antialiased(RID p_font_rid, bool p_antialiased) = 0; + virtual bool font_is_antialiased(RID p_font_rid) const = 0; + + virtual void font_set_multichannel_signed_distance_field(RID p_font_rid, bool p_msdf) = 0; + virtual bool font_is_multichannel_signed_distance_field(RID p_font_rid) const = 0; + + virtual void font_set_msdf_pixel_range(RID p_font_rid, int p_msdf_pixel_range) = 0; + virtual int font_get_msdf_pixel_range(RID p_font_rid) const = 0; + + virtual void font_set_msdf_size(RID p_font_rid, int p_msdf_size) = 0; + virtual int font_get_msdf_size(RID p_font_rid) const = 0; + + virtual void font_set_fixed_size(RID p_font_rid, int p_fixed_size) = 0; + virtual int font_get_fixed_size(RID p_font_rid) const = 0; + + virtual void font_set_force_autohinter(RID p_font_rid, bool p_force_autohinter) = 0; + virtual bool font_is_force_autohinter(RID p_font_rid) const = 0; + + virtual void font_set_hinting(RID p_font_rid, TextServer::Hinting p_hinting) = 0; + virtual TextServer::Hinting font_get_hinting(RID p_font_rid) const = 0; - virtual void font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) = 0; - virtual void font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) = 0; - virtual void font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) = 0; + virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) = 0; + virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const = 0; - virtual float font_get_height(RID p_font, int p_size) const = 0; - virtual float font_get_ascent(RID p_font, int p_size) const = 0; - virtual float font_get_descent(RID p_font, int p_size) const = 0; + virtual void font_set_oversampling(RID p_font_rid, real_t p_oversampling) = 0; + virtual real_t font_get_oversampling(RID p_font_rid) const = 0; - virtual int font_get_spacing_space(RID p_font) const = 0; - virtual void font_set_spacing_space(RID p_font, int p_value) = 0; + virtual Array font_get_size_cache_list(RID p_font_rid) const = 0; + virtual void font_clear_size_cache(RID p_font_rid) = 0; + virtual void font_remove_size_cache(RID p_font_rid, const Vector2i &p_size) = 0; - virtual int font_get_spacing_glyph(RID p_font) const = 0; - virtual void font_set_spacing_glyph(RID p_font, int p_value) = 0; + virtual void font_set_ascent(RID p_font_rid, int p_size, real_t p_ascent) = 0; + virtual real_t font_get_ascent(RID p_font_rid, int p_size) const = 0; - virtual float font_get_underline_position(RID p_font, int p_size) const = 0; - virtual float font_get_underline_thickness(RID p_font, int p_size) const = 0; + virtual void font_set_descent(RID p_font_rid, int p_size, real_t p_descent) = 0; + virtual real_t font_get_descent(RID p_font_rid, int p_size) const = 0; - virtual void font_set_antialiased(RID p_font, bool p_antialiased) = 0; - virtual bool font_get_antialiased(RID p_font) const = 0; + virtual void font_set_underline_position(RID p_font_rid, int p_size, real_t p_underline_position) = 0; + virtual real_t font_get_underline_position(RID p_font_rid, int p_size) const = 0; - virtual Dictionary font_get_feature_list(RID p_font) const { return Dictionary(); }; - virtual Dictionary font_get_variation_list(RID p_font) const { return Dictionary(); }; + virtual void font_set_underline_thickness(RID p_font_rid, int p_size, real_t p_underline_thickness) = 0; + virtual real_t font_get_underline_thickness(RID p_font_rid, int p_size) const = 0; - virtual void font_set_variation(RID p_font, const String &p_name, double p_value){}; - virtual double font_get_variation(RID p_font, const String &p_name) const { return 0; }; + virtual void font_set_scale(RID p_font_rid, int p_size, real_t p_scale) = 0; + virtual real_t font_get_scale(RID p_font_rid, int p_size) const = 0; - virtual void font_set_distance_field_hint(RID p_font, bool p_distance_field) = 0; - virtual bool font_get_distance_field_hint(RID p_font) const = 0; + virtual void font_set_spacing(RID p_font_rid, int p_size, SpacingType p_spacing, int p_value) = 0; + virtual int font_get_spacing(RID p_font_rid, int p_size, SpacingType p_spacing) const = 0; - virtual void font_set_hinting(RID p_font, Hinting p_hinting) = 0; - virtual Hinting font_get_hinting(RID p_font) const = 0; + virtual int font_get_texture_count(RID p_font_rid, const Vector2i &p_size) const = 0; + virtual void font_clear_textures(RID p_font_rid, const Vector2i &p_size) = 0; + virtual void font_remove_texture(RID p_font_rid, const Vector2i &p_size, int p_texture_index) = 0; - virtual void font_set_force_autohinter(RID p_font, bool p_enabeld) = 0; - virtual bool font_get_force_autohinter(RID p_font) const = 0; + virtual void font_set_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) = 0; + virtual Ref<Image> font_get_texture_image(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const = 0; - virtual bool font_has_char(RID p_font, char32_t p_char) const = 0; - virtual String font_get_supported_chars(RID p_font) const = 0; + virtual void font_set_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) = 0; + virtual PackedInt32Array font_get_texture_offsets(RID p_font_rid, const Vector2i &p_size, int p_texture_index) const = 0; - virtual bool font_has_outline(RID p_font) const = 0; - virtual float font_get_base_size(RID p_font) const = 0; + virtual Array font_get_glyph_list(RID p_font_rid, const Vector2i &p_size) const = 0; + virtual void font_clear_glyphs(RID p_font_rid, const Vector2i &p_size) = 0; + virtual void font_remove_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) = 0; - virtual bool font_is_language_supported(RID p_font, const String &p_language) const = 0; - virtual void font_set_language_support_override(RID p_font, const String &p_language, bool p_supported) = 0; - virtual bool font_get_language_support_override(RID p_font, const String &p_language) = 0; - virtual void font_remove_language_support_override(RID p_font, const String &p_language) = 0; - virtual Vector<String> font_get_language_support_overrides(RID p_font) = 0; + virtual Vector2 font_get_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph) const = 0; + virtual void font_set_glyph_advance(RID p_font_rid, int p_size, int32_t p_glyph, const Vector2 &p_advance) = 0; - virtual bool font_is_script_supported(RID p_font, const String &p_script) const = 0; - virtual void font_set_script_support_override(RID p_font, const String &p_script, bool p_supported) = 0; - virtual bool font_get_script_support_override(RID p_font, const String &p_script) = 0; - virtual void font_remove_script_support_override(RID p_font, const String &p_script) = 0; - virtual Vector<String> font_get_script_support_overrides(RID p_font) = 0; + virtual Vector2 font_get_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const = 0; + virtual void font_set_glyph_offset(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) = 0; - virtual uint32_t font_get_glyph_index(RID p_font, char32_t p_char, char32_t p_variation_selector = 0x0000) const = 0; - virtual Vector2 font_get_glyph_advance(RID p_font, uint32_t p_index, int p_size) const = 0; - virtual Vector2 font_get_glyph_kerning(RID p_font, uint32_t p_index_a, uint32_t p_index_b, int p_size) const = 0; + virtual Vector2 font_get_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const = 0; + virtual void font_set_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) = 0; - virtual Vector2 font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const = 0; - virtual Vector2 font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const = 0; + virtual Rect2 font_get_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const = 0; + virtual void font_set_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) = 0; - virtual bool font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const = 0; + virtual int font_get_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const = 0; + virtual void font_set_glyph_texture_idx(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) = 0; - virtual float font_get_oversampling() const = 0; - virtual void font_set_oversampling(float p_oversampling) = 0; + virtual bool font_get_glyph_contours(RID p_font, int p_size, int32_t p_index, Vector<Vector3> &r_points, Vector<int32_t> &r_contours, bool &r_orientation) const = 0; + + virtual Array font_get_kerning_list(RID p_font_rid, int p_size) const = 0; + virtual void font_clear_kerning_map(RID p_font_rid, int p_size) = 0; + virtual void font_remove_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) = 0; + + virtual void font_set_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) = 0; + virtual Vector2 font_get_kerning(RID p_font_rid, int p_size, const Vector2i &p_glyph_pair) const = 0; + + virtual int32_t font_get_glyph_index(RID p_font_rid, int p_size, char32_t p_char, char32_t p_variation_selector) const = 0; + + virtual bool font_has_char(RID p_font_rid, char32_t p_char) const = 0; + virtual String font_get_supported_chars(RID p_font_rid) const = 0; + + virtual void font_render_range(RID p_font, const Vector2i &p_size, char32_t p_start, char32_t p_end) = 0; + virtual void font_render_glyph(RID p_font_rid, const Vector2i &p_size, int32_t p_index) = 0; + + virtual void font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color = Color(1, 1, 1)) const = 0; + virtual void font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color = Color(1, 1, 1)) const = 0; + + virtual bool font_is_language_supported(RID p_font_rid, const String &p_language) const = 0; + virtual void font_set_language_support_override(RID p_font_rid, const String &p_language, bool p_supported) = 0; + virtual bool font_get_language_support_override(RID p_font_rid, const String &p_language) = 0; + virtual void font_remove_language_support_override(RID p_font_rid, const String &p_language) = 0; + virtual Vector<String> font_get_language_support_overrides(RID p_font_rid) = 0; + + virtual bool font_is_script_supported(RID p_font_rid, const String &p_script) const = 0; + virtual void font_set_script_support_override(RID p_font_rid, const String &p_script, bool p_supported) = 0; + virtual bool font_get_script_support_override(RID p_font_rid, const String &p_script) = 0; + virtual void font_remove_script_support_override(RID p_font_rid, const String &p_script) = 0; + virtual Vector<String> font_get_script_support_overrides(RID p_font_rid) = 0; + + virtual Dictionary font_supported_feature_list(RID p_font_rid) const = 0; + virtual Dictionary font_supported_variation_list(RID p_font_rid) const = 0; + + virtual real_t font_get_global_oversampling() const = 0; + virtual void font_set_global_oversampling(real_t p_oversampling) = 0; Vector2 get_hex_code_box_size(int p_size, char32_t p_index) const; void draw_hex_code_box(RID p_canvas, int p_size, const Vector2 &p_pos, char32_t p_index, const Color &p_color) const; - virtual Vector<String> get_system_fonts() const = 0; - /* Shaped text buffer interface */ virtual RID create_shaped_text(Direction p_direction = DIRECTION_AUTO, Orientation p_orientation = ORIENTATION_HORIZONTAL) = 0; @@ -351,8 +409,8 @@ public: virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const = 0; // Copy shaped substring (e.g. line break) without reshaping, but correctly reordered, preservers range. virtual RID shaped_text_get_parent(RID p_shaped) const = 0; - virtual float shaped_text_fit_to_width(RID p_shaped, float p_width, uint8_t /*JustificationFlag*/ p_jst_flags = JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA) = 0; - virtual float shaped_text_tab_align(RID p_shaped, const Vector<float> &p_tab_stops) = 0; + virtual real_t shaped_text_fit_to_width(RID p_shaped, real_t p_width, uint8_t /*JustificationFlag*/ p_jst_flags = JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA) = 0; + virtual real_t shaped_text_tab_align(RID p_shaped, const Vector<real_t> &p_tab_stops) = 0; virtual bool shaped_text_shape(RID p_shaped) = 0; virtual bool shaped_text_update_breaks(RID p_shaped) = 0; @@ -366,36 +424,36 @@ public: virtual Vector<Glyph> shaped_text_sort_logical(RID p_shaped) = 0; - virtual Vector<Vector2i> shaped_text_get_line_breaks_adv(RID p_shaped, const Vector<float> &p_width, int p_start = 0, bool p_once = true, uint8_t /*TextBreakFlag*/ p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const; - virtual Vector<Vector2i> shaped_text_get_line_breaks(RID p_shaped, float p_width, int p_start = 0, uint8_t /*TextBreakFlag*/ p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const; + virtual Vector<Vector2i> shaped_text_get_line_breaks_adv(RID p_shaped, const Vector<real_t> &p_width, int p_start = 0, bool p_once = true, uint8_t /*TextBreakFlag*/ p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const; + virtual Vector<Vector2i> shaped_text_get_line_breaks(RID p_shaped, real_t p_width, int p_start = 0, uint8_t /*TextBreakFlag*/ p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const; virtual Vector<Vector2i> shaped_text_get_word_breaks(RID p_shaped, int p_grapheme_flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_PUNCTUATION) const; virtual TrimData shaped_text_get_trim_data(RID p_shaped) const; - virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_trim_flags) = 0; + virtual void shaped_text_overrun_trim_to_width(RID p_shaped, real_t p_width, uint8_t p_trim_flags) = 0; virtual Array shaped_text_get_objects(RID p_shaped) const = 0; virtual Rect2 shaped_text_get_object_rect(RID p_shaped, Variant p_key) const = 0; virtual Size2 shaped_text_get_size(RID p_shaped) const = 0; - virtual float shaped_text_get_ascent(RID p_shaped) const = 0; - virtual float shaped_text_get_descent(RID p_shaped) const = 0; - virtual float shaped_text_get_width(RID p_shaped) const = 0; - virtual float shaped_text_get_underline_position(RID p_shaped) const = 0; - virtual float shaped_text_get_underline_thickness(RID p_shaped) const = 0; + virtual real_t shaped_text_get_ascent(RID p_shaped) const = 0; + virtual real_t shaped_text_get_descent(RID p_shaped) const = 0; + virtual real_t shaped_text_get_width(RID p_shaped) const = 0; + virtual real_t shaped_text_get_underline_position(RID p_shaped) const = 0; + virtual real_t shaped_text_get_underline_thickness(RID p_shaped) const = 0; virtual Direction shaped_text_get_dominant_direciton_in_range(RID p_shaped, int p_start, int p_end) const; virtual void shaped_text_get_carets(RID p_shaped, int p_position, Rect2 &p_leading_caret, Direction &p_leading_dir, Rect2 &p_trailing_caret, Direction &p_trailing_dir) const; virtual Vector<Vector2> shaped_text_get_selection(RID p_shaped, int p_start, int p_end) const; - virtual int shaped_text_hit_test_grapheme(RID p_shaped, float p_coords) const; // Return grapheme index. - virtual int shaped_text_hit_test_position(RID p_shaped, float p_coords) const; // Return caret/selection position. + virtual int shaped_text_hit_test_grapheme(RID p_shaped, real_t p_coords) const; // Return grapheme index. + virtual int shaped_text_hit_test_position(RID p_shaped, real_t p_coords) const; // Return caret/selection position. virtual int shaped_text_next_grapheme_pos(RID p_shaped, int p_pos); virtual int shaped_text_prev_grapheme_pos(RID p_shaped, int p_pos); // The pen position is always placed on the baseline and moveing left to right. - virtual void shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_pos, float p_clip_l = -1.f, float p_clip_r = -1.f, const Color &p_color = Color(1, 1, 1)) const; - virtual void shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vector2 &p_pos, float p_clip_l = -1.f, float p_clip_r = -1.f, int p_outline_size = 1, const Color &p_color = Color(1, 1, 1)) const; + virtual void shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_pos, real_t p_clip_l = -1.f, real_t p_clip_r = -1.f, const Color &p_color = Color(1, 1, 1)) const; + virtual void shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vector2 &p_pos, real_t p_clip_l = -1.f, real_t p_clip_r = -1.f, int p_outline_size = 1, const Color &p_color = Color(1, 1, 1)) const; // Number conversion. virtual String format_number(const String &p_string, const String &p_language = "") const { return p_string; }; @@ -403,9 +461,9 @@ public: virtual String percent_sign(const String &p_language = "") const { return "%"; }; /* GDScript wrappers */ - RID _create_font_memory(const PackedByteArray &p_data, const String &p_type, int p_base_size = 16); + RID _create_font_memory(const PackedByteArray &p_data, int p_base_size = 16); - Dictionary _font_get_glyph_contours(RID p_font, int p_size, uint32_t p_index) const; + Dictionary _font_get_glyph_contours(RID p_font, int p_size, int32_t p_index) const; Array _shaped_text_get_glyphs(RID p_shaped) const; Dictionary _shaped_text_get_carets(RID p_shaped, int p_position) const; @@ -413,7 +471,7 @@ public: void _shaped_text_set_bidi_override(RID p_shaped, const Array &p_override); Array _shaped_text_get_line_breaks_adv(RID p_shaped, const PackedFloat32Array &p_width, int p_start, bool p_once, uint8_t p_break_flags) const; - Array _shaped_text_get_line_breaks(RID p_shaped, float p_width, int p_start, uint8_t p_break_flags) const; + Array _shaped_text_get_line_breaks(RID p_shaped, real_t p_width, int p_start, uint8_t p_break_flags) const; Array _shaped_text_get_word_breaks(RID p_shaped) const; Array _shaped_text_get_selection(RID p_shaped, int p_start, int p_end) const; @@ -491,5 +549,6 @@ VARIANT_ENUM_CAST(TextServer::GraphemeFlag); VARIANT_ENUM_CAST(TextServer::Hinting); VARIANT_ENUM_CAST(TextServer::Feature); VARIANT_ENUM_CAST(TextServer::ContourPointTag); +VARIANT_ENUM_CAST(TextServer::SpacingType); #endif // TEXT_SERVER_H diff --git a/servers/xr/xr_interface.cpp b/servers/xr/xr_interface.cpp index 09e8e12f8b..bf54158905 100644 --- a/servers/xr/xr_interface.cpp +++ b/servers/xr/xr_interface.cpp @@ -29,28 +29,26 @@ /*************************************************************************/ #include "xr_interface.h" -#include "servers/rendering/renderer_compositor.h" +// #include "servers/rendering/renderer_compositor.h" void XRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_name"), &XRInterface::get_name); ClassDB::bind_method(D_METHOD("get_capabilities"), &XRInterface::get_capabilities); ClassDB::bind_method(D_METHOD("is_primary"), &XRInterface::is_primary); - ClassDB::bind_method(D_METHOD("set_is_primary", "enable"), &XRInterface::set_is_primary); + ClassDB::bind_method(D_METHOD("set_primary", "primary"), &XRInterface::set_primary); ClassDB::bind_method(D_METHOD("is_initialized"), &XRInterface::is_initialized); - ClassDB::bind_method(D_METHOD("set_is_initialized", "initialized"), &XRInterface::set_is_initialized); ClassDB::bind_method(D_METHOD("initialize"), &XRInterface::initialize); ClassDB::bind_method(D_METHOD("uninitialize"), &XRInterface::uninitialize); ClassDB::bind_method(D_METHOD("get_tracking_status"), &XRInterface::get_tracking_status); - ClassDB::bind_method(D_METHOD("get_render_targetsize"), &XRInterface::get_render_targetsize); + ClassDB::bind_method(D_METHOD("get_render_target_size"), &XRInterface::get_render_target_size); ClassDB::bind_method(D_METHOD("get_view_count"), &XRInterface::get_view_count); ADD_GROUP("Interface", "interface_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interface_is_primary"), "set_is_primary", "is_primary"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interface_is_initialized"), "set_is_initialized", "is_initialized"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interface_is_primary"), "set_primary", "is_primary"); // we don't have any properties specific to VR yet.... @@ -77,70 +75,48 @@ void XRInterface::_bind_methods() { BIND_ENUM_CONSTANT(XR_INSUFFICIENT_FEATURES); BIND_ENUM_CONSTANT(XR_UNKNOWN_TRACKING); BIND_ENUM_CONSTANT(XR_NOT_TRACKING); -}; - -StringName XRInterface::get_name() const { - return "Unknown"; -}; +} bool XRInterface::is_primary() { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL_V(xr_server, false); return xr_server->get_primary_interface() == this; -}; +} -void XRInterface::set_is_primary(bool p_is_primary) { +void XRInterface::set_primary(bool p_primary) { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL(xr_server); - if (p_is_primary) { + if (p_primary) { ERR_FAIL_COND(!is_initialized()); xr_server->set_primary_interface(this); - } else { - xr_server->clear_primary_interface_if(this); - }; -}; - -void XRInterface::set_is_initialized(bool p_initialized) { - if (p_initialized) { - if (!is_initialized()) { - initialize(); - }; - } else { - if (is_initialized()) { - uninitialize(); - }; - }; -}; - -XRInterface::Tracking_status XRInterface::get_tracking_status() const { - return tracking_state; -}; - -XRInterface::XRInterface() { - tracking_state = XR_UNKNOWN_TRACKING; -}; + } else if (xr_server->get_primary_interface() == this) { + xr_server->set_primary_interface(nullptr); + } +} -XRInterface::~XRInterface() {} +XRInterface::XRInterface() {} -// optional render to external texture which enhances performance on those platforms that require us to submit our end result into special textures. -unsigned int XRInterface::get_external_texture_for_eye(XRInterface::Eyes p_eye) { - return 0; -}; +XRInterface::~XRInterface() {} /** these will only be implemented on AR interfaces, so we want dummies for VR **/ bool XRInterface::get_anchor_detection_is_enabled() const { return false; -}; +} void XRInterface::set_anchor_detection_is_enabled(bool p_enable) { - // don't do anything here, this needs to be implemented on AR interface to enable/disable things like plane detection etc. } int XRInterface::get_camera_feed_id() { - // don't do anything here, this needs to be implemented on AR interface to enable/disable things like plane detection etc. - return 0; -}; +} + +/** these are optional, so we want dummies **/ +XRInterface::TrackingStatus XRInterface::get_tracking_status() const { + return XR_UNKNOWN_TRACKING; +} + +void XRInterface::notification(int p_what) { +} diff --git a/servers/xr/xr_interface.h b/servers/xr/xr_interface.h index 6b248c9554..4f5d4bad10 100644 --- a/servers/xr/xr_interface.h +++ b/servers/xr/xr_interface.h @@ -68,7 +68,7 @@ public: EYE_RIGHT }; - enum Tracking_status { /* tracking status currently based on AR but we can start doing more with this for VR as well */ + enum TrackingStatus { /* tracking status currently based on AR but we can start doing more with this for VR as well */ XR_NORMAL_TRACKING, XR_EXCESSIVE_MOTION, XR_INSUFFICIENT_FEATURES, @@ -76,26 +76,25 @@ public: XR_NOT_TRACKING }; +private: protected: _THREAD_SAFE_CLASS_ - Tracking_status tracking_state; static void _bind_methods(); public: /** general interface information **/ - virtual StringName get_name() const; - virtual int get_capabilities() const = 0; + virtual StringName get_name() const = 0; + virtual uint32_t get_capabilities() const = 0; bool is_primary(); - void set_is_primary(bool p_is_primary); + void set_primary(bool p_is_primary); virtual bool is_initialized() const = 0; /* returns true if we've initialized this interface */ - void set_is_initialized(bool p_initialized); /* helper function, will call initialize or uninitialize */ virtual bool initialize() = 0; /* initialize this interface, if this has an HMD it becomes the primary interface */ virtual void uninitialize() = 0; /* deinitialize this interface */ - Tracking_status get_tracking_status() const; /* get the status of our current tracking */ + virtual TrackingStatus get_tracking_status() const; /* get the status of our current tracking */ /** specific to VR **/ // nothing yet @@ -107,27 +106,25 @@ public: /** rendering and internal **/ - virtual Size2 get_render_targetsize() = 0; /* returns the recommended render target size per eye for this device */ + virtual Size2 get_render_target_size() = 0; /* returns the recommended render target size per eye for this device */ virtual uint32_t get_view_count() = 0; /* returns the view count we need (1 is monoscopic, 2 is stereoscopic but can be more) */ virtual Transform3D get_camera_transform() = 0; /* returns the position of our camera for updating our camera node. For monoscopic this is equal to the views transform, for stereoscopic this should be an average */ virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) = 0; /* get each views transform */ virtual CameraMatrix get_projection_for_view(uint32_t p_view, real_t p_aspect, real_t p_z_near, real_t p_z_far) = 0; /* get each view projection matrix */ + // note, external color/depth/vrs texture support will be added here soon. + virtual Vector<BlitToScreen> commit_views(RID p_render_target, const Rect2 &p_screen_rect) = 0; /* commit rendered views to the XR interface */ virtual void process() = 0; - virtual void notification(int p_what) = 0; + virtual void notification(int p_what); XRInterface(); ~XRInterface(); - - // deprecated - virtual unsigned int get_external_texture_for_eye(XRInterface::Eyes p_eye); /* if applicable return external texture to render to */ - virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) = 0; /* output the left or right eye */ }; VARIANT_ENUM_CAST(XRInterface::Capabilities); VARIANT_ENUM_CAST(XRInterface::Eyes); -VARIANT_ENUM_CAST(XRInterface::Tracking_status); +VARIANT_ENUM_CAST(XRInterface::TrackingStatus); -#endif +#endif // !XR_INTERFACE_H diff --git a/servers/xr/xr_interface_extension.cpp b/servers/xr/xr_interface_extension.cpp new file mode 100644 index 0000000000..6340485bde --- /dev/null +++ b/servers/xr/xr_interface_extension.cpp @@ -0,0 +1,262 @@ +/*************************************************************************/ +/* xr_interface_extension.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 "xr_interface_extension.h" +#include "servers/rendering/renderer_storage.h" +#include "servers/rendering/rendering_server_globals.h" + +void XRInterfaceExtension::_bind_methods() { + GDVIRTUAL_BIND(_get_name); + GDVIRTUAL_BIND(_get_capabilities); + + GDVIRTUAL_BIND(_is_initialized); + GDVIRTUAL_BIND(_initialize); + GDVIRTUAL_BIND(_uninitialize); + + GDVIRTUAL_BIND(_get_tracking_status); + + GDVIRTUAL_BIND(_get_render_target_size); + GDVIRTUAL_BIND(_get_view_count); + GDVIRTUAL_BIND(_get_camera_transform); + GDVIRTUAL_BIND(_get_transform_for_view, "view", "cam_transform"); + GDVIRTUAL_BIND(_get_projection_for_view, "view", "aspect", "z_near", "z_far"); + + GDVIRTUAL_BIND(_commit_views, "render_target", "screen_rect"); + + GDVIRTUAL_BIND(_process); + GDVIRTUAL_BIND(_notification, "what"); + + // we don't have any properties specific to VR yet.... + + // but we do have properties specific to AR.... + GDVIRTUAL_BIND(_get_anchor_detection_is_enabled); + GDVIRTUAL_BIND(_set_anchor_detection_is_enabled, "enabled"); + GDVIRTUAL_BIND(_get_camera_feed_id); + + // helper methods + ClassDB::bind_method(D_METHOD("add_blit", "render_target", "src_rect", "dst_rect", "use_layer", "layer", "apply_lens_distortion", "eye_center", "k1", "k2", "upscale", "aspect_ratio"), &XRInterfaceExtension::add_blit); + ClassDB::bind_method(D_METHOD("get_render_target_texture", "render_target"), &XRInterfaceExtension::get_render_target_texture); + // ClassDB::bind_method(D_METHOD("get_render_target_depth", "render_target"), &XRInterfaceExtension::get_render_target_depth); +} + +StringName XRInterfaceExtension::get_name() const { + StringName name; + + if (GDVIRTUAL_CALL(_get_name, name)) { + return name; + } + + return "Unknown"; +} + +uint32_t XRInterfaceExtension::get_capabilities() const { + uint32_t capabilities; + + if (GDVIRTUAL_CALL(_get_capabilities, capabilities)) { + return capabilities; + } + + return 0; +} + +bool XRInterfaceExtension::is_initialized() const { + bool initialised = false; + + if (GDVIRTUAL_CALL(_is_initialized, initialised)) { + return initialised; + } + + return false; +} + +bool XRInterfaceExtension::initialize() { + bool initialised = false; + + if (GDVIRTUAL_CALL(_initialize, initialised)) { + return initialised; + } + + return false; +} + +void XRInterfaceExtension::uninitialize() { + GDVIRTUAL_CALL(_uninitialize); +} + +XRInterface::TrackingStatus XRInterfaceExtension::get_tracking_status() const { + uint32_t status; + + if (GDVIRTUAL_CALL(_get_tracking_status, status)) { + return TrackingStatus(status); + } + + return XR_UNKNOWN_TRACKING; +} + +/** these will only be implemented on AR interfaces, so we want dummies for VR **/ +bool XRInterfaceExtension::get_anchor_detection_is_enabled() const { + bool enabled; + + if (GDVIRTUAL_CALL(_get_anchor_detection_is_enabled, enabled)) { + return enabled; + } + + return false; +} + +void XRInterfaceExtension::set_anchor_detection_is_enabled(bool p_enable) { + // don't do anything here, this needs to be implemented on AR interface to enable/disable things like plane detection etc. + GDVIRTUAL_CALL(_set_anchor_detection_is_enabled, p_enable); +} + +int XRInterfaceExtension::get_camera_feed_id() { + int feed_id; + + if (GDVIRTUAL_CALL(_get_camera_feed_id, feed_id)) { + return feed_id; + } + + return 0; +} + +Size2 XRInterfaceExtension::get_render_target_size() { + Size2 size; + + if (GDVIRTUAL_CALL(_get_render_target_size, size)) { + return size; + } + + return Size2(0, 0); +} + +uint32_t XRInterfaceExtension::get_view_count() { + uint32_t view_count; + + if (GDVIRTUAL_CALL(_get_view_count, view_count)) { + return view_count; + } + + return 1; +} + +Transform3D XRInterfaceExtension::get_camera_transform() { + Transform3D transform; + + if (GDVIRTUAL_CALL(_get_camera_transform, transform)) { + return transform; + } + + return Transform3D(); +} + +Transform3D XRInterfaceExtension::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) { + Transform3D transform; + + if (GDVIRTUAL_CALL(_get_transform_for_view, p_view, p_cam_transform, transform)) { + return transform; + } + + return Transform3D(); +} + +CameraMatrix XRInterfaceExtension::get_projection_for_view(uint32_t p_view, real_t p_aspect, real_t p_z_near, real_t p_z_far) { + CameraMatrix cm; + PackedFloat64Array arr; + + if (GDVIRTUAL_CALL(_get_projection_for_view, p_view, p_aspect, p_z_near, p_z_far, arr)) { + ERR_FAIL_COND_V_MSG(arr.size() != 16, CameraMatrix(), "Projection matrix must contain 16 floats"); + real_t *m = (real_t *)cm.matrix; + for (int i = 0; i < 16; i++) { + m[i] = arr[i]; + } + return cm; + } + + return CameraMatrix(); +} + +void XRInterfaceExtension::add_blit(RID p_render_target, Rect2 p_src_rect, Rect2i p_dst_rect, bool p_use_layer, uint32_t p_layer, bool p_apply_lens_distortion, Vector2 p_eye_center, float p_k1, float p_k2, float p_upscale, float p_aspect_ratio) { + BlitToScreen blit; + + ERR_FAIL_COND_MSG(!can_add_blits, "add_blit can only be called from an XR plugin from within _commit_views!"); + + blit.render_target = p_render_target; + blit.src_rect = p_src_rect; + blit.dst_rect = p_dst_rect; + + blit.multi_view.use_layer = p_use_layer; + blit.multi_view.layer = p_layer; + + blit.lens_distortion.apply = p_apply_lens_distortion; + blit.lens_distortion.eye_center = p_eye_center; + blit.lens_distortion.k1 = p_k1; + blit.lens_distortion.k2 = p_k2; + blit.lens_distortion.upscale = p_upscale; + blit.lens_distortion.aspect_ratio = p_aspect_ratio; + + blits.push_back(blit); +} + +Vector<BlitToScreen> XRInterfaceExtension::commit_views(RID p_render_target, const Rect2 &p_screen_rect) { + // This is just so our XR plugin can add blits... + blits.clear(); + can_add_blits = true; + + if (GDVIRTUAL_CALL(_commit_views, p_render_target, p_screen_rect)) { + return blits; + } + + can_add_blits = false; + return blits; +} + +void XRInterfaceExtension::process() { + GDVIRTUAL_CALL(_process); +} + +void XRInterfaceExtension::notification(int p_what) { + GDVIRTUAL_CALL(_notification, p_what); +} + +RID XRInterfaceExtension::get_render_target_texture(RID p_render_target) { + RendererStorage *storage = RSG::storage; + ERR_FAIL_NULL_V_MSG(storage, RID(), "Renderer storage not setup"); + + return storage->render_target_get_texture(p_render_target); +} + +/* +RID XRInterfaceExtension::get_render_target_depth(RID p_render_target) { + RendererStorage *storage = RSG::storage; + ERR_FAIL_NULL_V_MSG(storage, RID(), "Renderer storage not setup"); + + return storage->render_target_get_depth(p_render_target); +} +*/ diff --git a/modules/gdnative/xr/xr_interface_gdnative.h b/servers/xr/xr_interface_extension.h index 42e9206c1f..94914a7b3f 100644 --- a/modules/gdnative/xr/xr_interface_gdnative.h +++ b/servers/xr/xr_interface_extension.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* xr_interface_gdnative.h */ +/* xr_interface_extension.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,68 +28,82 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef XR_INTERFACE_GDNATIVE_H -#define XR_INTERFACE_GDNATIVE_H +#ifndef XR_INTERFACE_EXTENSION_H +#define XR_INTERFACE_EXTENSION_H -#include "modules/gdnative/gdnative.h" #include "servers/xr/xr_interface.h" -/** - @authors Hinsbart & Karroffel & Mux213 +class XRInterfaceExtension : public XRInterface { + GDCLASS(XRInterfaceExtension, XRInterface); - This subclass of our AR/VR interface forms a bridge to GDNative. -*/ - -class XRInterfaceGDNative : public XRInterface { - GDCLASS(XRInterfaceGDNative, XRInterface); - - void cleanup(); +public: +private: + bool can_add_blits = false; + Vector<BlitToScreen> blits; protected: - const godot_xr_interface_gdnative *interface; - void *data; + _THREAD_SAFE_CLASS_ static void _bind_methods(); public: /** general interface information **/ - XRInterfaceGDNative(); - ~XRInterfaceGDNative(); - - void set_interface(const godot_xr_interface_gdnative *p_interface); - virtual StringName get_name() const override; - virtual int get_capabilities() const override; + virtual uint32_t get_capabilities() const override; + + GDVIRTUAL0RC(StringName, _get_name); + GDVIRTUAL0RC(uint32_t, _get_capabilities); virtual bool is_initialized() const override; virtual bool initialize() override; virtual void uninitialize() override; + GDVIRTUAL0RC(bool, _is_initialized); + GDVIRTUAL0R(bool, _initialize); + GDVIRTUAL0(_uninitialize); + + virtual TrackingStatus get_tracking_status() const override; + GDVIRTUAL0RC(uint32_t, _get_tracking_status); + + /** specific to VR **/ + // nothing yet + /** specific to AR **/ virtual bool get_anchor_detection_is_enabled() const override; virtual void set_anchor_detection_is_enabled(bool p_enable) override; virtual int get_camera_feed_id() override; + GDVIRTUAL0RC(bool, _get_anchor_detection_is_enabled); + GDVIRTUAL1(_set_anchor_detection_is_enabled, bool); + GDVIRTUAL0RC(int, _get_camera_feed_id); + /** rendering and internal **/ - virtual Size2 get_render_targetsize() override; + + virtual Size2 get_render_target_size() override; virtual uint32_t get_view_count() override; virtual Transform3D get_camera_transform() override; virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; - - // we expose a Vector<float> version of this function to GDNative - Vector<float> _get_projection_for_eye(XRInterface::Eyes p_eye, real_t p_aspect, real_t p_z_near, real_t p_z_far); - - // and a CameraMatrix version to XRServer virtual CameraMatrix get_projection_for_view(uint32_t p_view, real_t p_aspect, real_t p_z_near, real_t p_z_far) override; + GDVIRTUAL0R(Size2, _get_render_target_size); + GDVIRTUAL0R(uint32_t, _get_view_count); + GDVIRTUAL0R(Transform3D, _get_camera_transform); + GDVIRTUAL2R(Transform3D, _get_transform_for_view, uint32_t, const Transform3D &); + GDVIRTUAL4R(PackedFloat64Array, _get_projection_for_view, uint32_t, real_t, real_t, real_t); + + void add_blit(RID p_render_target, Rect2 p_src_rect, Rect2i p_dst_rect, bool p_use_layer = false, uint32_t p_layer = 0, bool p_apply_lens_distortion = false, Vector2 p_eye_center = Vector2(), float p_k1 = 0.0, float p_k2 = 0.0, float p_upscale = 1.0, float p_aspect_ratio = 1.0); virtual Vector<BlitToScreen> commit_views(RID p_render_target, const Rect2 &p_screen_rect) override; + GDVIRTUAL2(_commit_views, RID, const Rect2 &); virtual void process() override; virtual void notification(int p_what) override; - // deprecated - virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) override; - virtual unsigned int get_external_texture_for_eye(XRInterface::Eyes p_eye) override; + GDVIRTUAL0(_process); + GDVIRTUAL1(_notification, int); + + /* access to some internals we need */ + RID get_render_target_texture(RID p_render_target); + // RID get_render_target_depth(RID p_render_target); }; -#endif // XR_INTERFACE_GDNATIVE_H +#endif // !XR_INTERFACE_EXTENSION_H diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp index c27656047f..c18a9f8b4e 100644 --- a/servers/xr_server.cpp +++ b/servers/xr_server.cpp @@ -49,7 +49,6 @@ void XRServer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "world_scale"), "set_world_scale", "get_world_scale"); ClassDB::bind_method(D_METHOD("add_interface", "interface"), &XRServer::add_interface); - ClassDB::bind_method(D_METHOD("clear_primary_interface_if", "interface"), &XRServer::clear_primary_interface_if); ClassDB::bind_method(D_METHOD("get_interface_count"), &XRServer::get_interface_count); ClassDB::bind_method(D_METHOD("remove_interface", "interface"), &XRServer::remove_interface); ClassDB::bind_method(D_METHOD("get_interface", "idx"), &XRServer::get_interface); @@ -316,17 +315,14 @@ Ref<XRInterface> XRServer::get_primary_interface() const { }; void XRServer::set_primary_interface(const Ref<XRInterface> &p_primary_interface) { - ERR_FAIL_COND(p_primary_interface.is_null()); - primary_interface = p_primary_interface; - - print_verbose("XR: Primary interface set to: " + primary_interface->get_name()); -}; - -void XRServer::clear_primary_interface_if(const Ref<XRInterface> &p_primary_interface) { - if (primary_interface == p_primary_interface) { + if (p_primary_interface.is_null()) { print_verbose("XR: Clearing primary interface"); primary_interface.unref(); - }; + } else { + primary_interface = p_primary_interface; + + print_verbose("XR: Primary interface set to: " + primary_interface->get_name()); + } }; uint64_t XRServer::get_last_process_usec() { diff --git a/servers/xr_server.h b/servers/xr_server.h index 25431844c2..af183e175d 100644 --- a/servers/xr_server.h +++ b/servers/xr_server.h @@ -159,7 +159,6 @@ public: */ Ref<XRInterface> get_primary_interface() const; void set_primary_interface(const Ref<XRInterface> &p_primary_interface); - void clear_primary_interface_if(const Ref<XRInterface> &p_primary_interface); /* this is automatically called if an interface destructs */ /* Our trackers are objects that expose the orientation and position of physical devices such as controller, anchor points, etc. diff --git a/tests/test_class_db.h b/tests/test_class_db.h index ea680da5d6..20397bb144 100644 --- a/tests/test_class_db.h +++ b/tests/test_class_db.h @@ -527,7 +527,7 @@ void add_exposed_classes(Context &r_context) { Map<StringName, StringName> accessor_methods; for (const PropertyInfo &property : property_list) { - if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY) { + if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY || (property.type == Variant::NIL && property.usage & PROPERTY_USAGE_ARRAY)) { continue; } diff --git a/tests/test_config_file.h b/tests/test_config_file.h index 33fd30ffa1..e3f40e16fd 100644 --- a/tests/test_config_file.h +++ b/tests/test_config_file.h @@ -122,6 +122,8 @@ TEST_CASE("[ConfigFile] Saving file") { config_file.set_value("player", "position", Vector2(3, 4)); config_file.set_value("graphics", "antialiasing", true); config_file.set_value("graphics", "antiAliasing", false); + config_file.set_value("quoted", String::utf8("静音"), 42); + config_file.set_value("quoted", "a=b", 7); #ifdef WINDOWS_ENABLED const String config_path = OS::get_singleton()->get_environment("TEMP").plus_file("config.ini"); @@ -132,7 +134,7 @@ TEST_CASE("[ConfigFile] Saving file") { config_file.save(config_path); // Expected contents of the saved ConfigFile. - const String contents = R"([player] + const String contents = String::utf8(R"([player] name="Unnamed Player" tagline="Waiting @@ -145,7 +147,12 @@ position=Vector2(3, 4) antialiasing=true antiAliasing=false -)"; + +[quoted] + +"静音"=42 +"a=b"=7 +)"); FileAccessRef file = FileAccess::open(config_path, FileAccess::READ); CHECK_MESSAGE(file->get_as_utf8_string() == contents, diff --git a/tests/test_geometry_2d.h b/tests/test_geometry_2d.h index 32d4114a1c..25af8c355e 100644 --- a/tests/test_geometry_2d.h +++ b/tests/test_geometry_2d.h @@ -51,8 +51,6 @@ TEST_CASE("[Geometry2D] Point in circle") { CHECK_FALSE(Geometry2D::is_point_in_circle(Vector2(7, -42), Vector2(4, -40), 3.5)); // This tests points on the edge of the circle. They are treated as being inside the circle. - // In `is_point_in_triangle` and `is_point_in_polygon` they are treated as being outside, so in order the make - // the behaviour consistent this may change in the future (see issue #44717 and PR #44274). CHECK(Geometry2D::is_point_in_circle(Vector2(1.0, 0.0), Vector2(0, 0), 1.0)); CHECK(Geometry2D::is_point_in_circle(Vector2(0.0, -1.0), Vector2(0, 0), 1.0)); } @@ -66,7 +64,7 @@ TEST_CASE("[Geometry2D] Point in triangle") { CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(0, 0), Vector2(1, 4), Vector2(3, 2), Vector2(5, 4))); // This tests points on the edge of the triangle. They are treated as being outside the triangle. - // In `is_point_in_circle` they are treated as being inside, so in order the make + // In `is_point_in_circle` and `is_point_in_polygon` they are treated as being inside, so in order the make // the behaviour consistent this may change in the future (see issue #44717 and PR #44274). CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(1, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1))); CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(0, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1))); @@ -95,11 +93,16 @@ TEST_CASE("[Geometry2D] Point in polygon") { CHECK(Geometry2D::is_point_in_polygon(Vector2(370, 55), p)); CHECK(Geometry2D::is_point_in_polygon(Vector2(-160, 190), p)); - // This tests points on the edge of the polygon. They are treated as being outside the polygon. - // In `is_point_in_circle` they are treated as being inside, so in order the make - // the behaviour consistent this may change in the future (see issue #44717 and PR #44274). - CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(68, 112), p)); - CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(-88, 120), p)); + // This tests points on the edge of the polygon. They are treated as being inside the polygon. + int c = p.size(); + for (int i = 0; i < c; i++) { + const Vector2 &p1 = p[i]; + CHECK(Geometry2D::is_point_in_polygon(p1, p)); + + const Vector2 &p2 = p[(i + 1) % c]; + Vector2 midpoint((p1 + p2) * 0.5); + CHECK(Geometry2D::is_point_in_polygon(midpoint, p)); + } } TEST_CASE("[Geometry2D] Polygon clockwise") { @@ -140,9 +143,21 @@ TEST_CASE("[Geometry2D] Segment intersection.") { CHECK(r.is_equal_approx(Vector2(0, 0))); CHECK_FALSE(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(0.1, 0.1), &r)); + CHECK_FALSE(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0.1, 0.1), Vector2(1, 1), &r)); + CHECK_FALSE_MESSAGE( - Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0, 1), Vector2(1, -1), &r), + Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0, 1), Vector2(2, -1), &r), "Parallel segments should not intersect."); + + CHECK_MESSAGE( + Geometry2D::segment_intersects_segment(Vector2(0, 0), Vector2(0, 1), Vector2(0, 0), Vector2(1, 0), &r), + "Touching segments should intersect."); + CHECK(r.is_equal_approx(Vector2(0, 0))); + + CHECK_MESSAGE( + Geometry2D::segment_intersects_segment(Vector2(0, 1), Vector2(0, 0), Vector2(0, 0), Vector2(1, 0), &r), + "Touching segments should intersect."); + CHECK(r.is_equal_approx(Vector2(0, 0))); } TEST_CASE("[Geometry2D] Closest point to segment") { diff --git a/tests/test_object.h b/tests/test_object.h index a18adf31b6..8cb7116a20 100644 --- a/tests/test_object.h +++ b/tests/test_object.h @@ -93,8 +93,8 @@ public: Ref<Script> get_script() const override { return Ref<Script>(); } - const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override { - return Vector<MultiplayerAPI::RPCConfig>(); + const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override { + return Vector<Multiplayer::RPCConfig>(); } ScriptLanguage *get_language() override { return nullptr; diff --git a/tests/test_string.h b/tests/test_string.h index 79fdb7bb56..82b23d8a00 100644 --- a/tests/test_string.h +++ b/tests/test_string.h @@ -1155,7 +1155,7 @@ TEST_CASE("[String] Path functions") { CHECK(String(path[i]).get_extension() == ext[i]); CHECK(String(path[i]).get_file() == file[i]); CHECK(String(path[i]).is_absolute_path() == abs[i]); - CHECK(String(path[i]).is_rel_path() != abs[i]); + CHECK(String(path[i]).is_relative_path() != abs[i]); CHECK(String(path[i]).simplify_path().get_base_dir().plus_file(file[i]) == String(path[i]).simplify_path()); } diff --git a/tests/test_text_server.h b/tests/test_text_server.h index cac022e33f..b8ed4f89d0 100644 --- a/tests/test_text_server.h +++ b/tests/test_text_server.h @@ -55,7 +55,8 @@ TEST_SUITE("[[TextServer]") { for (int i = 0; i < TextServerManager::get_interface_count(); i++) { TextServer *ts = TextServerManager::initialize(i, err); - RID font = ts->create_font_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf"); + RID font = ts->create_font(); + ts->font_set_data_ptr(font, _font_NotoSans_Regular, _font_NotoSans_Regular_size); TEST_FAIL_COND(font == RID(), "Loading font failed."); ts->free(font); } @@ -65,9 +66,14 @@ TEST_SUITE("[[TextServer]") { for (int i = 0; i < TextServerManager::get_interface_count(); i++) { TextServer *ts = TextServerManager::initialize(i, err); + RID font1 = ts->create_font(); + ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size); + RID font2 = ts->create_font(); + ts->font_set_data_ptr(font2, _font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size); + Vector<RID> font; - font.push_back(ts->create_font_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf")); - font.push_back(ts->create_font_memory(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, "ttf")); + font.push_back(font1); + font.push_back(font2); String test = U"คนอ้วน khon uan ראה"; // 6^ 17^ @@ -111,9 +117,14 @@ TEST_SUITE("[[TextServer]") { continue; } + RID font1 = ts->create_font(); + ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size); + RID font2 = ts->create_font(); + ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size); + Vector<RID> font; - font.push_back(ts->create_font_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf")); - font.push_back(ts->create_font_memory(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, "ttf")); + font.push_back(font1); + font.push_back(font2); String test = U"Arabic (اَلْعَرَبِيَّةُ, al-ʿarabiyyah)"; // 7^ 26^ @@ -155,9 +166,14 @@ TEST_SUITE("[[TextServer]") { String test_1 = U"test test test"; // 5^ 10^ + RID font1 = ts->create_font(); + ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size); + RID font2 = ts->create_font(); + ts->font_set_data_ptr(font2, _font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size); + Vector<RID> font; - font.push_back(ts->create_font_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf")); - font.push_back(ts->create_font_memory(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, "ttf")); + font.push_back(font1); + font.push_back(font2); RID ctx = ts->create_shaped_text(); TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); @@ -185,9 +201,14 @@ TEST_SUITE("[[TextServer]") { for (int i = 0; i < TextServerManager::get_interface_count(); i++) { TextServer *ts = TextServerManager::initialize(i, err); + RID font1 = ts->create_font(); + ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size); + RID font2 = ts->create_font(); + ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size); + Vector<RID> font; - font.push_back(ts->create_font_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf")); - font.push_back(ts->create_font_memory(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, "ttf")); + font.push_back(font1); + font.push_back(font2); String test_1 = U"الحمد"; String test_2 = U"الحمد test"; @@ -242,7 +263,6 @@ TEST_SUITE("[[TextServer]") { font.clear(); } } - memdelete(tsman); } } diff --git a/thirdparty/README.md b/thirdparty/README.md index 1c310eae05..3b5ec77b73 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -472,16 +472,25 @@ Collection of single-file libraries used in Godot components. * Upstream: https://github.com/nothings/stb * Version: 1.00 (2bb4a0accd4003c1db4c24533981e01b1adfd656, 2019) * License: Public Domain or Unlicense or MIT -- `stb_vorbis.c` - * Upstream: https://github.com/nothings/stb - * Version: 1.20 (314d0a6f9af5af27e585336eecea333e95c5a2d8, 2020) - * License: Public Domain or Unlicense or MIT - `yuv2rgb.h` * Upstream: http://wss.co.uk/pinknoise/yuv2rgb/ (to check) * Version: ? * License: BSD +## msdfgen + +- Upstream: https://github.com/Chlumsky/msdfgen +- Version: 1.9.1 (1b3b6b985094e6f12751177490add3ad11dd91a9, 2010) +- License: MIT + +Files extracted from the upstream source: + +- `msdfgen.h` +- Files in `core/` folder. +- `LICENSE.txt` and `CHANGELOG.md` + + ## nanosvg - Upstream: https://github.com/memononen/nanosvg diff --git a/thirdparty/misc/stb_vorbis.c b/thirdparty/misc/stb_vorbis.c deleted file mode 100644 index a8cbfa6c23..0000000000 --- a/thirdparty/misc/stb_vorbis.c +++ /dev/null @@ -1,5563 +0,0 @@ -// Ogg Vorbis audio decoder - v1.20 - public domain -// http://nothings.org/stb_vorbis/ -// -// Original version written by Sean Barrett in 2007. -// -// Originally sponsored by RAD Game Tools. Seeking implementation -// sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker, -// Elias Software, Aras Pranckevicius, and Sean Barrett. -// -// LICENSE -// -// See end of file for license information. -// -// Limitations: -// -// - floor 0 not supported (used in old ogg vorbis files pre-2004) -// - lossless sample-truncation at beginning ignored -// - cannot concatenate multiple vorbis streams -// - sample positions are 32-bit, limiting seekable 192Khz -// files to around 6 hours (Ogg supports 64-bit) -// -// Feature contributors: -// Dougall Johnson (sample-exact seeking) -// -// Bugfix/warning contributors: -// Terje Mathisen Niklas Frykholm Andy Hill -// Casey Muratori John Bolton Gargaj -// Laurent Gomila Marc LeBlanc Ronny Chevalier -// Bernhard Wodo Evan Balster github:alxprd -// Tom Beaumont Ingo Leitgeb Nicolas Guillemot -// Phillip Bennefall Rohit Thiago Goulart -// github:manxorist saga musix github:infatum -// Timur Gagiev Maxwell Koo Peter Waller -// github:audinowho Dougall Johnson David Reid -// github:Clownacy Pedro J. Estebanez Remi Verschelde -// -// Partial history: -// 1.20 - 2020-07-11 - several small fixes -// 1.19 - 2020-02-05 - warnings -// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc. -// 1.17 - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure) -// 1.16 - 2019-03-04 - fix warnings -// 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found -// 1.14 - 2018-02-11 - delete bogus dealloca usage -// 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) -// 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files -// 1.11 - 2017-07-23 - fix MinGW compilation -// 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory -// 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version -// 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame -// 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const -// 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) -// some crash fixes when out of memory or with corrupt files -// fix some inappropriately signed shifts -// 1.05 - 2015-04-19 - don't define __forceinline if it's redundant -// 1.04 - 2014-08-27 - fix missing const-correct case in API -// 1.03 - 2014-08-07 - warning fixes -// 1.02 - 2014-07-09 - declare qsort comparison as explicitly _cdecl in Windows -// 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct) -// 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel; -// (API change) report sample rate for decode-full-file funcs -// -// See end of file for full version history. - - -////////////////////////////////////////////////////////////////////////////// -// -// HEADER BEGINS HERE -// - -#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H -#define STB_VORBIS_INCLUDE_STB_VORBIS_H - -#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) -#define STB_VORBIS_NO_STDIO 1 -#endif - -#ifndef STB_VORBIS_NO_STDIO -#include <stdio.h> -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/////////// THREAD SAFETY - -// Individual stb_vorbis* handles are not thread-safe; you cannot decode from -// them from multiple threads at the same time. However, you can have multiple -// stb_vorbis* handles and decode from them independently in multiple thrads. - - -/////////// MEMORY ALLOCATION - -// normally stb_vorbis uses malloc() to allocate memory at startup, -// and alloca() to allocate temporary memory during a frame on the -// stack. (Memory consumption will depend on the amount of setup -// data in the file and how you set the compile flags for speed -// vs. size. In my test files the maximal-size usage is ~150KB.) -// -// You can modify the wrapper functions in the source (setup_malloc, -// setup_temp_malloc, temp_malloc) to change this behavior, or you -// can use a simpler allocation model: you pass in a buffer from -// which stb_vorbis will allocate _all_ its memory (including the -// temp memory). "open" may fail with a VORBIS_outofmem if you -// do not pass in enough data; there is no way to determine how -// much you do need except to succeed (at which point you can -// query get_info to find the exact amount required. yes I know -// this is lame). -// -// If you pass in a non-NULL buffer of the type below, allocation -// will occur from it as described above. Otherwise just pass NULL -// to use malloc()/alloca() - -typedef struct -{ - char *alloc_buffer; - int alloc_buffer_length_in_bytes; -} stb_vorbis_alloc; - - -/////////// FUNCTIONS USEABLE WITH ALL INPUT MODES - -typedef struct stb_vorbis stb_vorbis; - -typedef struct -{ - unsigned int sample_rate; - int channels; - - unsigned int setup_memory_required; - unsigned int setup_temp_memory_required; - unsigned int temp_memory_required; - - int max_frame_size; -} stb_vorbis_info; - -typedef struct -{ - char *vendor; - - int comment_list_length; - char **comment_list; -} stb_vorbis_comment; - -// get general information about the file -extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f); - -// get ogg comments -extern stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f); - -// get the last error detected (clears it, too) -extern int stb_vorbis_get_error(stb_vorbis *f); - -// close an ogg vorbis file and free all memory in use -extern void stb_vorbis_close(stb_vorbis *f); - -// this function returns the offset (in samples) from the beginning of the -// file that will be returned by the next decode, if it is known, or -1 -// otherwise. after a flush_pushdata() call, this may take a while before -// it becomes valid again. -// NOT WORKING YET after a seek with PULLDATA API -extern int stb_vorbis_get_sample_offset(stb_vorbis *f); - -// returns the current seek point within the file, or offset from the beginning -// of the memory buffer. In pushdata mode it returns 0. -extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); - -/////////// PUSHDATA API - -#ifndef STB_VORBIS_NO_PUSHDATA_API - -// this API allows you to get blocks of data from any source and hand -// them to stb_vorbis. you have to buffer them; stb_vorbis will tell -// you how much it used, and you have to give it the rest next time; -// and stb_vorbis may not have enough data to work with and you will -// need to give it the same data again PLUS more. Note that the Vorbis -// specification does not bound the size of an individual frame. - -extern stb_vorbis *stb_vorbis_open_pushdata( - const unsigned char * datablock, int datablock_length_in_bytes, - int *datablock_memory_consumed_in_bytes, - int *error, - const stb_vorbis_alloc *alloc_buffer); -// create a vorbis decoder by passing in the initial data block containing -// the ogg&vorbis headers (you don't need to do parse them, just provide -// the first N bytes of the file--you're told if it's not enough, see below) -// on success, returns an stb_vorbis *, does not set error, returns the amount of -// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes; -// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed -// if returns NULL and *error is VORBIS_need_more_data, then the input block was -// incomplete and you need to pass in a larger block from the start of the file - -extern int stb_vorbis_decode_frame_pushdata( - stb_vorbis *f, - const unsigned char *datablock, int datablock_length_in_bytes, - int *channels, // place to write number of float * buffers - float ***output, // place to write float ** array of float * buffers - int *samples // place to write number of output samples - ); -// decode a frame of audio sample data if possible from the passed-in data block -// -// return value: number of bytes we used from datablock -// -// possible cases: -// 0 bytes used, 0 samples output (need more data) -// N bytes used, 0 samples output (resynching the stream, keep going) -// N bytes used, M samples output (one frame of data) -// note that after opening a file, you will ALWAYS get one N-bytes,0-sample -// frame, because Vorbis always "discards" the first frame. -// -// Note that on resynch, stb_vorbis will rarely consume all of the buffer, -// instead only datablock_length_in_bytes-3 or less. This is because it wants -// to avoid missing parts of a page header if they cross a datablock boundary, -// without writing state-machiney code to record a partial detection. -// -// The number of channels returned are stored in *channels (which can be -// NULL--it is always the same as the number of channels reported by -// get_info). *output will contain an array of float* buffers, one per -// channel. In other words, (*output)[0][0] contains the first sample from -// the first channel, and (*output)[1][0] contains the first sample from -// the second channel. - -extern void stb_vorbis_flush_pushdata(stb_vorbis *f); -// inform stb_vorbis that your next datablock will not be contiguous with -// previous ones (e.g. you've seeked in the data); future attempts to decode -// frames will cause stb_vorbis to resynchronize (as noted above), and -// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it -// will begin decoding the _next_ frame. -// -// if you want to seek using pushdata, you need to seek in your file, then -// call stb_vorbis_flush_pushdata(), then start calling decoding, then once -// decoding is returning you data, call stb_vorbis_get_sample_offset, and -// if you don't like the result, seek your file again and repeat. -#endif - - -////////// PULLING INPUT API - -#ifndef STB_VORBIS_NO_PULLDATA_API -// This API assumes stb_vorbis is allowed to pull data from a source-- -// either a block of memory containing the _entire_ vorbis stream, or a -// FILE * that you or it create, or possibly some other reading mechanism -// if you go modify the source to replace the FILE * case with some kind -// of callback to your code. (But if you don't support seeking, you may -// just want to go ahead and use pushdata.) - -#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION) -extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output); -#endif -#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION) -extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output); -#endif -// decode an entire file and output the data interleaved into a malloc()ed -// buffer stored in *output. The return value is the number of samples -// decoded, or -1 if the file could not be opened or was not an ogg vorbis file. -// When you're done with it, just free() the pointer returned in *output. - -extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, - int *error, const stb_vorbis_alloc *alloc_buffer); -// create an ogg vorbis decoder from an ogg vorbis stream in memory (note -// this must be the entire stream!). on failure, returns NULL and sets *error - -#ifndef STB_VORBIS_NO_STDIO -extern stb_vorbis * stb_vorbis_open_filename(const char *filename, - int *error, const stb_vorbis_alloc *alloc_buffer); -// create an ogg vorbis decoder from a filename via fopen(). on failure, -// returns NULL and sets *error (possibly to VORBIS_file_open_failure). - -extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, - int *error, const stb_vorbis_alloc *alloc_buffer); -// create an ogg vorbis decoder from an open FILE *, looking for a stream at -// the _current_ seek point (ftell). on failure, returns NULL and sets *error. -// note that stb_vorbis must "own" this stream; if you seek it in between -// calls to stb_vorbis, it will become confused. Moreover, if you attempt to -// perform stb_vorbis_seek_*() operations on this file, it will assume it -// owns the _entire_ rest of the file after the start point. Use the next -// function, stb_vorbis_open_file_section(), to limit it. - -extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, - int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len); -// create an ogg vorbis decoder from an open FILE *, looking for a stream at -// the _current_ seek point (ftell); the stream will be of length 'len' bytes. -// on failure, returns NULL and sets *error. note that stb_vorbis must "own" -// this stream; if you seek it in between calls to stb_vorbis, it will become -// confused. -#endif - -extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number); -extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); -// these functions seek in the Vorbis file to (approximately) 'sample_number'. -// after calling seek_frame(), the next call to get_frame_*() will include -// the specified sample. after calling stb_vorbis_seek(), the next call to -// stb_vorbis_get_samples_* will start with the specified sample. If you -// do not need to seek to EXACTLY the target sample when using get_samples_*, -// you can also use seek_frame(). - -extern int stb_vorbis_seek_start(stb_vorbis *f); -// this function is equivalent to stb_vorbis_seek(f,0) - -extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f); -extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f); -// these functions return the total length of the vorbis stream - -extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output); -// decode the next frame and return the number of samples. the number of -// channels returned are stored in *channels (which can be NULL--it is always -// the same as the number of channels reported by get_info). *output will -// contain an array of float* buffers, one per channel. These outputs will -// be overwritten on the next call to stb_vorbis_get_frame_*. -// -// You generally should not intermix calls to stb_vorbis_get_frame_*() -// and stb_vorbis_get_samples_*(), since the latter calls the former. - -#ifndef STB_VORBIS_NO_INTEGER_CONVERSION -extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts); -extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples); -#endif -// decode the next frame and return the number of *samples* per channel. -// Note that for interleaved data, you pass in the number of shorts (the -// size of your array), but the return value is the number of samples per -// channel, not the total number of samples. -// -// The data is coerced to the number of channels you request according to the -// channel coercion rules (see below). You must pass in the size of your -// buffer(s) so that stb_vorbis will not overwrite the end of the buffer. -// The maximum buffer size needed can be gotten from get_info(); however, -// the Vorbis I specification implies an absolute maximum of 4096 samples -// per channel. - -// Channel coercion rules: -// Let M be the number of channels requested, and N the number of channels present, -// and Cn be the nth channel; let stereo L be the sum of all L and center channels, -// and stereo R be the sum of all R and center channels (channel assignment from the -// vorbis spec). -// M N output -// 1 k sum(Ck) for all k -// 2 * stereo L, stereo R -// k l k > l, the first l channels, then 0s -// k l k <= l, the first k channels -// Note that this is not _good_ surround etc. mixing at all! It's just so -// you get something useful. - -extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats); -extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples); -// gets num_samples samples, not necessarily on a frame boundary--this requires -// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES. -// Returns the number of samples stored per channel; it may be less than requested -// at the end of the file. If there are no more samples in the file, returns 0. - -#ifndef STB_VORBIS_NO_INTEGER_CONVERSION -extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts); -extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples); -#endif -// gets num_samples samples, not necessarily on a frame boundary--this requires -// buffering so you have to supply the buffers. Applies the coercion rules above -// to produce 'channels' channels. Returns the number of samples stored per channel; -// it may be less than requested at the end of the file. If there are no more -// samples in the file, returns 0. - -#endif - -//////// ERROR CODES - -enum STBVorbisError -{ - VORBIS__no_error, - - VORBIS_need_more_data=1, // not a real error - - VORBIS_invalid_api_mixing, // can't mix API modes - VORBIS_outofmem, // not enough memory - VORBIS_feature_not_supported, // uses floor 0 - VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small - VORBIS_file_open_failure, // fopen() failed - VORBIS_seek_without_length, // can't seek in unknown-length file - - VORBIS_unexpected_eof=10, // file is truncated? - VORBIS_seek_invalid, // seek past EOF - - // decoding errors (corrupt/invalid stream) -- you probably - // don't care about the exact details of these - - // vorbis errors: - VORBIS_invalid_setup=20, - VORBIS_invalid_stream, - - // ogg errors: - VORBIS_missing_capture_pattern=30, - VORBIS_invalid_stream_structure_version, - VORBIS_continued_packet_flag_invalid, - VORBIS_incorrect_stream_serial_number, - VORBIS_invalid_first_page, - VORBIS_bad_packet_type, - VORBIS_cant_find_last_page, - VORBIS_seek_failed, - VORBIS_ogg_skeleton_not_supported -}; - - -#ifdef __cplusplus -} -#endif - -#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H -// -// HEADER ENDS HERE -// -////////////////////////////////////////////////////////////////////////////// - -#ifndef STB_VORBIS_HEADER_ONLY - -// global configuration settings (e.g. set these in the project/makefile), -// or just set them in this file at the top (although ideally the first few -// should be visible when the header file is compiled too, although it's not -// crucial) - -// STB_VORBIS_NO_PUSHDATA_API -// does not compile the code for the various stb_vorbis_*_pushdata() -// functions -// #define STB_VORBIS_NO_PUSHDATA_API - -// STB_VORBIS_NO_PULLDATA_API -// does not compile the code for the non-pushdata APIs -// #define STB_VORBIS_NO_PULLDATA_API - -// STB_VORBIS_NO_STDIO -// does not compile the code for the APIs that use FILE *s internally -// or externally (implied by STB_VORBIS_NO_PULLDATA_API) -// #define STB_VORBIS_NO_STDIO - -// STB_VORBIS_NO_INTEGER_CONVERSION -// does not compile the code for converting audio sample data from -// float to integer (implied by STB_VORBIS_NO_PULLDATA_API) -// #define STB_VORBIS_NO_INTEGER_CONVERSION - -// STB_VORBIS_NO_FAST_SCALED_FLOAT -// does not use a fast float-to-int trick to accelerate float-to-int on -// most platforms which requires endianness be defined correctly. -//#define STB_VORBIS_NO_FAST_SCALED_FLOAT - - -// STB_VORBIS_MAX_CHANNELS [number] -// globally define this to the maximum number of channels you need. -// The spec does not put a restriction on channels except that -// the count is stored in a byte, so 255 is the hard limit. -// Reducing this saves about 16 bytes per value, so using 16 saves -// (255-16)*16 or around 4KB. Plus anything other memory usage -// I forgot to account for. Can probably go as low as 8 (7.1 audio), -// 6 (5.1 audio), or 2 (stereo only). -#ifndef STB_VORBIS_MAX_CHANNELS -#define STB_VORBIS_MAX_CHANNELS 16 // enough for anyone? -#endif - -// STB_VORBIS_PUSHDATA_CRC_COUNT [number] -// after a flush_pushdata(), stb_vorbis begins scanning for the -// next valid page, without backtracking. when it finds something -// that looks like a page, it streams through it and verifies its -// CRC32. Should that validation fail, it keeps scanning. But it's -// possible that _while_ streaming through to check the CRC32 of -// one candidate page, it sees another candidate page. This #define -// determines how many "overlapping" candidate pages it can search -// at once. Note that "real" pages are typically ~4KB to ~8KB, whereas -// garbage pages could be as big as 64KB, but probably average ~16KB. -// So don't hose ourselves by scanning an apparent 64KB page and -// missing a ton of real ones in the interim; so minimum of 2 -#ifndef STB_VORBIS_PUSHDATA_CRC_COUNT -#define STB_VORBIS_PUSHDATA_CRC_COUNT 4 -#endif - -// STB_VORBIS_FAST_HUFFMAN_LENGTH [number] -// sets the log size of the huffman-acceleration table. Maximum -// supported value is 24. with larger numbers, more decodings are O(1), -// but the table size is larger so worse cache missing, so you'll have -// to probe (and try multiple ogg vorbis files) to find the sweet spot. -#ifndef STB_VORBIS_FAST_HUFFMAN_LENGTH -#define STB_VORBIS_FAST_HUFFMAN_LENGTH 10 -#endif - -// STB_VORBIS_FAST_BINARY_LENGTH [number] -// sets the log size of the binary-search acceleration table. this -// is used in similar fashion to the fast-huffman size to set initial -// parameters for the binary search - -// STB_VORBIS_FAST_HUFFMAN_INT -// The fast huffman tables are much more efficient if they can be -// stored as 16-bit results instead of 32-bit results. This restricts -// the codebooks to having only 65535 possible outcomes, though. -// (At least, accelerated by the huffman table.) -#ifndef STB_VORBIS_FAST_HUFFMAN_INT -#define STB_VORBIS_FAST_HUFFMAN_SHORT -#endif - -// STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH -// If the 'fast huffman' search doesn't succeed, then stb_vorbis falls -// back on binary searching for the correct one. This requires storing -// extra tables with the huffman codes in sorted order. Defining this -// symbol trades off space for speed by forcing a linear search in the -// non-fast case, except for "sparse" codebooks. -// #define STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH - -// STB_VORBIS_DIVIDES_IN_RESIDUE -// stb_vorbis precomputes the result of the scalar residue decoding -// that would otherwise require a divide per chunk. you can trade off -// space for time by defining this symbol. -// #define STB_VORBIS_DIVIDES_IN_RESIDUE - -// STB_VORBIS_DIVIDES_IN_CODEBOOK -// vorbis VQ codebooks can be encoded two ways: with every case explicitly -// stored, or with all elements being chosen from a small range of values, -// and all values possible in all elements. By default, stb_vorbis expands -// this latter kind out to look like the former kind for ease of decoding, -// because otherwise an integer divide-per-vector-element is required to -// unpack the index. If you define STB_VORBIS_DIVIDES_IN_CODEBOOK, you can -// trade off storage for speed. -//#define STB_VORBIS_DIVIDES_IN_CODEBOOK - -#ifdef STB_VORBIS_CODEBOOK_SHORTS -#error "STB_VORBIS_CODEBOOK_SHORTS is no longer supported as it produced incorrect results for some input formats" -#endif - -// STB_VORBIS_DIVIDE_TABLE -// this replaces small integer divides in the floor decode loop with -// table lookups. made less than 1% difference, so disabled by default. - -// STB_VORBIS_NO_INLINE_DECODE -// disables the inlining of the scalar codebook fast-huffman decode. -// might save a little codespace; useful for debugging -// #define STB_VORBIS_NO_INLINE_DECODE - -// STB_VORBIS_NO_DEFER_FLOOR -// Normally we only decode the floor without synthesizing the actual -// full curve. We can instead synthesize the curve immediately. This -// requires more memory and is very likely slower, so I don't think -// you'd ever want to do it except for debugging. -// #define STB_VORBIS_NO_DEFER_FLOOR - - - - -////////////////////////////////////////////////////////////////////////////// - -#ifdef STB_VORBIS_NO_PULLDATA_API - #define STB_VORBIS_NO_INTEGER_CONVERSION - #define STB_VORBIS_NO_STDIO -#endif - -#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) - #define STB_VORBIS_NO_STDIO 1 -#endif - -#ifndef STB_VORBIS_NO_INTEGER_CONVERSION -#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT - - // only need endianness for fast-float-to-int, which we don't - // use for pushdata - - #ifndef STB_VORBIS_BIG_ENDIAN - #define STB_VORBIS_ENDIAN 0 - #else - #define STB_VORBIS_ENDIAN 1 - #endif - -#endif -#endif - - -#ifndef STB_VORBIS_NO_STDIO -#include <stdio.h> -#endif - -#ifndef STB_VORBIS_NO_CRT - #include <stdlib.h> - #include <string.h> - #include <assert.h> - #include <math.h> - - // find definition of alloca if it's not in stdlib.h: - #if defined(_MSC_VER) || defined(__MINGW32__) - #include <malloc.h> - #endif - #if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__) - #include <alloca.h> - #endif -#else // STB_VORBIS_NO_CRT - #define NULL 0 - #define malloc(s) 0 - #define free(s) ((void) 0) - #define realloc(s) 0 -#endif // STB_VORBIS_NO_CRT - -#include <limits.h> - -#ifdef __MINGW32__ - // eff you mingw: - // "fixed": - // http://sourceforge.net/p/mingw-w64/mailman/message/32882927/ - // "no that broke the build, reverted, who cares about C": - // http://sourceforge.net/p/mingw-w64/mailman/message/32890381/ - #ifdef __forceinline - #undef __forceinline - #endif - #define __forceinline - #ifndef alloca - #define alloca __builtin_alloca - #endif -#elif !defined(_MSC_VER) - #if __GNUC__ - #define __forceinline inline - #else - #define __forceinline - #endif -#endif - -#if STB_VORBIS_MAX_CHANNELS > 256 -#error "Value of STB_VORBIS_MAX_CHANNELS outside of allowed range" -#endif - -#if STB_VORBIS_FAST_HUFFMAN_LENGTH > 24 -#error "Value of STB_VORBIS_FAST_HUFFMAN_LENGTH outside of allowed range" -#endif - - -#if 0 -#include <crtdbg.h> -#define CHECK(f) _CrtIsValidHeapPointer(f->channel_buffers[1]) -#else -#define CHECK(f) ((void) 0) -#endif - -#define MAX_BLOCKSIZE_LOG 13 // from specification -#define MAX_BLOCKSIZE (1 << MAX_BLOCKSIZE_LOG) - - -typedef unsigned char uint8; -typedef signed char int8; -typedef unsigned short uint16; -typedef signed short int16; -typedef unsigned int uint32; -typedef signed int int32; - -#ifndef TRUE -#define TRUE 1 -#define FALSE 0 -#endif - -typedef float codetype; - -// @NOTE -// -// Some arrays below are tagged "//varies", which means it's actually -// a variable-sized piece of data, but rather than malloc I assume it's -// small enough it's better to just allocate it all together with the -// main thing -// -// Most of the variables are specified with the smallest size I could pack -// them into. It might give better performance to make them all full-sized -// integers. It should be safe to freely rearrange the structures or change -// the sizes larger--nothing relies on silently truncating etc., nor the -// order of variables. - -#define FAST_HUFFMAN_TABLE_SIZE (1 << STB_VORBIS_FAST_HUFFMAN_LENGTH) -#define FAST_HUFFMAN_TABLE_MASK (FAST_HUFFMAN_TABLE_SIZE - 1) - -typedef struct -{ - int dimensions, entries; - uint8 *codeword_lengths; - float minimum_value; - float delta_value; - uint8 value_bits; - uint8 lookup_type; - uint8 sequence_p; - uint8 sparse; - uint32 lookup_values; - codetype *multiplicands; - uint32 *codewords; - #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT - int16 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; - #else - int32 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; - #endif - uint32 *sorted_codewords; - int *sorted_values; - int sorted_entries; -} Codebook; - -typedef struct -{ - uint8 order; - uint16 rate; - uint16 bark_map_size; - uint8 amplitude_bits; - uint8 amplitude_offset; - uint8 number_of_books; - uint8 book_list[16]; // varies -} Floor0; - -typedef struct -{ - uint8 partitions; - uint8 partition_class_list[32]; // varies - uint8 class_dimensions[16]; // varies - uint8 class_subclasses[16]; // varies - uint8 class_masterbooks[16]; // varies - int16 subclass_books[16][8]; // varies - uint16 Xlist[31*8+2]; // varies - uint8 sorted_order[31*8+2]; - uint8 neighbors[31*8+2][2]; - uint8 floor1_multiplier; - uint8 rangebits; - int values; -} Floor1; - -typedef union -{ - Floor0 floor0; - Floor1 floor1; -} Floor; - -typedef struct -{ - uint32 begin, end; - uint32 part_size; - uint8 classifications; - uint8 classbook; - uint8 **classdata; - int16 (*residue_books)[8]; -} Residue; - -typedef struct -{ - uint8 magnitude; - uint8 angle; - uint8 mux; -} MappingChannel; - -typedef struct -{ - uint16 coupling_steps; - MappingChannel *chan; - uint8 submaps; - uint8 submap_floor[15]; // varies - uint8 submap_residue[15]; // varies -} Mapping; - -typedef struct -{ - uint8 blockflag; - uint8 mapping; - uint16 windowtype; - uint16 transformtype; -} Mode; - -typedef struct -{ - uint32 goal_crc; // expected crc if match - int bytes_left; // bytes left in packet - uint32 crc_so_far; // running crc - int bytes_done; // bytes processed in _current_ chunk - uint32 sample_loc; // granule pos encoded in page -} CRCscan; - -typedef struct -{ - uint32 page_start, page_end; - uint32 last_decoded_sample; -} ProbedPage; - -struct stb_vorbis -{ - // user-accessible info - unsigned int sample_rate; - int channels; - - unsigned int setup_memory_required; - unsigned int temp_memory_required; - unsigned int setup_temp_memory_required; - - char *vendor; - int comment_list_length; - char **comment_list; - - // input config -#ifndef STB_VORBIS_NO_STDIO - FILE *f; - uint32 f_start; - int close_on_free; -#endif - - uint8 *stream; - uint8 *stream_start; - uint8 *stream_end; - - uint32 stream_len; - - uint8 push_mode; - - // the page to seek to when seeking to start, may be zero - uint32 first_audio_page_offset; - - // p_first is the page on which the first audio packet ends - // (but not necessarily the page on which it starts) - ProbedPage p_first, p_last; - - // memory management - stb_vorbis_alloc alloc; - int setup_offset; - int temp_offset; - - // run-time results - int eof; - enum STBVorbisError error; - - // user-useful data - - // header info - int blocksize[2]; - int blocksize_0, blocksize_1; - int codebook_count; - Codebook *codebooks; - int floor_count; - uint16 floor_types[64]; // varies - Floor *floor_config; - int residue_count; - uint16 residue_types[64]; // varies - Residue *residue_config; - int mapping_count; - Mapping *mapping; - int mode_count; - Mode mode_config[64]; // varies - - uint32 total_samples; - - // decode buffer - float *channel_buffers[STB_VORBIS_MAX_CHANNELS]; - float *outputs [STB_VORBIS_MAX_CHANNELS]; - - float *previous_window[STB_VORBIS_MAX_CHANNELS]; - int previous_length; - - #ifndef STB_VORBIS_NO_DEFER_FLOOR - int16 *finalY[STB_VORBIS_MAX_CHANNELS]; - #else - float *floor_buffers[STB_VORBIS_MAX_CHANNELS]; - #endif - - uint32 current_loc; // sample location of next frame to decode - int current_loc_valid; - - // per-blocksize precomputed data - - // twiddle factors - float *A[2],*B[2],*C[2]; - float *window[2]; - uint16 *bit_reverse[2]; - - // current page/packet/segment streaming info - uint32 serial; // stream serial number for verification - int last_page; - int segment_count; - uint8 segments[255]; - uint8 page_flag; - uint8 bytes_in_seg; - uint8 first_decode; - int next_seg; - int last_seg; // flag that we're on the last segment - int last_seg_which; // what was the segment number of the last seg? - uint32 acc; - int valid_bits; - int packet_bytes; - int end_seg_with_known_loc; - uint32 known_loc_for_packet; - int discard_samples_deferred; - uint32 samples_output; - - // push mode scanning - int page_crc_tests; // only in push_mode: number of tests active; -1 if not searching -#ifndef STB_VORBIS_NO_PUSHDATA_API - CRCscan scan[STB_VORBIS_PUSHDATA_CRC_COUNT]; -#endif - - // sample-access - int channel_buffer_start; - int channel_buffer_end; -}; - -#if defined(STB_VORBIS_NO_PUSHDATA_API) - #define IS_PUSH_MODE(f) FALSE -#elif defined(STB_VORBIS_NO_PULLDATA_API) - #define IS_PUSH_MODE(f) TRUE -#else - #define IS_PUSH_MODE(f) ((f)->push_mode) -#endif - -typedef struct stb_vorbis vorb; - -static int error(vorb *f, enum STBVorbisError e) -{ - f->error = e; - if (!f->eof && e != VORBIS_need_more_data) { - f->error=e; // breakpoint for debugging - } - return 0; -} - - -// these functions are used for allocating temporary memory -// while decoding. if you can afford the stack space, use -// alloca(); otherwise, provide a temp buffer and it will -// allocate out of those. - -#define array_size_required(count,size) (count*(sizeof(void *)+(size))) - -#define temp_alloc(f,size) (f->alloc.alloc_buffer ? setup_temp_malloc(f,size) : alloca(size)) -#define temp_free(f,p) (void)0 -#define temp_alloc_save(f) ((f)->temp_offset) -#define temp_alloc_restore(f,p) ((f)->temp_offset = (p)) - -#define temp_block_array(f,count,size) make_block_array(temp_alloc(f,array_size_required(count,size)), count, size) - -// given a sufficiently large block of memory, make an array of pointers to subblocks of it -static void *make_block_array(void *mem, int count, int size) -{ - int i; - void ** p = (void **) mem; - char *q = (char *) (p + count); - for (i=0; i < count; ++i) { - p[i] = q; - q += size; - } - return p; -} - -static void *setup_malloc(vorb *f, int sz) -{ - sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. - f->setup_memory_required += sz; - if (f->alloc.alloc_buffer) { - void *p = (char *) f->alloc.alloc_buffer + f->setup_offset; - if (f->setup_offset + sz > f->temp_offset) return NULL; - f->setup_offset += sz; - return p; - } - return sz ? malloc(sz) : NULL; -} - -static void setup_free(vorb *f, void *p) -{ - if (f->alloc.alloc_buffer) return; // do nothing; setup mem is a stack - free(p); -} - -static void *setup_temp_malloc(vorb *f, int sz) -{ - sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. - if (f->alloc.alloc_buffer) { - if (f->temp_offset - sz < f->setup_offset) return NULL; - f->temp_offset -= sz; - return (char *) f->alloc.alloc_buffer + f->temp_offset; - } - return malloc(sz); -} - -static void setup_temp_free(vorb *f, void *p, int sz) -{ - if (f->alloc.alloc_buffer) { - f->temp_offset += (sz+7)&~7; - return; - } - free(p); -} - -#define CRC32_POLY 0x04c11db7 // from spec - -static uint32 crc_table[256]; -static void crc32_init(void) -{ - int i,j; - uint32 s; - for(i=0; i < 256; i++) { - for (s=(uint32) i << 24, j=0; j < 8; ++j) - s = (s << 1) ^ (s >= (1U<<31) ? CRC32_POLY : 0); - crc_table[i] = s; - } -} - -static __forceinline uint32 crc32_update(uint32 crc, uint8 byte) -{ - return (crc << 8) ^ crc_table[byte ^ (crc >> 24)]; -} - - -// used in setup, and for huffman that doesn't go fast path -static unsigned int bit_reverse(unsigned int n) -{ - n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1); - n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2); - n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4); - n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8); - return (n >> 16) | (n << 16); -} - -static float square(float x) -{ - return x*x; -} - -// this is a weird definition of log2() for which log2(1) = 1, log2(2) = 2, log2(4) = 3 -// as required by the specification. fast(?) implementation from stb.h -// @OPTIMIZE: called multiple times per-packet with "constants"; move to setup -static int ilog(int32 n) -{ - static signed char log2_4[16] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 }; - - if (n < 0) return 0; // signed n returns 0 - - // 2 compares if n < 16, 3 compares otherwise (4 if signed or n > 1<<29) - if (n < (1 << 14)) - if (n < (1 << 4)) return 0 + log2_4[n ]; - else if (n < (1 << 9)) return 5 + log2_4[n >> 5]; - else return 10 + log2_4[n >> 10]; - else if (n < (1 << 24)) - if (n < (1 << 19)) return 15 + log2_4[n >> 15]; - else return 20 + log2_4[n >> 20]; - else if (n < (1 << 29)) return 25 + log2_4[n >> 25]; - else return 30 + log2_4[n >> 30]; -} - -#ifndef M_PI - #define M_PI 3.14159265358979323846264f // from CRC -#endif - -// code length assigned to a value with no huffman encoding -#define NO_CODE 255 - -/////////////////////// LEAF SETUP FUNCTIONS ////////////////////////// -// -// these functions are only called at setup, and only a few times -// per file - -static float float32_unpack(uint32 x) -{ - // from the specification - uint32 mantissa = x & 0x1fffff; - uint32 sign = x & 0x80000000; - uint32 exp = (x & 0x7fe00000) >> 21; - double res = sign ? -(double)mantissa : (double)mantissa; - return (float) ldexp((float)res, exp-788); -} - - -// zlib & jpeg huffman tables assume that the output symbols -// can either be arbitrarily arranged, or have monotonically -// increasing frequencies--they rely on the lengths being sorted; -// this makes for a very simple generation algorithm. -// vorbis allows a huffman table with non-sorted lengths. This -// requires a more sophisticated construction, since symbols in -// order do not map to huffman codes "in order". -static void add_entry(Codebook *c, uint32 huff_code, int symbol, int count, int len, uint32 *values) -{ - if (!c->sparse) { - c->codewords [symbol] = huff_code; - } else { - c->codewords [count] = huff_code; - c->codeword_lengths[count] = len; - values [count] = symbol; - } -} - -static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values) -{ - int i,k,m=0; - uint32 available[32]; - - memset(available, 0, sizeof(available)); - // find the first entry - for (k=0; k < n; ++k) if (len[k] < NO_CODE) break; - if (k == n) { assert(c->sorted_entries == 0); return TRUE; } - // add to the list - add_entry(c, 0, k, m++, len[k], values); - // add all available leaves - for (i=1; i <= len[k]; ++i) - available[i] = 1U << (32-i); - // note that the above code treats the first case specially, - // but it's really the same as the following code, so they - // could probably be combined (except the initial code is 0, - // and I use 0 in available[] to mean 'empty') - for (i=k+1; i < n; ++i) { - uint32 res; - int z = len[i], y; - if (z == NO_CODE) continue; - // find lowest available leaf (should always be earliest, - // which is what the specification calls for) - // note that this property, and the fact we can never have - // more than one free leaf at a given level, isn't totally - // trivial to prove, but it seems true and the assert never - // fires, so! - while (z > 0 && !available[z]) --z; - if (z == 0) { return FALSE; } - res = available[z]; - assert(z >= 0 && z < 32); - available[z] = 0; - add_entry(c, bit_reverse(res), i, m++, len[i], values); - // propagate availability up the tree - if (z != len[i]) { - assert(len[i] >= 0 && len[i] < 32); - for (y=len[i]; y > z; --y) { - assert(available[y] == 0); - available[y] = res + (1 << (32-y)); - } - } - } - return TRUE; -} - -// accelerated huffman table allows fast O(1) match of all symbols -// of length <= STB_VORBIS_FAST_HUFFMAN_LENGTH -static void compute_accelerated_huffman(Codebook *c) -{ - int i, len; - for (i=0; i < FAST_HUFFMAN_TABLE_SIZE; ++i) - c->fast_huffman[i] = -1; - - len = c->sparse ? c->sorted_entries : c->entries; - #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT - if (len > 32767) len = 32767; // largest possible value we can encode! - #endif - for (i=0; i < len; ++i) { - if (c->codeword_lengths[i] <= STB_VORBIS_FAST_HUFFMAN_LENGTH) { - uint32 z = c->sparse ? bit_reverse(c->sorted_codewords[i]) : c->codewords[i]; - // set table entries for all bit combinations in the higher bits - while (z < FAST_HUFFMAN_TABLE_SIZE) { - c->fast_huffman[z] = i; - z += 1 << c->codeword_lengths[i]; - } - } - } -} - -#ifdef _MSC_VER -#define STBV_CDECL __cdecl -#else -#define STBV_CDECL -#endif - -static int STBV_CDECL uint32_compare(const void *p, const void *q) -{ - uint32 x = * (uint32 *) p; - uint32 y = * (uint32 *) q; - return x < y ? -1 : x > y; -} - -static int include_in_sort(Codebook *c, uint8 len) -{ - if (c->sparse) { assert(len != NO_CODE); return TRUE; } - if (len == NO_CODE) return FALSE; - if (len > STB_VORBIS_FAST_HUFFMAN_LENGTH) return TRUE; - return FALSE; -} - -// if the fast table above doesn't work, we want to binary -// search them... need to reverse the bits -static void compute_sorted_huffman(Codebook *c, uint8 *lengths, uint32 *values) -{ - int i, len; - // build a list of all the entries - // OPTIMIZATION: don't include the short ones, since they'll be caught by FAST_HUFFMAN. - // this is kind of a frivolous optimization--I don't see any performance improvement, - // but it's like 4 extra lines of code, so. - if (!c->sparse) { - int k = 0; - for (i=0; i < c->entries; ++i) - if (include_in_sort(c, lengths[i])) - c->sorted_codewords[k++] = bit_reverse(c->codewords[i]); - assert(k == c->sorted_entries); - } else { - for (i=0; i < c->sorted_entries; ++i) - c->sorted_codewords[i] = bit_reverse(c->codewords[i]); - } - - qsort(c->sorted_codewords, c->sorted_entries, sizeof(c->sorted_codewords[0]), uint32_compare); - c->sorted_codewords[c->sorted_entries] = 0xffffffff; - - len = c->sparse ? c->sorted_entries : c->entries; - // now we need to indicate how they correspond; we could either - // #1: sort a different data structure that says who they correspond to - // #2: for each sorted entry, search the original list to find who corresponds - // #3: for each original entry, find the sorted entry - // #1 requires extra storage, #2 is slow, #3 can use binary search! - for (i=0; i < len; ++i) { - int huff_len = c->sparse ? lengths[values[i]] : lengths[i]; - if (include_in_sort(c,huff_len)) { - uint32 code = bit_reverse(c->codewords[i]); - int x=0, n=c->sorted_entries; - while (n > 1) { - // invariant: sc[x] <= code < sc[x+n] - int m = x + (n >> 1); - if (c->sorted_codewords[m] <= code) { - x = m; - n -= (n>>1); - } else { - n >>= 1; - } - } - assert(c->sorted_codewords[x] == code); - if (c->sparse) { - c->sorted_values[x] = values[i]; - c->codeword_lengths[x] = huff_len; - } else { - c->sorted_values[x] = i; - } - } - } -} - -// only run while parsing the header (3 times) -static int vorbis_validate(uint8 *data) -{ - static uint8 vorbis[6] = { 'v', 'o', 'r', 'b', 'i', 's' }; - return memcmp(data, vorbis, 6) == 0; -} - -// called from setup only, once per code book -// (formula implied by specification) -static int lookup1_values(int entries, int dim) -{ - int r = (int) floor(exp((float) log((float) entries) / dim)); - if ((int) floor(pow((float) r+1, dim)) <= entries) // (int) cast for MinGW warning; - ++r; // floor() to avoid _ftol() when non-CRT - if (pow((float) r+1, dim) <= entries) - return -1; - if ((int) floor(pow((float) r, dim)) > entries) - return -1; - return r; -} - -// called twice per file -static void compute_twiddle_factors(int n, float *A, float *B, float *C) -{ - int n4 = n >> 2, n8 = n >> 3; - int k,k2; - - for (k=k2=0; k < n4; ++k,k2+=2) { - A[k2 ] = (float) cos(4*k*M_PI/n); - A[k2+1] = (float) -sin(4*k*M_PI/n); - B[k2 ] = (float) cos((k2+1)*M_PI/n/2) * 0.5f; - B[k2+1] = (float) sin((k2+1)*M_PI/n/2) * 0.5f; - } - for (k=k2=0; k < n8; ++k,k2+=2) { - C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); - C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); - } -} - -static void compute_window(int n, float *window) -{ - int n2 = n >> 1, i; - for (i=0; i < n2; ++i) - window[i] = (float) sin(0.5 * M_PI * square((float) sin((i - 0 + 0.5) / n2 * 0.5 * M_PI))); -} - -static void compute_bitreverse(int n, uint16 *rev) -{ - int ld = ilog(n) - 1; // ilog is off-by-one from normal definitions - int i, n8 = n >> 3; - for (i=0; i < n8; ++i) - rev[i] = (bit_reverse(i) >> (32-ld+3)) << 2; -} - -static int init_blocksize(vorb *f, int b, int n) -{ - int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3; - f->A[b] = (float *) setup_malloc(f, sizeof(float) * n2); - f->B[b] = (float *) setup_malloc(f, sizeof(float) * n2); - f->C[b] = (float *) setup_malloc(f, sizeof(float) * n4); - if (!f->A[b] || !f->B[b] || !f->C[b]) return error(f, VORBIS_outofmem); - compute_twiddle_factors(n, f->A[b], f->B[b], f->C[b]); - f->window[b] = (float *) setup_malloc(f, sizeof(float) * n2); - if (!f->window[b]) return error(f, VORBIS_outofmem); - compute_window(n, f->window[b]); - f->bit_reverse[b] = (uint16 *) setup_malloc(f, sizeof(uint16) * n8); - if (!f->bit_reverse[b]) return error(f, VORBIS_outofmem); - compute_bitreverse(n, f->bit_reverse[b]); - return TRUE; -} - -static void neighbors(uint16 *x, int n, int *plow, int *phigh) -{ - int low = -1; - int high = 65536; - int i; - for (i=0; i < n; ++i) { - if (x[i] > low && x[i] < x[n]) { *plow = i; low = x[i]; } - if (x[i] < high && x[i] > x[n]) { *phigh = i; high = x[i]; } - } -} - -// this has been repurposed so y is now the original index instead of y -typedef struct -{ - uint16 x,id; -} stbv__floor_ordering; - -static int STBV_CDECL point_compare(const void *p, const void *q) -{ - stbv__floor_ordering *a = (stbv__floor_ordering *) p; - stbv__floor_ordering *b = (stbv__floor_ordering *) q; - return a->x < b->x ? -1 : a->x > b->x; -} - -// -/////////////////////// END LEAF SETUP FUNCTIONS ////////////////////////// - - -#if defined(STB_VORBIS_NO_STDIO) - #define USE_MEMORY(z) TRUE -#else - #define USE_MEMORY(z) ((z)->stream) -#endif - -static uint8 get8(vorb *z) -{ - if (USE_MEMORY(z)) { - if (z->stream >= z->stream_end) { z->eof = TRUE; return 0; } - return *z->stream++; - } - - #ifndef STB_VORBIS_NO_STDIO - { - int c = fgetc(z->f); - if (c == EOF) { z->eof = TRUE; return 0; } - return c; - } - #endif -} - -static uint32 get32(vorb *f) -{ - uint32 x; - x = get8(f); - x += get8(f) << 8; - x += get8(f) << 16; - x += (uint32) get8(f) << 24; - return x; -} - -static int getn(vorb *z, uint8 *data, int n) -{ - if (USE_MEMORY(z)) { - if (z->stream+n > z->stream_end) { z->eof = 1; return 0; } - memcpy(data, z->stream, n); - z->stream += n; - return 1; - } - - #ifndef STB_VORBIS_NO_STDIO - if (fread(data, n, 1, z->f) == 1) - return 1; - else { - z->eof = 1; - return 0; - } - #endif -} - -static void skip(vorb *z, int n) -{ - if (USE_MEMORY(z)) { - z->stream += n; - if (z->stream >= z->stream_end) z->eof = 1; - return; - } - #ifndef STB_VORBIS_NO_STDIO - { - long x = ftell(z->f); - fseek(z->f, x+n, SEEK_SET); - } - #endif -} - -static int set_file_offset(stb_vorbis *f, unsigned int loc) -{ - #ifndef STB_VORBIS_NO_PUSHDATA_API - if (f->push_mode) return 0; - #endif - f->eof = 0; - if (USE_MEMORY(f)) { - if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) { - f->stream = f->stream_end; - f->eof = 1; - return 0; - } else { - f->stream = f->stream_start + loc; - return 1; - } - } - #ifndef STB_VORBIS_NO_STDIO - if (loc + f->f_start < loc || loc >= 0x80000000) { - loc = 0x7fffffff; - f->eof = 1; - } else { - loc += f->f_start; - } - if (!fseek(f->f, loc, SEEK_SET)) - return 1; - f->eof = 1; - fseek(f->f, f->f_start, SEEK_END); - return 0; - #endif -} - - -static uint8 ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 }; - -static int capture_pattern(vorb *f) -{ - if (0x4f != get8(f)) return FALSE; - if (0x67 != get8(f)) return FALSE; - if (0x67 != get8(f)) return FALSE; - if (0x53 != get8(f)) return FALSE; - return TRUE; -} - -#define PAGEFLAG_continued_packet 1 -#define PAGEFLAG_first_page 2 -#define PAGEFLAG_last_page 4 - -static int start_page_no_capturepattern(vorb *f) -{ - uint32 loc0,loc1,n; - if (f->first_decode && !IS_PUSH_MODE(f)) { - f->p_first.page_start = stb_vorbis_get_file_offset(f) - 4; - } - // stream structure version - if (0 != get8(f)) return error(f, VORBIS_invalid_stream_structure_version); - // header flag - f->page_flag = get8(f); - // absolute granule position - loc0 = get32(f); - loc1 = get32(f); - // @TODO: validate loc0,loc1 as valid positions? - // stream serial number -- vorbis doesn't interleave, so discard - get32(f); - //if (f->serial != get32(f)) return error(f, VORBIS_incorrect_stream_serial_number); - // page sequence number - n = get32(f); - f->last_page = n; - // CRC32 - get32(f); - // page_segments - f->segment_count = get8(f); - if (!getn(f, f->segments, f->segment_count)) - return error(f, VORBIS_unexpected_eof); - // assume we _don't_ know any the sample position of any segments - f->end_seg_with_known_loc = -2; - if (loc0 != ~0U || loc1 != ~0U) { - int i; - // determine which packet is the last one that will complete - for (i=f->segment_count-1; i >= 0; --i) - if (f->segments[i] < 255) - break; - // 'i' is now the index of the _last_ segment of a packet that ends - if (i >= 0) { - f->end_seg_with_known_loc = i; - f->known_loc_for_packet = loc0; - } - } - if (f->first_decode) { - int i,len; - len = 0; - for (i=0; i < f->segment_count; ++i) - len += f->segments[i]; - len += 27 + f->segment_count; - f->p_first.page_end = f->p_first.page_start + len; - f->p_first.last_decoded_sample = loc0; - } - f->next_seg = 0; - return TRUE; -} - -static int start_page(vorb *f) -{ - if (!capture_pattern(f)) return error(f, VORBIS_missing_capture_pattern); - return start_page_no_capturepattern(f); -} - -static int start_packet(vorb *f) -{ - while (f->next_seg == -1) { - if (!start_page(f)) return FALSE; - if (f->page_flag & PAGEFLAG_continued_packet) - return error(f, VORBIS_continued_packet_flag_invalid); - } - f->last_seg = FALSE; - f->valid_bits = 0; - f->packet_bytes = 0; - f->bytes_in_seg = 0; - // f->next_seg is now valid - return TRUE; -} - -static int maybe_start_packet(vorb *f) -{ - if (f->next_seg == -1) { - int x = get8(f); - if (f->eof) return FALSE; // EOF at page boundary is not an error! - if (0x4f != x ) return error(f, VORBIS_missing_capture_pattern); - if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); - if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); - if (0x53 != get8(f)) return error(f, VORBIS_missing_capture_pattern); - if (!start_page_no_capturepattern(f)) return FALSE; - if (f->page_flag & PAGEFLAG_continued_packet) { - // set up enough state that we can read this packet if we want, - // e.g. during recovery - f->last_seg = FALSE; - f->bytes_in_seg = 0; - return error(f, VORBIS_continued_packet_flag_invalid); - } - } - return start_packet(f); -} - -static int next_segment(vorb *f) -{ - int len; - if (f->last_seg) return 0; - if (f->next_seg == -1) { - f->last_seg_which = f->segment_count-1; // in case start_page fails - if (!start_page(f)) { f->last_seg = 1; return 0; } - if (!(f->page_flag & PAGEFLAG_continued_packet)) return error(f, VORBIS_continued_packet_flag_invalid); - } - len = f->segments[f->next_seg++]; - if (len < 255) { - f->last_seg = TRUE; - f->last_seg_which = f->next_seg-1; - } - if (f->next_seg >= f->segment_count) - f->next_seg = -1; - assert(f->bytes_in_seg == 0); - f->bytes_in_seg = len; - return len; -} - -#define EOP (-1) -#define INVALID_BITS (-1) - -static int get8_packet_raw(vorb *f) -{ - if (!f->bytes_in_seg) { // CLANG! - if (f->last_seg) return EOP; - else if (!next_segment(f)) return EOP; - } - assert(f->bytes_in_seg > 0); - --f->bytes_in_seg; - ++f->packet_bytes; - return get8(f); -} - -static int get8_packet(vorb *f) -{ - int x = get8_packet_raw(f); - f->valid_bits = 0; - return x; -} - -static int get32_packet(vorb *f) -{ - uint32 x; - x = get8_packet(f); - x += get8_packet(f) << 8; - x += get8_packet(f) << 16; - x += (uint32) get8_packet(f) << 24; - return x; -} - -static void flush_packet(vorb *f) -{ - while (get8_packet_raw(f) != EOP); -} - -// @OPTIMIZE: this is the secondary bit decoder, so it's probably not as important -// as the huffman decoder? -static uint32 get_bits(vorb *f, int n) -{ - uint32 z; - - if (f->valid_bits < 0) return 0; - if (f->valid_bits < n) { - if (n > 24) { - // the accumulator technique below would not work correctly in this case - z = get_bits(f, 24); - z += get_bits(f, n-24) << 24; - return z; - } - if (f->valid_bits == 0) f->acc = 0; - while (f->valid_bits < n) { - int z = get8_packet_raw(f); - if (z == EOP) { - f->valid_bits = INVALID_BITS; - return 0; - } - f->acc += z << f->valid_bits; - f->valid_bits += 8; - } - } - - assert(f->valid_bits >= n); - z = f->acc & ((1 << n)-1); - f->acc >>= n; - f->valid_bits -= n; - return z; -} - -// @OPTIMIZE: primary accumulator for huffman -// expand the buffer to as many bits as possible without reading off end of packet -// it might be nice to allow f->valid_bits and f->acc to be stored in registers, -// e.g. cache them locally and decode locally -static __forceinline void prep_huffman(vorb *f) -{ - if (f->valid_bits <= 24) { - if (f->valid_bits == 0) f->acc = 0; - do { - int z; - if (f->last_seg && !f->bytes_in_seg) return; - z = get8_packet_raw(f); - if (z == EOP) return; - f->acc += (unsigned) z << f->valid_bits; - f->valid_bits += 8; - } while (f->valid_bits <= 24); - } -} - -enum -{ - VORBIS_packet_id = 1, - VORBIS_packet_comment = 3, - VORBIS_packet_setup = 5 -}; - -static int codebook_decode_scalar_raw(vorb *f, Codebook *c) -{ - int i; - prep_huffman(f); - - if (c->codewords == NULL && c->sorted_codewords == NULL) - return -1; - - // cases to use binary search: sorted_codewords && !c->codewords - // sorted_codewords && c->entries > 8 - if (c->entries > 8 ? c->sorted_codewords!=NULL : !c->codewords) { - // binary search - uint32 code = bit_reverse(f->acc); - int x=0, n=c->sorted_entries, len; - - while (n > 1) { - // invariant: sc[x] <= code < sc[x+n] - int m = x + (n >> 1); - if (c->sorted_codewords[m] <= code) { - x = m; - n -= (n>>1); - } else { - n >>= 1; - } - } - // x is now the sorted index - if (!c->sparse) x = c->sorted_values[x]; - // x is now sorted index if sparse, or symbol otherwise - len = c->codeword_lengths[x]; - if (f->valid_bits >= len) { - f->acc >>= len; - f->valid_bits -= len; - return x; - } - - f->valid_bits = 0; - return -1; - } - - // if small, linear search - assert(!c->sparse); - for (i=0; i < c->entries; ++i) { - if (c->codeword_lengths[i] == NO_CODE) continue; - if (c->codewords[i] == (f->acc & ((1 << c->codeword_lengths[i])-1))) { - if (f->valid_bits >= c->codeword_lengths[i]) { - f->acc >>= c->codeword_lengths[i]; - f->valid_bits -= c->codeword_lengths[i]; - return i; - } - f->valid_bits = 0; - return -1; - } - } - - error(f, VORBIS_invalid_stream); - f->valid_bits = 0; - return -1; -} - -#ifndef STB_VORBIS_NO_INLINE_DECODE - -#define DECODE_RAW(var, f,c) \ - if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) \ - prep_huffman(f); \ - var = f->acc & FAST_HUFFMAN_TABLE_MASK; \ - var = c->fast_huffman[var]; \ - if (var >= 0) { \ - int n = c->codeword_lengths[var]; \ - f->acc >>= n; \ - f->valid_bits -= n; \ - if (f->valid_bits < 0) { f->valid_bits = 0; var = -1; } \ - } else { \ - var = codebook_decode_scalar_raw(f,c); \ - } - -#else - -static int codebook_decode_scalar(vorb *f, Codebook *c) -{ - int i; - if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) - prep_huffman(f); - // fast huffman table lookup - i = f->acc & FAST_HUFFMAN_TABLE_MASK; - i = c->fast_huffman[i]; - if (i >= 0) { - f->acc >>= c->codeword_lengths[i]; - f->valid_bits -= c->codeword_lengths[i]; - if (f->valid_bits < 0) { f->valid_bits = 0; return -1; } - return i; - } - return codebook_decode_scalar_raw(f,c); -} - -#define DECODE_RAW(var,f,c) var = codebook_decode_scalar(f,c); - -#endif - -#define DECODE(var,f,c) \ - DECODE_RAW(var,f,c) \ - if (c->sparse) var = c->sorted_values[var]; - -#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - #define DECODE_VQ(var,f,c) DECODE_RAW(var,f,c) -#else - #define DECODE_VQ(var,f,c) DECODE(var,f,c) -#endif - - - - - - -// CODEBOOK_ELEMENT_FAST is an optimization for the CODEBOOK_FLOATS case -// where we avoid one addition -#define CODEBOOK_ELEMENT(c,off) (c->multiplicands[off]) -#define CODEBOOK_ELEMENT_FAST(c,off) (c->multiplicands[off]) -#define CODEBOOK_ELEMENT_BASE(c) (0) - -static int codebook_decode_start(vorb *f, Codebook *c) -{ - int z = -1; - - // type 0 is only legal in a scalar context - if (c->lookup_type == 0) - error(f, VORBIS_invalid_stream); - else { - DECODE_VQ(z,f,c); - if (c->sparse) assert(z < c->sorted_entries); - if (z < 0) { // check for EOP - if (!f->bytes_in_seg) - if (f->last_seg) - return z; - error(f, VORBIS_invalid_stream); - } - } - return z; -} - -static int codebook_decode(vorb *f, Codebook *c, float *output, int len) -{ - int i,z = codebook_decode_start(f,c); - if (z < 0) return FALSE; - if (len > c->dimensions) len = c->dimensions; - -#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - float last = CODEBOOK_ELEMENT_BASE(c); - int div = 1; - for (i=0; i < len; ++i) { - int off = (z / div) % c->lookup_values; - float val = CODEBOOK_ELEMENT_FAST(c,off) + last; - output[i] += val; - if (c->sequence_p) last = val + c->minimum_value; - div *= c->lookup_values; - } - return TRUE; - } -#endif - - z *= c->dimensions; - if (c->sequence_p) { - float last = CODEBOOK_ELEMENT_BASE(c); - for (i=0; i < len; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - output[i] += val; - last = val + c->minimum_value; - } - } else { - float last = CODEBOOK_ELEMENT_BASE(c); - for (i=0; i < len; ++i) { - output[i] += CODEBOOK_ELEMENT_FAST(c,z+i) + last; - } - } - - return TRUE; -} - -static int codebook_decode_step(vorb *f, Codebook *c, float *output, int len, int step) -{ - int i,z = codebook_decode_start(f,c); - float last = CODEBOOK_ELEMENT_BASE(c); - if (z < 0) return FALSE; - if (len > c->dimensions) len = c->dimensions; - -#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - int div = 1; - for (i=0; i < len; ++i) { - int off = (z / div) % c->lookup_values; - float val = CODEBOOK_ELEMENT_FAST(c,off) + last; - output[i*step] += val; - if (c->sequence_p) last = val; - div *= c->lookup_values; - } - return TRUE; - } -#endif - - z *= c->dimensions; - for (i=0; i < len; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - output[i*step] += val; - if (c->sequence_p) last = val; - } - - return TRUE; -} - -static int codebook_decode_deinterleave_repeat(vorb *f, Codebook *c, float **outputs, int ch, int *c_inter_p, int *p_inter_p, int len, int total_decode) -{ - int c_inter = *c_inter_p; - int p_inter = *p_inter_p; - int i,z, effective = c->dimensions; - - // type 0 is only legal in a scalar context - if (c->lookup_type == 0) return error(f, VORBIS_invalid_stream); - - while (total_decode > 0) { - float last = CODEBOOK_ELEMENT_BASE(c); - DECODE_VQ(z,f,c); - #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - assert(!c->sparse || z < c->sorted_entries); - #endif - if (z < 0) { - if (!f->bytes_in_seg) - if (f->last_seg) return FALSE; - return error(f, VORBIS_invalid_stream); - } - - // if this will take us off the end of the buffers, stop short! - // we check by computing the length of the virtual interleaved - // buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter), - // and the length we'll be using (effective) - if (c_inter + p_inter*ch + effective > len * ch) { - effective = len*ch - (p_inter*ch - c_inter); - } - - #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - int div = 1; - for (i=0; i < effective; ++i) { - int off = (z / div) % c->lookup_values; - float val = CODEBOOK_ELEMENT_FAST(c,off) + last; - if (outputs[c_inter]) - outputs[c_inter][p_inter] += val; - if (++c_inter == ch) { c_inter = 0; ++p_inter; } - if (c->sequence_p) last = val; - div *= c->lookup_values; - } - } else - #endif - { - z *= c->dimensions; - if (c->sequence_p) { - for (i=0; i < effective; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - if (outputs[c_inter]) - outputs[c_inter][p_inter] += val; - if (++c_inter == ch) { c_inter = 0; ++p_inter; } - last = val; - } - } else { - for (i=0; i < effective; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - if (outputs[c_inter]) - outputs[c_inter][p_inter] += val; - if (++c_inter == ch) { c_inter = 0; ++p_inter; } - } - } - } - - total_decode -= effective; - } - *c_inter_p = c_inter; - *p_inter_p = p_inter; - return TRUE; -} - -static int predict_point(int x, int x0, int x1, int y0, int y1) -{ - int dy = y1 - y0; - int adx = x1 - x0; - // @OPTIMIZE: force int division to round in the right direction... is this necessary on x86? - int err = abs(dy) * (x - x0); - int off = err / adx; - return dy < 0 ? y0 - off : y0 + off; -} - -// the following table is block-copied from the specification -static float inverse_db_table[256] = -{ - 1.0649863e-07f, 1.1341951e-07f, 1.2079015e-07f, 1.2863978e-07f, - 1.3699951e-07f, 1.4590251e-07f, 1.5538408e-07f, 1.6548181e-07f, - 1.7623575e-07f, 1.8768855e-07f, 1.9988561e-07f, 2.1287530e-07f, - 2.2670913e-07f, 2.4144197e-07f, 2.5713223e-07f, 2.7384213e-07f, - 2.9163793e-07f, 3.1059021e-07f, 3.3077411e-07f, 3.5226968e-07f, - 3.7516214e-07f, 3.9954229e-07f, 4.2550680e-07f, 4.5315863e-07f, - 4.8260743e-07f, 5.1396998e-07f, 5.4737065e-07f, 5.8294187e-07f, - 6.2082472e-07f, 6.6116941e-07f, 7.0413592e-07f, 7.4989464e-07f, - 7.9862701e-07f, 8.5052630e-07f, 9.0579828e-07f, 9.6466216e-07f, - 1.0273513e-06f, 1.0941144e-06f, 1.1652161e-06f, 1.2409384e-06f, - 1.3215816e-06f, 1.4074654e-06f, 1.4989305e-06f, 1.5963394e-06f, - 1.7000785e-06f, 1.8105592e-06f, 1.9282195e-06f, 2.0535261e-06f, - 2.1869758e-06f, 2.3290978e-06f, 2.4804557e-06f, 2.6416497e-06f, - 2.8133190e-06f, 2.9961443e-06f, 3.1908506e-06f, 3.3982101e-06f, - 3.6190449e-06f, 3.8542308e-06f, 4.1047004e-06f, 4.3714470e-06f, - 4.6555282e-06f, 4.9580707e-06f, 5.2802740e-06f, 5.6234160e-06f, - 5.9888572e-06f, 6.3780469e-06f, 6.7925283e-06f, 7.2339451e-06f, - 7.7040476e-06f, 8.2047000e-06f, 8.7378876e-06f, 9.3057248e-06f, - 9.9104632e-06f, 1.0554501e-05f, 1.1240392e-05f, 1.1970856e-05f, - 1.2748789e-05f, 1.3577278e-05f, 1.4459606e-05f, 1.5399272e-05f, - 1.6400004e-05f, 1.7465768e-05f, 1.8600792e-05f, 1.9809576e-05f, - 2.1096914e-05f, 2.2467911e-05f, 2.3928002e-05f, 2.5482978e-05f, - 2.7139006e-05f, 2.8902651e-05f, 3.0780908e-05f, 3.2781225e-05f, - 3.4911534e-05f, 3.7180282e-05f, 3.9596466e-05f, 4.2169667e-05f, - 4.4910090e-05f, 4.7828601e-05f, 5.0936773e-05f, 5.4246931e-05f, - 5.7772202e-05f, 6.1526565e-05f, 6.5524908e-05f, 6.9783085e-05f, - 7.4317983e-05f, 7.9147585e-05f, 8.4291040e-05f, 8.9768747e-05f, - 9.5602426e-05f, 0.00010181521f, 0.00010843174f, 0.00011547824f, - 0.00012298267f, 0.00013097477f, 0.00013948625f, 0.00014855085f, - 0.00015820453f, 0.00016848555f, 0.00017943469f, 0.00019109536f, - 0.00020351382f, 0.00021673929f, 0.00023082423f, 0.00024582449f, - 0.00026179955f, 0.00027881276f, 0.00029693158f, 0.00031622787f, - 0.00033677814f, 0.00035866388f, 0.00038197188f, 0.00040679456f, - 0.00043323036f, 0.00046138411f, 0.00049136745f, 0.00052329927f, - 0.00055730621f, 0.00059352311f, 0.00063209358f, 0.00067317058f, - 0.00071691700f, 0.00076350630f, 0.00081312324f, 0.00086596457f, - 0.00092223983f, 0.00098217216f, 0.0010459992f, 0.0011139742f, - 0.0011863665f, 0.0012634633f, 0.0013455702f, 0.0014330129f, - 0.0015261382f, 0.0016253153f, 0.0017309374f, 0.0018434235f, - 0.0019632195f, 0.0020908006f, 0.0022266726f, 0.0023713743f, - 0.0025254795f, 0.0026895994f, 0.0028643847f, 0.0030505286f, - 0.0032487691f, 0.0034598925f, 0.0036847358f, 0.0039241906f, - 0.0041792066f, 0.0044507950f, 0.0047400328f, 0.0050480668f, - 0.0053761186f, 0.0057254891f, 0.0060975636f, 0.0064938176f, - 0.0069158225f, 0.0073652516f, 0.0078438871f, 0.0083536271f, - 0.0088964928f, 0.009474637f, 0.010090352f, 0.010746080f, - 0.011444421f, 0.012188144f, 0.012980198f, 0.013823725f, - 0.014722068f, 0.015678791f, 0.016697687f, 0.017782797f, - 0.018938423f, 0.020169149f, 0.021479854f, 0.022875735f, - 0.024362330f, 0.025945531f, 0.027631618f, 0.029427276f, - 0.031339626f, 0.033376252f, 0.035545228f, 0.037855157f, - 0.040315199f, 0.042935108f, 0.045725273f, 0.048696758f, - 0.051861348f, 0.055231591f, 0.058820850f, 0.062643361f, - 0.066714279f, 0.071049749f, 0.075666962f, 0.080584227f, - 0.085821044f, 0.091398179f, 0.097337747f, 0.10366330f, - 0.11039993f, 0.11757434f, 0.12521498f, 0.13335215f, - 0.14201813f, 0.15124727f, 0.16107617f, 0.17154380f, - 0.18269168f, 0.19456402f, 0.20720788f, 0.22067342f, - 0.23501402f, 0.25028656f, 0.26655159f, 0.28387361f, - 0.30232132f, 0.32196786f, 0.34289114f, 0.36517414f, - 0.38890521f, 0.41417847f, 0.44109412f, 0.46975890f, - 0.50028648f, 0.53279791f, 0.56742212f, 0.60429640f, - 0.64356699f, 0.68538959f, 0.72993007f, 0.77736504f, - 0.82788260f, 0.88168307f, 0.9389798f, 1.0f -}; - - -// @OPTIMIZE: if you want to replace this bresenham line-drawing routine, -// note that you must produce bit-identical output to decode correctly; -// this specific sequence of operations is specified in the spec (it's -// drawing integer-quantized frequency-space lines that the encoder -// expects to be exactly the same) -// ... also, isn't the whole point of Bresenham's algorithm to NOT -// have to divide in the setup? sigh. -#ifndef STB_VORBIS_NO_DEFER_FLOOR -#define LINE_OP(a,b) a *= b -#else -#define LINE_OP(a,b) a = b -#endif - -#ifdef STB_VORBIS_DIVIDE_TABLE -#define DIVTAB_NUMER 32 -#define DIVTAB_DENOM 64 -int8 integer_divide_table[DIVTAB_NUMER][DIVTAB_DENOM]; // 2KB -#endif - -static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y1, int n) -{ - int dy = y1 - y0; - int adx = x1 - x0; - int ady = abs(dy); - int base; - int x=x0,y=y0; - int err = 0; - int sy; - -#ifdef STB_VORBIS_DIVIDE_TABLE - if (adx < DIVTAB_DENOM && ady < DIVTAB_NUMER) { - if (dy < 0) { - base = -integer_divide_table[ady][adx]; - sy = base-1; - } else { - base = integer_divide_table[ady][adx]; - sy = base+1; - } - } else { - base = dy / adx; - if (dy < 0) - sy = base - 1; - else - sy = base+1; - } -#else - base = dy / adx; - if (dy < 0) - sy = base - 1; - else - sy = base+1; -#endif - ady -= abs(base) * adx; - if (x1 > n) x1 = n; - if (x < x1) { - LINE_OP(output[x], inverse_db_table[y&255]); - for (++x; x < x1; ++x) { - err += ady; - if (err >= adx) { - err -= adx; - y += sy; - } else - y += base; - LINE_OP(output[x], inverse_db_table[y&255]); - } - } -} - -static int residue_decode(vorb *f, Codebook *book, float *target, int offset, int n, int rtype) -{ - int k; - if (rtype == 0) { - int step = n / book->dimensions; - for (k=0; k < step; ++k) - if (!codebook_decode_step(f, book, target+offset+k, n-offset-k, step)) - return FALSE; - } else { - for (k=0; k < n; ) { - if (!codebook_decode(f, book, target+offset, n-k)) - return FALSE; - k += book->dimensions; - offset += book->dimensions; - } - } - return TRUE; -} - -// n is 1/2 of the blocksize -- -// specification: "Correct per-vector decode length is [n]/2" -static void decode_residue(vorb *f, float *residue_buffers[], int ch, int n, int rn, uint8 *do_not_decode) -{ - int i,j,pass; - Residue *r = f->residue_config + rn; - int rtype = f->residue_types[rn]; - int c = r->classbook; - int classwords = f->codebooks[c].dimensions; - unsigned int actual_size = rtype == 2 ? n*2 : n; - unsigned int limit_r_begin = (r->begin < actual_size ? r->begin : actual_size); - unsigned int limit_r_end = (r->end < actual_size ? r->end : actual_size); - int n_read = limit_r_end - limit_r_begin; - int part_read = n_read / r->part_size; - int temp_alloc_point = temp_alloc_save(f); - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - uint8 ***part_classdata = (uint8 ***) temp_block_array(f,f->channels, part_read * sizeof(**part_classdata)); - #else - int **classifications = (int **) temp_block_array(f,f->channels, part_read * sizeof(**classifications)); - #endif - - CHECK(f); - - for (i=0; i < ch; ++i) - if (!do_not_decode[i]) - memset(residue_buffers[i], 0, sizeof(float) * n); - - if (rtype == 2 && ch != 1) { - for (j=0; j < ch; ++j) - if (!do_not_decode[j]) - break; - if (j == ch) - goto done; - - for (pass=0; pass < 8; ++pass) { - int pcount = 0, class_set = 0; - if (ch == 2) { - while (pcount < part_read) { - int z = r->begin + pcount*r->part_size; - int c_inter = (z & 1), p_inter = z>>1; - if (pass == 0) { - Codebook *c = f->codebooks+r->classbook; - int q; - DECODE(q,f,c); - if (q == EOP) goto done; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - part_classdata[0][class_set] = r->classdata[q]; - #else - for (i=classwords-1; i >= 0; --i) { - classifications[0][i+pcount] = q % r->classifications; - q /= r->classifications; - } - #endif - } - for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { - int z = r->begin + pcount*r->part_size; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - int c = part_classdata[0][class_set][i]; - #else - int c = classifications[0][pcount]; - #endif - int b = r->residue_books[c][pass]; - if (b >= 0) { - Codebook *book = f->codebooks + b; - #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) - goto done; - #else - // saves 1% - if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) - goto done; - #endif - } else { - z += r->part_size; - c_inter = z & 1; - p_inter = z >> 1; - } - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - ++class_set; - #endif - } - } else if (ch > 2) { - while (pcount < part_read) { - int z = r->begin + pcount*r->part_size; - int c_inter = z % ch, p_inter = z/ch; - if (pass == 0) { - Codebook *c = f->codebooks+r->classbook; - int q; - DECODE(q,f,c); - if (q == EOP) goto done; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - part_classdata[0][class_set] = r->classdata[q]; - #else - for (i=classwords-1; i >= 0; --i) { - classifications[0][i+pcount] = q % r->classifications; - q /= r->classifications; - } - #endif - } - for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { - int z = r->begin + pcount*r->part_size; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - int c = part_classdata[0][class_set][i]; - #else - int c = classifications[0][pcount]; - #endif - int b = r->residue_books[c][pass]; - if (b >= 0) { - Codebook *book = f->codebooks + b; - if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) - goto done; - } else { - z += r->part_size; - c_inter = z % ch; - p_inter = z / ch; - } - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - ++class_set; - #endif - } - } - } - goto done; - } - CHECK(f); - - for (pass=0; pass < 8; ++pass) { - int pcount = 0, class_set=0; - while (pcount < part_read) { - if (pass == 0) { - for (j=0; j < ch; ++j) { - if (!do_not_decode[j]) { - Codebook *c = f->codebooks+r->classbook; - int temp; - DECODE(temp,f,c); - if (temp == EOP) goto done; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - part_classdata[j][class_set] = r->classdata[temp]; - #else - for (i=classwords-1; i >= 0; --i) { - classifications[j][i+pcount] = temp % r->classifications; - temp /= r->classifications; - } - #endif - } - } - } - for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { - for (j=0; j < ch; ++j) { - if (!do_not_decode[j]) { - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - int c = part_classdata[j][class_set][i]; - #else - int c = classifications[j][pcount]; - #endif - int b = r->residue_books[c][pass]; - if (b >= 0) { - float *target = residue_buffers[j]; - int offset = r->begin + pcount * r->part_size; - int n = r->part_size; - Codebook *book = f->codebooks + b; - if (!residue_decode(f, book, target, offset, n, rtype)) - goto done; - } - } - } - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - ++class_set; - #endif - } - } - done: - CHECK(f); - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - temp_free(f,part_classdata); - #else - temp_free(f,classifications); - #endif - temp_alloc_restore(f,temp_alloc_point); -} - - -#if 0 -// slow way for debugging -void inverse_mdct_slow(float *buffer, int n) -{ - int i,j; - int n2 = n >> 1; - float *x = (float *) malloc(sizeof(*x) * n2); - memcpy(x, buffer, sizeof(*x) * n2); - for (i=0; i < n; ++i) { - float acc = 0; - for (j=0; j < n2; ++j) - // formula from paper: - //acc += n/4.0f * x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); - // formula from wikipedia - //acc += 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); - // these are equivalent, except the formula from the paper inverts the multiplier! - // however, what actually works is NO MULTIPLIER!?! - //acc += 64 * 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); - acc += x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); - buffer[i] = acc; - } - free(x); -} -#elif 0 -// same as above, but just barely able to run in real time on modern machines -void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) -{ - float mcos[16384]; - int i,j; - int n2 = n >> 1, nmask = (n << 2) -1; - float *x = (float *) malloc(sizeof(*x) * n2); - memcpy(x, buffer, sizeof(*x) * n2); - for (i=0; i < 4*n; ++i) - mcos[i] = (float) cos(M_PI / 2 * i / n); - - for (i=0; i < n; ++i) { - float acc = 0; - for (j=0; j < n2; ++j) - acc += x[j] * mcos[(2 * i + 1 + n2)*(2*j+1) & nmask]; - buffer[i] = acc; - } - free(x); -} -#elif 0 -// transform to use a slow dct-iv; this is STILL basically trivial, -// but only requires half as many ops -void dct_iv_slow(float *buffer, int n) -{ - float mcos[16384]; - float x[2048]; - int i,j; - int n2 = n >> 1, nmask = (n << 3) - 1; - memcpy(x, buffer, sizeof(*x) * n); - for (i=0; i < 8*n; ++i) - mcos[i] = (float) cos(M_PI / 4 * i / n); - for (i=0; i < n; ++i) { - float acc = 0; - for (j=0; j < n; ++j) - acc += x[j] * mcos[((2 * i + 1)*(2*j+1)) & nmask]; - buffer[i] = acc; - } -} - -void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) -{ - int i, n4 = n >> 2, n2 = n >> 1, n3_4 = n - n4; - float temp[4096]; - - memcpy(temp, buffer, n2 * sizeof(float)); - dct_iv_slow(temp, n2); // returns -c'-d, a-b' - - for (i=0; i < n4 ; ++i) buffer[i] = temp[i+n4]; // a-b' - for ( ; i < n3_4; ++i) buffer[i] = -temp[n3_4 - i - 1]; // b-a', c+d' - for ( ; i < n ; ++i) buffer[i] = -temp[i - n3_4]; // c'+d -} -#endif - -#ifndef LIBVORBIS_MDCT -#define LIBVORBIS_MDCT 0 -#endif - -#if LIBVORBIS_MDCT -// directly call the vorbis MDCT using an interface documented -// by Jeff Roberts... useful for performance comparison -typedef struct -{ - int n; - int log2n; - - float *trig; - int *bitrev; - - float scale; -} mdct_lookup; - -extern void mdct_init(mdct_lookup *lookup, int n); -extern void mdct_clear(mdct_lookup *l); -extern void mdct_backward(mdct_lookup *init, float *in, float *out); - -mdct_lookup M1,M2; - -void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) -{ - mdct_lookup *M; - if (M1.n == n) M = &M1; - else if (M2.n == n) M = &M2; - else if (M1.n == 0) { mdct_init(&M1, n); M = &M1; } - else { - if (M2.n) __asm int 3; - mdct_init(&M2, n); - M = &M2; - } - - mdct_backward(M, buffer, buffer); -} -#endif - - -// the following were split out into separate functions while optimizing; -// they could be pushed back up but eh. __forceinline showed no change; -// they're probably already being inlined. -static void imdct_step3_iter0_loop(int n, float *e, int i_off, int k_off, float *A) -{ - float *ee0 = e + i_off; - float *ee2 = ee0 + k_off; - int i; - - assert((n & 3) == 0); - for (i=(n>>2); i > 0; --i) { - float k00_20, k01_21; - k00_20 = ee0[ 0] - ee2[ 0]; - k01_21 = ee0[-1] - ee2[-1]; - ee0[ 0] += ee2[ 0];//ee0[ 0] = ee0[ 0] + ee2[ 0]; - ee0[-1] += ee2[-1];//ee0[-1] = ee0[-1] + ee2[-1]; - ee2[ 0] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-1] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - - k00_20 = ee0[-2] - ee2[-2]; - k01_21 = ee0[-3] - ee2[-3]; - ee0[-2] += ee2[-2];//ee0[-2] = ee0[-2] + ee2[-2]; - ee0[-3] += ee2[-3];//ee0[-3] = ee0[-3] + ee2[-3]; - ee2[-2] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-3] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - - k00_20 = ee0[-4] - ee2[-4]; - k01_21 = ee0[-5] - ee2[-5]; - ee0[-4] += ee2[-4];//ee0[-4] = ee0[-4] + ee2[-4]; - ee0[-5] += ee2[-5];//ee0[-5] = ee0[-5] + ee2[-5]; - ee2[-4] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-5] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - - k00_20 = ee0[-6] - ee2[-6]; - k01_21 = ee0[-7] - ee2[-7]; - ee0[-6] += ee2[-6];//ee0[-6] = ee0[-6] + ee2[-6]; - ee0[-7] += ee2[-7];//ee0[-7] = ee0[-7] + ee2[-7]; - ee2[-6] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-7] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - ee0 -= 8; - ee2 -= 8; - } -} - -static void imdct_step3_inner_r_loop(int lim, float *e, int d0, int k_off, float *A, int k1) -{ - int i; - float k00_20, k01_21; - - float *e0 = e + d0; - float *e2 = e0 + k_off; - - for (i=lim >> 2; i > 0; --i) { - k00_20 = e0[-0] - e2[-0]; - k01_21 = e0[-1] - e2[-1]; - e0[-0] += e2[-0];//e0[-0] = e0[-0] + e2[-0]; - e0[-1] += e2[-1];//e0[-1] = e0[-1] + e2[-1]; - e2[-0] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-1] = (k01_21)*A[0] + (k00_20) * A[1]; - - A += k1; - - k00_20 = e0[-2] - e2[-2]; - k01_21 = e0[-3] - e2[-3]; - e0[-2] += e2[-2];//e0[-2] = e0[-2] + e2[-2]; - e0[-3] += e2[-3];//e0[-3] = e0[-3] + e2[-3]; - e2[-2] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-3] = (k01_21)*A[0] + (k00_20) * A[1]; - - A += k1; - - k00_20 = e0[-4] - e2[-4]; - k01_21 = e0[-5] - e2[-5]; - e0[-4] += e2[-4];//e0[-4] = e0[-4] + e2[-4]; - e0[-5] += e2[-5];//e0[-5] = e0[-5] + e2[-5]; - e2[-4] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-5] = (k01_21)*A[0] + (k00_20) * A[1]; - - A += k1; - - k00_20 = e0[-6] - e2[-6]; - k01_21 = e0[-7] - e2[-7]; - e0[-6] += e2[-6];//e0[-6] = e0[-6] + e2[-6]; - e0[-7] += e2[-7];//e0[-7] = e0[-7] + e2[-7]; - e2[-6] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-7] = (k01_21)*A[0] + (k00_20) * A[1]; - - e0 -= 8; - e2 -= 8; - - A += k1; - } -} - -static void imdct_step3_inner_s_loop(int n, float *e, int i_off, int k_off, float *A, int a_off, int k0) -{ - int i; - float A0 = A[0]; - float A1 = A[0+1]; - float A2 = A[0+a_off]; - float A3 = A[0+a_off+1]; - float A4 = A[0+a_off*2+0]; - float A5 = A[0+a_off*2+1]; - float A6 = A[0+a_off*3+0]; - float A7 = A[0+a_off*3+1]; - - float k00,k11; - - float *ee0 = e +i_off; - float *ee2 = ee0+k_off; - - for (i=n; i > 0; --i) { - k00 = ee0[ 0] - ee2[ 0]; - k11 = ee0[-1] - ee2[-1]; - ee0[ 0] = ee0[ 0] + ee2[ 0]; - ee0[-1] = ee0[-1] + ee2[-1]; - ee2[ 0] = (k00) * A0 - (k11) * A1; - ee2[-1] = (k11) * A0 + (k00) * A1; - - k00 = ee0[-2] - ee2[-2]; - k11 = ee0[-3] - ee2[-3]; - ee0[-2] = ee0[-2] + ee2[-2]; - ee0[-3] = ee0[-3] + ee2[-3]; - ee2[-2] = (k00) * A2 - (k11) * A3; - ee2[-3] = (k11) * A2 + (k00) * A3; - - k00 = ee0[-4] - ee2[-4]; - k11 = ee0[-5] - ee2[-5]; - ee0[-4] = ee0[-4] + ee2[-4]; - ee0[-5] = ee0[-5] + ee2[-5]; - ee2[-4] = (k00) * A4 - (k11) * A5; - ee2[-5] = (k11) * A4 + (k00) * A5; - - k00 = ee0[-6] - ee2[-6]; - k11 = ee0[-7] - ee2[-7]; - ee0[-6] = ee0[-6] + ee2[-6]; - ee0[-7] = ee0[-7] + ee2[-7]; - ee2[-6] = (k00) * A6 - (k11) * A7; - ee2[-7] = (k11) * A6 + (k00) * A7; - - ee0 -= k0; - ee2 -= k0; - } -} - -static __forceinline void iter_54(float *z) -{ - float k00,k11,k22,k33; - float y0,y1,y2,y3; - - k00 = z[ 0] - z[-4]; - y0 = z[ 0] + z[-4]; - y2 = z[-2] + z[-6]; - k22 = z[-2] - z[-6]; - - z[-0] = y0 + y2; // z0 + z4 + z2 + z6 - z[-2] = y0 - y2; // z0 + z4 - z2 - z6 - - // done with y0,y2 - - k33 = z[-3] - z[-7]; - - z[-4] = k00 + k33; // z0 - z4 + z3 - z7 - z[-6] = k00 - k33; // z0 - z4 - z3 + z7 - - // done with k33 - - k11 = z[-1] - z[-5]; - y1 = z[-1] + z[-5]; - y3 = z[-3] + z[-7]; - - z[-1] = y1 + y3; // z1 + z5 + z3 + z7 - z[-3] = y1 - y3; // z1 + z5 - z3 - z7 - z[-5] = k11 - k22; // z1 - z5 + z2 - z6 - z[-7] = k11 + k22; // z1 - z5 - z2 + z6 -} - -static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A, int base_n) -{ - int a_off = base_n >> 3; - float A2 = A[0+a_off]; - float *z = e + i_off; - float *base = z - 16 * n; - - while (z > base) { - float k00,k11; - - k00 = z[-0] - z[-8]; - k11 = z[-1] - z[-9]; - z[-0] = z[-0] + z[-8]; - z[-1] = z[-1] + z[-9]; - z[-8] = k00; - z[-9] = k11 ; - - k00 = z[ -2] - z[-10]; - k11 = z[ -3] - z[-11]; - z[ -2] = z[ -2] + z[-10]; - z[ -3] = z[ -3] + z[-11]; - z[-10] = (k00+k11) * A2; - z[-11] = (k11-k00) * A2; - - k00 = z[-12] - z[ -4]; // reverse to avoid a unary negation - k11 = z[ -5] - z[-13]; - z[ -4] = z[ -4] + z[-12]; - z[ -5] = z[ -5] + z[-13]; - z[-12] = k11; - z[-13] = k00; - - k00 = z[-14] - z[ -6]; // reverse to avoid a unary negation - k11 = z[ -7] - z[-15]; - z[ -6] = z[ -6] + z[-14]; - z[ -7] = z[ -7] + z[-15]; - z[-14] = (k00+k11) * A2; - z[-15] = (k00-k11) * A2; - - iter_54(z); - iter_54(z-8); - z -= 16; - } -} - -static void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) -{ - int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; - int ld; - // @OPTIMIZE: reduce register pressure by using fewer variables? - int save_point = temp_alloc_save(f); - float *buf2 = (float *) temp_alloc(f, n2 * sizeof(*buf2)); - float *u=NULL,*v=NULL; - // twiddle factors - float *A = f->A[blocktype]; - - // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" - // See notes about bugs in that paper in less-optimal implementation 'inverse_mdct_old' after this function. - - // kernel from paper - - - // merged: - // copy and reflect spectral data - // step 0 - - // note that it turns out that the items added together during - // this step are, in fact, being added to themselves (as reflected - // by step 0). inexplicable inefficiency! this became obvious - // once I combined the passes. - - // so there's a missing 'times 2' here (for adding X to itself). - // this propagates through linearly to the end, where the numbers - // are 1/2 too small, and need to be compensated for. - - { - float *d,*e, *AA, *e_stop; - d = &buf2[n2-2]; - AA = A; - e = &buffer[0]; - e_stop = &buffer[n2]; - while (e != e_stop) { - d[1] = (e[0] * AA[0] - e[2]*AA[1]); - d[0] = (e[0] * AA[1] + e[2]*AA[0]); - d -= 2; - AA += 2; - e += 4; - } - - e = &buffer[n2-3]; - while (d >= buf2) { - d[1] = (-e[2] * AA[0] - -e[0]*AA[1]); - d[0] = (-e[2] * AA[1] + -e[0]*AA[0]); - d -= 2; - AA += 2; - e -= 4; - } - } - - // now we use symbolic names for these, so that we can - // possibly swap their meaning as we change which operations - // are in place - - u = buffer; - v = buf2; - - // step 2 (paper output is w, now u) - // this could be in place, but the data ends up in the wrong - // place... _somebody_'s got to swap it, so this is nominated - { - float *AA = &A[n2-8]; - float *d0,*d1, *e0, *e1; - - e0 = &v[n4]; - e1 = &v[0]; - - d0 = &u[n4]; - d1 = &u[0]; - - while (AA >= A) { - float v40_20, v41_21; - - v41_21 = e0[1] - e1[1]; - v40_20 = e0[0] - e1[0]; - d0[1] = e0[1] + e1[1]; - d0[0] = e0[0] + e1[0]; - d1[1] = v41_21*AA[4] - v40_20*AA[5]; - d1[0] = v40_20*AA[4] + v41_21*AA[5]; - - v41_21 = e0[3] - e1[3]; - v40_20 = e0[2] - e1[2]; - d0[3] = e0[3] + e1[3]; - d0[2] = e0[2] + e1[2]; - d1[3] = v41_21*AA[0] - v40_20*AA[1]; - d1[2] = v40_20*AA[0] + v41_21*AA[1]; - - AA -= 8; - - d0 += 4; - d1 += 4; - e0 += 4; - e1 += 4; - } - } - - // step 3 - ld = ilog(n) - 1; // ilog is off-by-one from normal definitions - - // optimized step 3: - - // the original step3 loop can be nested r inside s or s inside r; - // it's written originally as s inside r, but this is dumb when r - // iterates many times, and s few. So I have two copies of it and - // switch between them halfway. - - // this is iteration 0 of step 3 - imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*0, -(n >> 3), A); - imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*1, -(n >> 3), A); - - // this is iteration 1 of step 3 - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*0, -(n >> 4), A, 16); - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*1, -(n >> 4), A, 16); - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*2, -(n >> 4), A, 16); - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*3, -(n >> 4), A, 16); - - l=2; - for (; l < (ld-3)>>1; ++l) { - int k0 = n >> (l+2), k0_2 = k0>>1; - int lim = 1 << (l+1); - int i; - for (i=0; i < lim; ++i) - imdct_step3_inner_r_loop(n >> (l+4), u, n2-1 - k0*i, -k0_2, A, 1 << (l+3)); - } - - for (; l < ld-6; ++l) { - int k0 = n >> (l+2), k1 = 1 << (l+3), k0_2 = k0>>1; - int rlim = n >> (l+6), r; - int lim = 1 << (l+1); - int i_off; - float *A0 = A; - i_off = n2-1; - for (r=rlim; r > 0; --r) { - imdct_step3_inner_s_loop(lim, u, i_off, -k0_2, A0, k1, k0); - A0 += k1*4; - i_off -= 8; - } - } - - // iterations with count: - // ld-6,-5,-4 all interleaved together - // the big win comes from getting rid of needless flops - // due to the constants on pass 5 & 4 being all 1 and 0; - // combining them to be simultaneous to improve cache made little difference - imdct_step3_inner_s_loop_ld654(n >> 5, u, n2-1, A, n); - - // output is u - - // step 4, 5, and 6 - // cannot be in-place because of step 5 - { - uint16 *bitrev = f->bit_reverse[blocktype]; - // weirdly, I'd have thought reading sequentially and writing - // erratically would have been better than vice-versa, but in - // fact that's not what my testing showed. (That is, with - // j = bitreverse(i), do you read i and write j, or read j and write i.) - - float *d0 = &v[n4-4]; - float *d1 = &v[n2-4]; - while (d0 >= v) { - int k4; - - k4 = bitrev[0]; - d1[3] = u[k4+0]; - d1[2] = u[k4+1]; - d0[3] = u[k4+2]; - d0[2] = u[k4+3]; - - k4 = bitrev[1]; - d1[1] = u[k4+0]; - d1[0] = u[k4+1]; - d0[1] = u[k4+2]; - d0[0] = u[k4+3]; - - d0 -= 4; - d1 -= 4; - bitrev += 2; - } - } - // (paper output is u, now v) - - - // data must be in buf2 - assert(v == buf2); - - // step 7 (paper output is v, now v) - // this is now in place - { - float *C = f->C[blocktype]; - float *d, *e; - - d = v; - e = v + n2 - 4; - - while (d < e) { - float a02,a11,b0,b1,b2,b3; - - a02 = d[0] - e[2]; - a11 = d[1] + e[3]; - - b0 = C[1]*a02 + C[0]*a11; - b1 = C[1]*a11 - C[0]*a02; - - b2 = d[0] + e[ 2]; - b3 = d[1] - e[ 3]; - - d[0] = b2 + b0; - d[1] = b3 + b1; - e[2] = b2 - b0; - e[3] = b1 - b3; - - a02 = d[2] - e[0]; - a11 = d[3] + e[1]; - - b0 = C[3]*a02 + C[2]*a11; - b1 = C[3]*a11 - C[2]*a02; - - b2 = d[2] + e[ 0]; - b3 = d[3] - e[ 1]; - - d[2] = b2 + b0; - d[3] = b3 + b1; - e[0] = b2 - b0; - e[1] = b1 - b3; - - C += 4; - d += 4; - e -= 4; - } - } - - // data must be in buf2 - - - // step 8+decode (paper output is X, now buffer) - // this generates pairs of data a la 8 and pushes them directly through - // the decode kernel (pushing rather than pulling) to avoid having - // to make another pass later - - // this cannot POSSIBLY be in place, so we refer to the buffers directly - - { - float *d0,*d1,*d2,*d3; - - float *B = f->B[blocktype] + n2 - 8; - float *e = buf2 + n2 - 8; - d0 = &buffer[0]; - d1 = &buffer[n2-4]; - d2 = &buffer[n2]; - d3 = &buffer[n-4]; - while (e >= v) { - float p0,p1,p2,p3; - - p3 = e[6]*B[7] - e[7]*B[6]; - p2 = -e[6]*B[6] - e[7]*B[7]; - - d0[0] = p3; - d1[3] = - p3; - d2[0] = p2; - d3[3] = p2; - - p1 = e[4]*B[5] - e[5]*B[4]; - p0 = -e[4]*B[4] - e[5]*B[5]; - - d0[1] = p1; - d1[2] = - p1; - d2[1] = p0; - d3[2] = p0; - - p3 = e[2]*B[3] - e[3]*B[2]; - p2 = -e[2]*B[2] - e[3]*B[3]; - - d0[2] = p3; - d1[1] = - p3; - d2[2] = p2; - d3[1] = p2; - - p1 = e[0]*B[1] - e[1]*B[0]; - p0 = -e[0]*B[0] - e[1]*B[1]; - - d0[3] = p1; - d1[0] = - p1; - d2[3] = p0; - d3[0] = p0; - - B -= 8; - e -= 8; - d0 += 4; - d2 += 4; - d1 -= 4; - d3 -= 4; - } - } - - temp_free(f,buf2); - temp_alloc_restore(f,save_point); -} - -#if 0 -// this is the original version of the above code, if you want to optimize it from scratch -void inverse_mdct_naive(float *buffer, int n) -{ - float s; - float A[1 << 12], B[1 << 12], C[1 << 11]; - int i,k,k2,k4, n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; - int n3_4 = n - n4, ld; - // how can they claim this only uses N words?! - // oh, because they're only used sparsely, whoops - float u[1 << 13], X[1 << 13], v[1 << 13], w[1 << 13]; - // set up twiddle factors - - for (k=k2=0; k < n4; ++k,k2+=2) { - A[k2 ] = (float) cos(4*k*M_PI/n); - A[k2+1] = (float) -sin(4*k*M_PI/n); - B[k2 ] = (float) cos((k2+1)*M_PI/n/2); - B[k2+1] = (float) sin((k2+1)*M_PI/n/2); - } - for (k=k2=0; k < n8; ++k,k2+=2) { - C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); - C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); - } - - // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" - // Note there are bugs in that pseudocode, presumably due to them attempting - // to rename the arrays nicely rather than representing the way their actual - // implementation bounces buffers back and forth. As a result, even in the - // "some formulars corrected" version, a direct implementation fails. These - // are noted below as "paper bug". - - // copy and reflect spectral data - for (k=0; k < n2; ++k) u[k] = buffer[k]; - for ( ; k < n ; ++k) u[k] = -buffer[n - k - 1]; - // kernel from paper - // step 1 - for (k=k2=k4=0; k < n4; k+=1, k2+=2, k4+=4) { - v[n-k4-1] = (u[k4] - u[n-k4-1]) * A[k2] - (u[k4+2] - u[n-k4-3])*A[k2+1]; - v[n-k4-3] = (u[k4] - u[n-k4-1]) * A[k2+1] + (u[k4+2] - u[n-k4-3])*A[k2]; - } - // step 2 - for (k=k4=0; k < n8; k+=1, k4+=4) { - w[n2+3+k4] = v[n2+3+k4] + v[k4+3]; - w[n2+1+k4] = v[n2+1+k4] + v[k4+1]; - w[k4+3] = (v[n2+3+k4] - v[k4+3])*A[n2-4-k4] - (v[n2+1+k4]-v[k4+1])*A[n2-3-k4]; - w[k4+1] = (v[n2+1+k4] - v[k4+1])*A[n2-4-k4] + (v[n2+3+k4]-v[k4+3])*A[n2-3-k4]; - } - // step 3 - ld = ilog(n) - 1; // ilog is off-by-one from normal definitions - for (l=0; l < ld-3; ++l) { - int k0 = n >> (l+2), k1 = 1 << (l+3); - int rlim = n >> (l+4), r4, r; - int s2lim = 1 << (l+2), s2; - for (r=r4=0; r < rlim; r4+=4,++r) { - for (s2=0; s2 < s2lim; s2+=2) { - u[n-1-k0*s2-r4] = w[n-1-k0*s2-r4] + w[n-1-k0*(s2+1)-r4]; - u[n-3-k0*s2-r4] = w[n-3-k0*s2-r4] + w[n-3-k0*(s2+1)-r4]; - u[n-1-k0*(s2+1)-r4] = (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1] - - (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1+1]; - u[n-3-k0*(s2+1)-r4] = (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1] - + (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1+1]; - } - } - if (l+1 < ld-3) { - // paper bug: ping-ponging of u&w here is omitted - memcpy(w, u, sizeof(u)); - } - } - - // step 4 - for (i=0; i < n8; ++i) { - int j = bit_reverse(i) >> (32-ld+3); - assert(j < n8); - if (i == j) { - // paper bug: original code probably swapped in place; if copying, - // need to directly copy in this case - int i8 = i << 3; - v[i8+1] = u[i8+1]; - v[i8+3] = u[i8+3]; - v[i8+5] = u[i8+5]; - v[i8+7] = u[i8+7]; - } else if (i < j) { - int i8 = i << 3, j8 = j << 3; - v[j8+1] = u[i8+1], v[i8+1] = u[j8 + 1]; - v[j8+3] = u[i8+3], v[i8+3] = u[j8 + 3]; - v[j8+5] = u[i8+5], v[i8+5] = u[j8 + 5]; - v[j8+7] = u[i8+7], v[i8+7] = u[j8 + 7]; - } - } - // step 5 - for (k=0; k < n2; ++k) { - w[k] = v[k*2+1]; - } - // step 6 - for (k=k2=k4=0; k < n8; ++k, k2 += 2, k4 += 4) { - u[n-1-k2] = w[k4]; - u[n-2-k2] = w[k4+1]; - u[n3_4 - 1 - k2] = w[k4+2]; - u[n3_4 - 2 - k2] = w[k4+3]; - } - // step 7 - for (k=k2=0; k < n8; ++k, k2 += 2) { - v[n2 + k2 ] = ( u[n2 + k2] + u[n-2-k2] + C[k2+1]*(u[n2+k2]-u[n-2-k2]) + C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; - v[n-2 - k2] = ( u[n2 + k2] + u[n-2-k2] - C[k2+1]*(u[n2+k2]-u[n-2-k2]) - C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; - v[n2+1+ k2] = ( u[n2+1+k2] - u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; - v[n-1 - k2] = (-u[n2+1+k2] + u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; - } - // step 8 - for (k=k2=0; k < n4; ++k,k2 += 2) { - X[k] = v[k2+n2]*B[k2 ] + v[k2+1+n2]*B[k2+1]; - X[n2-1-k] = v[k2+n2]*B[k2+1] - v[k2+1+n2]*B[k2 ]; - } - - // decode kernel to output - // determined the following value experimentally - // (by first figuring out what made inverse_mdct_slow work); then matching that here - // (probably vorbis encoder premultiplies by n or n/2, to save it on the decoder?) - s = 0.5; // theoretically would be n4 - - // [[[ note! the s value of 0.5 is compensated for by the B[] in the current code, - // so it needs to use the "old" B values to behave correctly, or else - // set s to 1.0 ]]] - for (i=0; i < n4 ; ++i) buffer[i] = s * X[i+n4]; - for ( ; i < n3_4; ++i) buffer[i] = -s * X[n3_4 - i - 1]; - for ( ; i < n ; ++i) buffer[i] = -s * X[i - n3_4]; -} -#endif - -static float *get_window(vorb *f, int len) -{ - len <<= 1; - if (len == f->blocksize_0) return f->window[0]; - if (len == f->blocksize_1) return f->window[1]; - return NULL; -} - -#ifndef STB_VORBIS_NO_DEFER_FLOOR -typedef int16 YTYPE; -#else -typedef int YTYPE; -#endif -static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *finalY, uint8 *step2_flag) -{ - int n2 = n >> 1; - int s = map->chan[i].mux, floor; - floor = map->submap_floor[s]; - if (f->floor_types[floor] == 0) { - return error(f, VORBIS_invalid_stream); - } else { - Floor1 *g = &f->floor_config[floor].floor1; - int j,q; - int lx = 0, ly = finalY[0] * g->floor1_multiplier; - for (q=1; q < g->values; ++q) { - j = g->sorted_order[q]; - #ifndef STB_VORBIS_NO_DEFER_FLOOR - if (finalY[j] >= 0) - #else - if (step2_flag[j]) - #endif - { - int hy = finalY[j] * g->floor1_multiplier; - int hx = g->Xlist[j]; - if (lx != hx) - draw_line(target, lx,ly, hx,hy, n2); - CHECK(f); - lx = hx, ly = hy; - } - } - if (lx < n2) { - // optimization of: draw_line(target, lx,ly, n,ly, n2); - for (j=lx; j < n2; ++j) - LINE_OP(target[j], inverse_db_table[ly]); - CHECK(f); - } - } - return TRUE; -} - -// The meaning of "left" and "right" -// -// For a given frame: -// we compute samples from 0..n -// window_center is n/2 -// we'll window and mix the samples from left_start to left_end with data from the previous frame -// all of the samples from left_end to right_start can be output without mixing; however, -// this interval is 0-length except when transitioning between short and long frames -// all of the samples from right_start to right_end need to be mixed with the next frame, -// which we don't have, so those get saved in a buffer -// frame N's right_end-right_start, the number of samples to mix with the next frame, -// has to be the same as frame N+1's left_end-left_start (which they are by -// construction) - -static int vorbis_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) -{ - Mode *m; - int i, n, prev, next, window_center; - f->channel_buffer_start = f->channel_buffer_end = 0; - - retry: - if (f->eof) return FALSE; - if (!maybe_start_packet(f)) - return FALSE; - // check packet type - if (get_bits(f,1) != 0) { - if (IS_PUSH_MODE(f)) - return error(f,VORBIS_bad_packet_type); - while (EOP != get8_packet(f)); - goto retry; - } - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - - i = get_bits(f, ilog(f->mode_count-1)); - if (i == EOP) return FALSE; - if (i >= f->mode_count) return FALSE; - *mode = i; - m = f->mode_config + i; - if (m->blockflag) { - n = f->blocksize_1; - prev = get_bits(f,1); - next = get_bits(f,1); - } else { - prev = next = 0; - n = f->blocksize_0; - } - -// WINDOWING - - window_center = n >> 1; - if (m->blockflag && !prev) { - *p_left_start = (n - f->blocksize_0) >> 2; - *p_left_end = (n + f->blocksize_0) >> 2; - } else { - *p_left_start = 0; - *p_left_end = window_center; - } - if (m->blockflag && !next) { - *p_right_start = (n*3 - f->blocksize_0) >> 2; - *p_right_end = (n*3 + f->blocksize_0) >> 2; - } else { - *p_right_start = window_center; - *p_right_end = n; - } - - return TRUE; -} - -static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start, int left_end, int right_start, int right_end, int *p_left) -{ - Mapping *map; - int i,j,k,n,n2; - int zero_channel[256]; - int really_zero_channel[256]; - -// WINDOWING - - n = f->blocksize[m->blockflag]; - map = &f->mapping[m->mapping]; - -// FLOORS - n2 = n >> 1; - - CHECK(f); - - for (i=0; i < f->channels; ++i) { - int s = map->chan[i].mux, floor; - zero_channel[i] = FALSE; - floor = map->submap_floor[s]; - if (f->floor_types[floor] == 0) { - return error(f, VORBIS_invalid_stream); - } else { - Floor1 *g = &f->floor_config[floor].floor1; - if (get_bits(f, 1)) { - short *finalY; - uint8 step2_flag[256]; - static int range_list[4] = { 256, 128, 86, 64 }; - int range = range_list[g->floor1_multiplier-1]; - int offset = 2; - finalY = f->finalY[i]; - finalY[0] = get_bits(f, ilog(range)-1); - finalY[1] = get_bits(f, ilog(range)-1); - for (j=0; j < g->partitions; ++j) { - int pclass = g->partition_class_list[j]; - int cdim = g->class_dimensions[pclass]; - int cbits = g->class_subclasses[pclass]; - int csub = (1 << cbits)-1; - int cval = 0; - if (cbits) { - Codebook *c = f->codebooks + g->class_masterbooks[pclass]; - DECODE(cval,f,c); - } - for (k=0; k < cdim; ++k) { - int book = g->subclass_books[pclass][cval & csub]; - cval = cval >> cbits; - if (book >= 0) { - int temp; - Codebook *c = f->codebooks + book; - DECODE(temp,f,c); - finalY[offset++] = temp; - } else - finalY[offset++] = 0; - } - } - if (f->valid_bits == INVALID_BITS) goto error; // behavior according to spec - step2_flag[0] = step2_flag[1] = 1; - for (j=2; j < g->values; ++j) { - int low, high, pred, highroom, lowroom, room, val; - low = g->neighbors[j][0]; - high = g->neighbors[j][1]; - //neighbors(g->Xlist, j, &low, &high); - pred = predict_point(g->Xlist[j], g->Xlist[low], g->Xlist[high], finalY[low], finalY[high]); - val = finalY[j]; - highroom = range - pred; - lowroom = pred; - if (highroom < lowroom) - room = highroom * 2; - else - room = lowroom * 2; - if (val) { - step2_flag[low] = step2_flag[high] = 1; - step2_flag[j] = 1; - if (val >= room) - if (highroom > lowroom) - finalY[j] = val - lowroom + pred; - else - finalY[j] = pred - val + highroom - 1; - else - if (val & 1) - finalY[j] = pred - ((val+1)>>1); - else - finalY[j] = pred + (val>>1); - } else { - step2_flag[j] = 0; - finalY[j] = pred; - } - } - -#ifdef STB_VORBIS_NO_DEFER_FLOOR - do_floor(f, map, i, n, f->floor_buffers[i], finalY, step2_flag); -#else - // defer final floor computation until _after_ residue - for (j=0; j < g->values; ++j) { - if (!step2_flag[j]) - finalY[j] = -1; - } -#endif - } else { - error: - zero_channel[i] = TRUE; - } - // So we just defer everything else to later - - // at this point we've decoded the floor into buffer - } - } - CHECK(f); - // at this point we've decoded all floors - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - - // re-enable coupled channels if necessary - memcpy(really_zero_channel, zero_channel, sizeof(really_zero_channel[0]) * f->channels); - for (i=0; i < map->coupling_steps; ++i) - if (!zero_channel[map->chan[i].magnitude] || !zero_channel[map->chan[i].angle]) { - zero_channel[map->chan[i].magnitude] = zero_channel[map->chan[i].angle] = FALSE; - } - - CHECK(f); -// RESIDUE DECODE - for (i=0; i < map->submaps; ++i) { - float *residue_buffers[STB_VORBIS_MAX_CHANNELS]; - int r; - uint8 do_not_decode[256]; - int ch = 0; - for (j=0; j < f->channels; ++j) { - if (map->chan[j].mux == i) { - if (zero_channel[j]) { - do_not_decode[ch] = TRUE; - residue_buffers[ch] = NULL; - } else { - do_not_decode[ch] = FALSE; - residue_buffers[ch] = f->channel_buffers[j]; - } - ++ch; - } - } - r = map->submap_residue[i]; - decode_residue(f, residue_buffers, ch, n2, r, do_not_decode); - } - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - CHECK(f); - -// INVERSE COUPLING - for (i = map->coupling_steps-1; i >= 0; --i) { - int n2 = n >> 1; - float *m = f->channel_buffers[map->chan[i].magnitude]; - float *a = f->channel_buffers[map->chan[i].angle ]; - for (j=0; j < n2; ++j) { - float a2,m2; - if (m[j] > 0) - if (a[j] > 0) - m2 = m[j], a2 = m[j] - a[j]; - else - a2 = m[j], m2 = m[j] + a[j]; - else - if (a[j] > 0) - m2 = m[j], a2 = m[j] + a[j]; - else - a2 = m[j], m2 = m[j] - a[j]; - m[j] = m2; - a[j] = a2; - } - } - CHECK(f); - - // finish decoding the floors -#ifndef STB_VORBIS_NO_DEFER_FLOOR - for (i=0; i < f->channels; ++i) { - if (really_zero_channel[i]) { - memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); - } else { - do_floor(f, map, i, n, f->channel_buffers[i], f->finalY[i], NULL); - } - } -#else - for (i=0; i < f->channels; ++i) { - if (really_zero_channel[i]) { - memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); - } else { - for (j=0; j < n2; ++j) - f->channel_buffers[i][j] *= f->floor_buffers[i][j]; - } - } -#endif - -// INVERSE MDCT - CHECK(f); - for (i=0; i < f->channels; ++i) - inverse_mdct(f->channel_buffers[i], n, f, m->blockflag); - CHECK(f); - - // this shouldn't be necessary, unless we exited on an error - // and want to flush to get to the next packet - flush_packet(f); - - if (f->first_decode) { - // assume we start so first non-discarded sample is sample 0 - // this isn't to spec, but spec would require us to read ahead - // and decode the size of all current frames--could be done, - // but presumably it's not a commonly used feature - f->current_loc = -n2; // start of first frame is positioned for discard - // we might have to discard samples "from" the next frame too, - // if we're lapping a large block then a small at the start? - f->discard_samples_deferred = n - right_end; - f->current_loc_valid = TRUE; - f->first_decode = FALSE; - } else if (f->discard_samples_deferred) { - if (f->discard_samples_deferred >= right_start - left_start) { - f->discard_samples_deferred -= (right_start - left_start); - left_start = right_start; - *p_left = left_start; - } else { - left_start += f->discard_samples_deferred; - *p_left = left_start; - f->discard_samples_deferred = 0; - } - } else if (f->previous_length == 0 && f->current_loc_valid) { - // we're recovering from a seek... that means we're going to discard - // the samples from this packet even though we know our position from - // the last page header, so we need to update the position based on - // the discarded samples here - // but wait, the code below is going to add this in itself even - // on a discard, so we don't need to do it here... - } - - // check if we have ogg information about the sample # for this packet - if (f->last_seg_which == f->end_seg_with_known_loc) { - // if we have a valid current loc, and this is final: - if (f->current_loc_valid && (f->page_flag & PAGEFLAG_last_page)) { - uint32 current_end = f->known_loc_for_packet; - // then let's infer the size of the (probably) short final frame - if (current_end < f->current_loc + (right_end-left_start)) { - if (current_end < f->current_loc) { - // negative truncation, that's impossible! - *len = 0; - } else { - *len = current_end - f->current_loc; - } - *len += left_start; // this doesn't seem right, but has no ill effect on my test files - if (*len > right_end) *len = right_end; // this should never happen - f->current_loc += *len; - return TRUE; - } - } - // otherwise, just set our sample loc - // guess that the ogg granule pos refers to the _middle_ of the - // last frame? - // set f->current_loc to the position of left_start - f->current_loc = f->known_loc_for_packet - (n2-left_start); - f->current_loc_valid = TRUE; - } - if (f->current_loc_valid) - f->current_loc += (right_start - left_start); - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - *len = right_end; // ignore samples after the window goes to 0 - CHECK(f); - - return TRUE; -} - -static int vorbis_decode_packet(vorb *f, int *len, int *p_left, int *p_right) -{ - int mode, left_end, right_end; - if (!vorbis_decode_initial(f, p_left, &left_end, p_right, &right_end, &mode)) return 0; - return vorbis_decode_packet_rest(f, len, f->mode_config + mode, *p_left, left_end, *p_right, right_end, p_left); -} - -static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right) -{ - int prev,i,j; - // we use right&left (the start of the right- and left-window sin()-regions) - // to determine how much to return, rather than inferring from the rules - // (same result, clearer code); 'left' indicates where our sin() window - // starts, therefore where the previous window's right edge starts, and - // therefore where to start mixing from the previous buffer. 'right' - // indicates where our sin() ending-window starts, therefore that's where - // we start saving, and where our returned-data ends. - - // mixin from previous window - if (f->previous_length) { - int i,j, n = f->previous_length; - float *w = get_window(f, n); - if (w == NULL) return 0; - for (i=0; i < f->channels; ++i) { - for (j=0; j < n; ++j) - f->channel_buffers[i][left+j] = - f->channel_buffers[i][left+j]*w[ j] + - f->previous_window[i][ j]*w[n-1-j]; - } - } - - prev = f->previous_length; - - // last half of this data becomes previous window - f->previous_length = len - right; - - // @OPTIMIZE: could avoid this copy by double-buffering the - // output (flipping previous_window with channel_buffers), but - // then previous_window would have to be 2x as large, and - // channel_buffers couldn't be temp mem (although they're NOT - // currently temp mem, they could be (unless we want to level - // performance by spreading out the computation)) - for (i=0; i < f->channels; ++i) - for (j=0; right+j < len; ++j) - f->previous_window[i][j] = f->channel_buffers[i][right+j]; - - if (!prev) - // there was no previous packet, so this data isn't valid... - // this isn't entirely true, only the would-have-overlapped data - // isn't valid, but this seems to be what the spec requires - return 0; - - // truncate a short frame - if (len < right) right = len; - - f->samples_output += right-left; - - return right - left; -} - -static int vorbis_pump_first_frame(stb_vorbis *f) -{ - int len, right, left, res; - res = vorbis_decode_packet(f, &len, &left, &right); - if (res) - vorbis_finish_frame(f, len, left, right); - return res; -} - -#ifndef STB_VORBIS_NO_PUSHDATA_API -static int is_whole_packet_present(stb_vorbis *f) -{ - // make sure that we have the packet available before continuing... - // this requires a full ogg parse, but we know we can fetch from f->stream - - // instead of coding this out explicitly, we could save the current read state, - // read the next packet with get8() until end-of-packet, check f->eof, then - // reset the state? but that would be slower, esp. since we'd have over 256 bytes - // of state to restore (primarily the page segment table) - - int s = f->next_seg, first = TRUE; - uint8 *p = f->stream; - - if (s != -1) { // if we're not starting the packet with a 'continue on next page' flag - for (; s < f->segment_count; ++s) { - p += f->segments[s]; - if (f->segments[s] < 255) // stop at first short segment - break; - } - // either this continues, or it ends it... - if (s == f->segment_count) - s = -1; // set 'crosses page' flag - if (p > f->stream_end) return error(f, VORBIS_need_more_data); - first = FALSE; - } - for (; s == -1;) { - uint8 *q; - int n; - - // check that we have the page header ready - if (p + 26 >= f->stream_end) return error(f, VORBIS_need_more_data); - // validate the page - if (memcmp(p, ogg_page_header, 4)) return error(f, VORBIS_invalid_stream); - if (p[4] != 0) return error(f, VORBIS_invalid_stream); - if (first) { // the first segment must NOT have 'continued_packet', later ones MUST - if (f->previous_length) - if ((p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); - // if no previous length, we're resynching, so we can come in on a continued-packet, - // which we'll just drop - } else { - if (!(p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); - } - n = p[26]; // segment counts - q = p+27; // q points to segment table - p = q + n; // advance past header - // make sure we've read the segment table - if (p > f->stream_end) return error(f, VORBIS_need_more_data); - for (s=0; s < n; ++s) { - p += q[s]; - if (q[s] < 255) - break; - } - if (s == n) - s = -1; // set 'crosses page' flag - if (p > f->stream_end) return error(f, VORBIS_need_more_data); - first = FALSE; - } - return TRUE; -} -#endif // !STB_VORBIS_NO_PUSHDATA_API - -static int start_decoder(vorb *f) -{ - uint8 header[6], x,y; - int len,i,j,k, max_submaps = 0; - int longest_floorlist=0; - - // first page, first packet - f->first_decode = TRUE; - - if (!start_page(f)) return FALSE; - // validate page flag - if (!(f->page_flag & PAGEFLAG_first_page)) return error(f, VORBIS_invalid_first_page); - if (f->page_flag & PAGEFLAG_last_page) return error(f, VORBIS_invalid_first_page); - if (f->page_flag & PAGEFLAG_continued_packet) return error(f, VORBIS_invalid_first_page); - // check for expected packet length - if (f->segment_count != 1) return error(f, VORBIS_invalid_first_page); - if (f->segments[0] != 30) { - // check for the Ogg skeleton fishead identifying header to refine our error - if (f->segments[0] == 64 && - getn(f, header, 6) && - header[0] == 'f' && - header[1] == 'i' && - header[2] == 's' && - header[3] == 'h' && - header[4] == 'e' && - header[5] == 'a' && - get8(f) == 'd' && - get8(f) == '\0') return error(f, VORBIS_ogg_skeleton_not_supported); - else - return error(f, VORBIS_invalid_first_page); - } - - // read packet - // check packet header - if (get8(f) != VORBIS_packet_id) return error(f, VORBIS_invalid_first_page); - if (!getn(f, header, 6)) return error(f, VORBIS_unexpected_eof); - if (!vorbis_validate(header)) return error(f, VORBIS_invalid_first_page); - // vorbis_version - if (get32(f) != 0) return error(f, VORBIS_invalid_first_page); - f->channels = get8(f); if (!f->channels) return error(f, VORBIS_invalid_first_page); - if (f->channels > STB_VORBIS_MAX_CHANNELS) return error(f, VORBIS_too_many_channels); - f->sample_rate = get32(f); if (!f->sample_rate) return error(f, VORBIS_invalid_first_page); - get32(f); // bitrate_maximum - get32(f); // bitrate_nominal - get32(f); // bitrate_minimum - x = get8(f); - { - int log0,log1; - log0 = x & 15; - log1 = x >> 4; - f->blocksize_0 = 1 << log0; - f->blocksize_1 = 1 << log1; - if (log0 < 6 || log0 > 13) return error(f, VORBIS_invalid_setup); - if (log1 < 6 || log1 > 13) return error(f, VORBIS_invalid_setup); - if (log0 > log1) return error(f, VORBIS_invalid_setup); - } - - // framing_flag - x = get8(f); - if (!(x & 1)) return error(f, VORBIS_invalid_first_page); - - // second packet! - if (!start_page(f)) return FALSE; - - if (!start_packet(f)) return FALSE; - - if (!next_segment(f)) return FALSE; - - if (get8_packet(f) != VORBIS_packet_comment) return error(f, VORBIS_invalid_setup); - for (i=0; i < 6; ++i) header[i] = get8_packet(f); - if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); - //file vendor - len = get32_packet(f); - f->vendor = (char*)setup_malloc(f, sizeof(char) * (len+1)); - if (f->vendor == NULL) return error(f, VORBIS_outofmem); - for(i=0; i < len; ++i) { - f->vendor[i] = get8_packet(f); - } - f->vendor[len] = (char)'\0'; - //user comments - f->comment_list_length = get32_packet(f); - f->comment_list = (char**)setup_malloc(f, sizeof(char*) * (f->comment_list_length)); - if (f->comment_list == NULL) return error(f, VORBIS_outofmem); - - for(i=0; i < f->comment_list_length; ++i) { - len = get32_packet(f); - f->comment_list[i] = (char*)setup_malloc(f, sizeof(char) * (len+1)); - if (f->comment_list[i] == NULL) return error(f, VORBIS_outofmem); - - for(j=0; j < len; ++j) { - f->comment_list[i][j] = get8_packet(f); - } - f->comment_list[i][len] = (char)'\0'; - } - - // framing_flag - x = get8_packet(f); - if (!(x & 1)) return error(f, VORBIS_invalid_setup); - - - skip(f, f->bytes_in_seg); - f->bytes_in_seg = 0; - - do { - len = next_segment(f); - skip(f, len); - f->bytes_in_seg = 0; - } while (len); - - // third packet! - if (!start_packet(f)) return FALSE; - - #ifndef STB_VORBIS_NO_PUSHDATA_API - if (IS_PUSH_MODE(f)) { - if (!is_whole_packet_present(f)) { - // convert error in ogg header to write type - if (f->error == VORBIS_invalid_stream) - f->error = VORBIS_invalid_setup; - return FALSE; - } - } - #endif - - crc32_init(); // always init it, to avoid multithread race conditions - - if (get8_packet(f) != VORBIS_packet_setup) return error(f, VORBIS_invalid_setup); - for (i=0; i < 6; ++i) header[i] = get8_packet(f); - if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); - - // codebooks - - f->codebook_count = get_bits(f,8) + 1; - f->codebooks = (Codebook *) setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count); - if (f->codebooks == NULL) return error(f, VORBIS_outofmem); - memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count); - for (i=0; i < f->codebook_count; ++i) { - uint32 *values; - int ordered, sorted_count; - int total=0; - uint8 *lengths; - Codebook *c = f->codebooks+i; - CHECK(f); - x = get_bits(f, 8); if (x != 0x42) return error(f, VORBIS_invalid_setup); - x = get_bits(f, 8); if (x != 0x43) return error(f, VORBIS_invalid_setup); - x = get_bits(f, 8); if (x != 0x56) return error(f, VORBIS_invalid_setup); - x = get_bits(f, 8); - c->dimensions = (get_bits(f, 8)<<8) + x; - x = get_bits(f, 8); - y = get_bits(f, 8); - c->entries = (get_bits(f, 8)<<16) + (y<<8) + x; - ordered = get_bits(f,1); - c->sparse = ordered ? 0 : get_bits(f,1); - - if (c->dimensions == 0 && c->entries != 0) return error(f, VORBIS_invalid_setup); - - if (c->sparse) - lengths = (uint8 *) setup_temp_malloc(f, c->entries); - else - lengths = c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); - - if (!lengths) return error(f, VORBIS_outofmem); - - if (ordered) { - int current_entry = 0; - int current_length = get_bits(f,5) + 1; - while (current_entry < c->entries) { - int limit = c->entries - current_entry; - int n = get_bits(f, ilog(limit)); - if (current_length >= 32) return error(f, VORBIS_invalid_setup); - if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); } - memset(lengths + current_entry, current_length, n); - current_entry += n; - ++current_length; - } - } else { - for (j=0; j < c->entries; ++j) { - int present = c->sparse ? get_bits(f,1) : 1; - if (present) { - lengths[j] = get_bits(f, 5) + 1; - ++total; - if (lengths[j] == 32) - return error(f, VORBIS_invalid_setup); - } else { - lengths[j] = NO_CODE; - } - } - } - - if (c->sparse && total >= c->entries >> 2) { - // convert sparse items to non-sparse! - if (c->entries > (int) f->setup_temp_memory_required) - f->setup_temp_memory_required = c->entries; - - c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); - if (c->codeword_lengths == NULL) return error(f, VORBIS_outofmem); - memcpy(c->codeword_lengths, lengths, c->entries); - setup_temp_free(f, lengths, c->entries); // note this is only safe if there have been no intervening temp mallocs! - lengths = c->codeword_lengths; - c->sparse = 0; - } - - // compute the size of the sorted tables - if (c->sparse) { - sorted_count = total; - } else { - sorted_count = 0; - #ifndef STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH - for (j=0; j < c->entries; ++j) - if (lengths[j] > STB_VORBIS_FAST_HUFFMAN_LENGTH && lengths[j] != NO_CODE) - ++sorted_count; - #endif - } - - c->sorted_entries = sorted_count; - values = NULL; - - CHECK(f); - if (!c->sparse) { - c->codewords = (uint32 *) setup_malloc(f, sizeof(c->codewords[0]) * c->entries); - if (!c->codewords) return error(f, VORBIS_outofmem); - } else { - unsigned int size; - if (c->sorted_entries) { - c->codeword_lengths = (uint8 *) setup_malloc(f, c->sorted_entries); - if (!c->codeword_lengths) return error(f, VORBIS_outofmem); - c->codewords = (uint32 *) setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries); - if (!c->codewords) return error(f, VORBIS_outofmem); - values = (uint32 *) setup_temp_malloc(f, sizeof(*values) * c->sorted_entries); - if (!values) return error(f, VORBIS_outofmem); - } - size = c->entries + (sizeof(*c->codewords) + sizeof(*values)) * c->sorted_entries; - if (size > f->setup_temp_memory_required) - f->setup_temp_memory_required = size; - } - - if (!compute_codewords(c, lengths, c->entries, values)) { - if (c->sparse) setup_temp_free(f, values, 0); - return error(f, VORBIS_invalid_setup); - } - - if (c->sorted_entries) { - // allocate an extra slot for sentinels - c->sorted_codewords = (uint32 *) setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries+1)); - if (c->sorted_codewords == NULL) return error(f, VORBIS_outofmem); - // allocate an extra slot at the front so that c->sorted_values[-1] is defined - // so that we can catch that case without an extra if - c->sorted_values = ( int *) setup_malloc(f, sizeof(*c->sorted_values ) * (c->sorted_entries+1)); - if (c->sorted_values == NULL) return error(f, VORBIS_outofmem); - ++c->sorted_values; - c->sorted_values[-1] = -1; - compute_sorted_huffman(c, lengths, values); - } - - if (c->sparse) { - setup_temp_free(f, values, sizeof(*values)*c->sorted_entries); - setup_temp_free(f, c->codewords, sizeof(*c->codewords)*c->sorted_entries); - setup_temp_free(f, lengths, c->entries); - c->codewords = NULL; - } - - compute_accelerated_huffman(c); - - CHECK(f); - c->lookup_type = get_bits(f, 4); - if (c->lookup_type > 2) return error(f, VORBIS_invalid_setup); - if (c->lookup_type > 0) { - uint16 *mults; - c->minimum_value = float32_unpack(get_bits(f, 32)); - c->delta_value = float32_unpack(get_bits(f, 32)); - c->value_bits = get_bits(f, 4)+1; - c->sequence_p = get_bits(f,1); - if (c->lookup_type == 1) { - int values = lookup1_values(c->entries, c->dimensions); - if (values < 0) return error(f, VORBIS_invalid_setup); - c->lookup_values = (uint32) values; - } else { - c->lookup_values = c->entries * c->dimensions; - } - if (c->lookup_values == 0) return error(f, VORBIS_invalid_setup); - mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values); - if (mults == NULL) return error(f, VORBIS_outofmem); - for (j=0; j < (int) c->lookup_values; ++j) { - int q = get_bits(f, c->value_bits); - if (q == EOP) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_invalid_setup); } - mults[j] = q; - } - -#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - int len, sparse = c->sparse; - float last=0; - // pre-expand the lookup1-style multiplicands, to avoid a divide in the inner loop - if (sparse) { - if (c->sorted_entries == 0) goto skip; - c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions); - } else - c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions); - if (c->multiplicands == NULL) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } - len = sparse ? c->sorted_entries : c->entries; - for (j=0; j < len; ++j) { - unsigned int z = sparse ? c->sorted_values[j] : j; - unsigned int div=1; - for (k=0; k < c->dimensions; ++k) { - int off = (z / div) % c->lookup_values; - float val = mults[off]; - val = mults[off]*c->delta_value + c->minimum_value + last; - c->multiplicands[j*c->dimensions + k] = val; - if (c->sequence_p) - last = val; - if (k+1 < c->dimensions) { - if (div > UINT_MAX / (unsigned int) c->lookup_values) { - setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); - return error(f, VORBIS_invalid_setup); - } - div *= c->lookup_values; - } - } - } - c->lookup_type = 2; - } - else -#endif - { - float last=0; - CHECK(f); - c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values); - if (c->multiplicands == NULL) { setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } - for (j=0; j < (int) c->lookup_values; ++j) { - float val = mults[j] * c->delta_value + c->minimum_value + last; - c->multiplicands[j] = val; - if (c->sequence_p) - last = val; - } - } -#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - skip:; -#endif - setup_temp_free(f, mults, sizeof(mults[0])*c->lookup_values); - - CHECK(f); - } - CHECK(f); - } - - // time domain transfers (notused) - - x = get_bits(f, 6) + 1; - for (i=0; i < x; ++i) { - uint32 z = get_bits(f, 16); - if (z != 0) return error(f, VORBIS_invalid_setup); - } - - // Floors - f->floor_count = get_bits(f, 6)+1; - f->floor_config = (Floor *) setup_malloc(f, f->floor_count * sizeof(*f->floor_config)); - if (f->floor_config == NULL) return error(f, VORBIS_outofmem); - for (i=0; i < f->floor_count; ++i) { - f->floor_types[i] = get_bits(f, 16); - if (f->floor_types[i] > 1) return error(f, VORBIS_invalid_setup); - if (f->floor_types[i] == 0) { - Floor0 *g = &f->floor_config[i].floor0; - g->order = get_bits(f,8); - g->rate = get_bits(f,16); - g->bark_map_size = get_bits(f,16); - g->amplitude_bits = get_bits(f,6); - g->amplitude_offset = get_bits(f,8); - g->number_of_books = get_bits(f,4) + 1; - for (j=0; j < g->number_of_books; ++j) - g->book_list[j] = get_bits(f,8); - return error(f, VORBIS_feature_not_supported); - } else { - stbv__floor_ordering p[31*8+2]; - Floor1 *g = &f->floor_config[i].floor1; - int max_class = -1; - g->partitions = get_bits(f, 5); - for (j=0; j < g->partitions; ++j) { - g->partition_class_list[j] = get_bits(f, 4); - if (g->partition_class_list[j] > max_class) - max_class = g->partition_class_list[j]; - } - for (j=0; j <= max_class; ++j) { - g->class_dimensions[j] = get_bits(f, 3)+1; - g->class_subclasses[j] = get_bits(f, 2); - if (g->class_subclasses[j]) { - g->class_masterbooks[j] = get_bits(f, 8); - if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup); - } - for (k=0; k < 1 << g->class_subclasses[j]; ++k) { - g->subclass_books[j][k] = get_bits(f,8)-1; - if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); - } - } - g->floor1_multiplier = get_bits(f,2)+1; - g->rangebits = get_bits(f,4); - g->Xlist[0] = 0; - g->Xlist[1] = 1 << g->rangebits; - g->values = 2; - for (j=0; j < g->partitions; ++j) { - int c = g->partition_class_list[j]; - for (k=0; k < g->class_dimensions[c]; ++k) { - g->Xlist[g->values] = get_bits(f, g->rangebits); - ++g->values; - } - } - // precompute the sorting - for (j=0; j < g->values; ++j) { - p[j].x = g->Xlist[j]; - p[j].id = j; - } - qsort(p, g->values, sizeof(p[0]), point_compare); - for (j=0; j < g->values-1; ++j) - if (p[j].x == p[j+1].x) - return error(f, VORBIS_invalid_setup); - for (j=0; j < g->values; ++j) - g->sorted_order[j] = (uint8) p[j].id; - // precompute the neighbors - for (j=2; j < g->values; ++j) { - int low = 0,hi = 0; - neighbors(g->Xlist, j, &low,&hi); - g->neighbors[j][0] = low; - g->neighbors[j][1] = hi; - } - - if (g->values > longest_floorlist) - longest_floorlist = g->values; - } - } - - // Residue - f->residue_count = get_bits(f, 6)+1; - f->residue_config = (Residue *) setup_malloc(f, f->residue_count * sizeof(f->residue_config[0])); - if (f->residue_config == NULL) return error(f, VORBIS_outofmem); - memset(f->residue_config, 0, f->residue_count * sizeof(f->residue_config[0])); - for (i=0; i < f->residue_count; ++i) { - uint8 residue_cascade[64]; - Residue *r = f->residue_config+i; - f->residue_types[i] = get_bits(f, 16); - if (f->residue_types[i] > 2) return error(f, VORBIS_invalid_setup); - r->begin = get_bits(f, 24); - r->end = get_bits(f, 24); - if (r->end < r->begin) return error(f, VORBIS_invalid_setup); - r->part_size = get_bits(f,24)+1; - r->classifications = get_bits(f,6)+1; - r->classbook = get_bits(f,8); - if (r->classbook >= f->codebook_count) return error(f, VORBIS_invalid_setup); - for (j=0; j < r->classifications; ++j) { - uint8 high_bits=0; - uint8 low_bits=get_bits(f,3); - if (get_bits(f,1)) - high_bits = get_bits(f,5); - residue_cascade[j] = high_bits*8 + low_bits; - } - r->residue_books = (short (*)[8]) setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications); - if (r->residue_books == NULL) return error(f, VORBIS_outofmem); - for (j=0; j < r->classifications; ++j) { - for (k=0; k < 8; ++k) { - if (residue_cascade[j] & (1 << k)) { - r->residue_books[j][k] = get_bits(f, 8); - if (r->residue_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); - } else { - r->residue_books[j][k] = -1; - } - } - } - // precompute the classifications[] array to avoid inner-loop mod/divide - // call it 'classdata' since we already have r->classifications - r->classdata = (uint8 **) setup_malloc(f, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); - if (!r->classdata) return error(f, VORBIS_outofmem); - memset(r->classdata, 0, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); - for (j=0; j < f->codebooks[r->classbook].entries; ++j) { - int classwords = f->codebooks[r->classbook].dimensions; - int temp = j; - r->classdata[j] = (uint8 *) setup_malloc(f, sizeof(r->classdata[j][0]) * classwords); - if (r->classdata[j] == NULL) return error(f, VORBIS_outofmem); - for (k=classwords-1; k >= 0; --k) { - r->classdata[j][k] = temp % r->classifications; - temp /= r->classifications; - } - } - } - - f->mapping_count = get_bits(f,6)+1; - f->mapping = (Mapping *) setup_malloc(f, f->mapping_count * sizeof(*f->mapping)); - if (f->mapping == NULL) return error(f, VORBIS_outofmem); - memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping)); - for (i=0; i < f->mapping_count; ++i) { - Mapping *m = f->mapping + i; - int mapping_type = get_bits(f,16); - if (mapping_type != 0) return error(f, VORBIS_invalid_setup); - m->chan = (MappingChannel *) setup_malloc(f, f->channels * sizeof(*m->chan)); - if (m->chan == NULL) return error(f, VORBIS_outofmem); - if (get_bits(f,1)) - m->submaps = get_bits(f,4)+1; - else - m->submaps = 1; - if (m->submaps > max_submaps) - max_submaps = m->submaps; - if (get_bits(f,1)) { - m->coupling_steps = get_bits(f,8)+1; - if (m->coupling_steps > f->channels) return error(f, VORBIS_invalid_setup); - for (k=0; k < m->coupling_steps; ++k) { - m->chan[k].magnitude = get_bits(f, ilog(f->channels-1)); - m->chan[k].angle = get_bits(f, ilog(f->channels-1)); - if (m->chan[k].magnitude >= f->channels) return error(f, VORBIS_invalid_setup); - if (m->chan[k].angle >= f->channels) return error(f, VORBIS_invalid_setup); - if (m->chan[k].magnitude == m->chan[k].angle) return error(f, VORBIS_invalid_setup); - } - } else - m->coupling_steps = 0; - - // reserved field - if (get_bits(f,2)) return error(f, VORBIS_invalid_setup); - if (m->submaps > 1) { - for (j=0; j < f->channels; ++j) { - m->chan[j].mux = get_bits(f, 4); - if (m->chan[j].mux >= m->submaps) return error(f, VORBIS_invalid_setup); - } - } else - // @SPECIFICATION: this case is missing from the spec - for (j=0; j < f->channels; ++j) - m->chan[j].mux = 0; - - for (j=0; j < m->submaps; ++j) { - get_bits(f,8); // discard - m->submap_floor[j] = get_bits(f,8); - m->submap_residue[j] = get_bits(f,8); - if (m->submap_floor[j] >= f->floor_count) return error(f, VORBIS_invalid_setup); - if (m->submap_residue[j] >= f->residue_count) return error(f, VORBIS_invalid_setup); - } - } - - // Modes - f->mode_count = get_bits(f, 6)+1; - for (i=0; i < f->mode_count; ++i) { - Mode *m = f->mode_config+i; - m->blockflag = get_bits(f,1); - m->windowtype = get_bits(f,16); - m->transformtype = get_bits(f,16); - m->mapping = get_bits(f,8); - if (m->windowtype != 0) return error(f, VORBIS_invalid_setup); - if (m->transformtype != 0) return error(f, VORBIS_invalid_setup); - if (m->mapping >= f->mapping_count) return error(f, VORBIS_invalid_setup); - } - - flush_packet(f); - - f->previous_length = 0; - - for (i=0; i < f->channels; ++i) { - f->channel_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1); - f->previous_window[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); - f->finalY[i] = (int16 *) setup_malloc(f, sizeof(int16) * longest_floorlist); - if (f->channel_buffers[i] == NULL || f->previous_window[i] == NULL || f->finalY[i] == NULL) return error(f, VORBIS_outofmem); - memset(f->channel_buffers[i], 0, sizeof(float) * f->blocksize_1); - #ifdef STB_VORBIS_NO_DEFER_FLOOR - f->floor_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); - if (f->floor_buffers[i] == NULL) return error(f, VORBIS_outofmem); - #endif - } - - if (!init_blocksize(f, 0, f->blocksize_0)) return FALSE; - if (!init_blocksize(f, 1, f->blocksize_1)) return FALSE; - f->blocksize[0] = f->blocksize_0; - f->blocksize[1] = f->blocksize_1; - -#ifdef STB_VORBIS_DIVIDE_TABLE - if (integer_divide_table[1][1]==0) - for (i=0; i < DIVTAB_NUMER; ++i) - for (j=1; j < DIVTAB_DENOM; ++j) - integer_divide_table[i][j] = i / j; -#endif - - // compute how much temporary memory is needed - - // 1. - { - uint32 imdct_mem = (f->blocksize_1 * sizeof(float) >> 1); - uint32 classify_mem; - int i,max_part_read=0; - for (i=0; i < f->residue_count; ++i) { - Residue *r = f->residue_config + i; - unsigned int actual_size = f->blocksize_1 / 2; - unsigned int limit_r_begin = r->begin < actual_size ? r->begin : actual_size; - unsigned int limit_r_end = r->end < actual_size ? r->end : actual_size; - int n_read = limit_r_end - limit_r_begin; - int part_read = n_read / r->part_size; - if (part_read > max_part_read) - max_part_read = part_read; - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(uint8 *)); - #else - classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(int *)); - #endif - - // maximum reasonable partition size is f->blocksize_1 - - f->temp_memory_required = classify_mem; - if (imdct_mem > f->temp_memory_required) - f->temp_memory_required = imdct_mem; - } - - - if (f->alloc.alloc_buffer) { - assert(f->temp_offset == f->alloc.alloc_buffer_length_in_bytes); - // check if there's enough temp memory so we don't error later - if (f->setup_offset + sizeof(*f) + f->temp_memory_required > (unsigned) f->temp_offset) - return error(f, VORBIS_outofmem); - } - - // @TODO: stb_vorbis_seek_start expects first_audio_page_offset to point to a page - // without PAGEFLAG_continued_packet, so this either points to the first page, or - // the page after the end of the headers. It might be cleaner to point to a page - // in the middle of the headers, when that's the page where the first audio packet - // starts, but we'd have to also correctly skip the end of any continued packet in - // stb_vorbis_seek_start. - if (f->next_seg == -1) { - f->first_audio_page_offset = stb_vorbis_get_file_offset(f); - } else { - f->first_audio_page_offset = 0; - } - - return TRUE; -} - -static void vorbis_deinit(stb_vorbis *p) -{ - int i,j; - - setup_free(p, p->vendor); - for (i=0; i < p->comment_list_length; ++i) { - setup_free(p, p->comment_list[i]); - } - setup_free(p, p->comment_list); - - if (p->residue_config) { - for (i=0; i < p->residue_count; ++i) { - Residue *r = p->residue_config+i; - if (r->classdata) { - for (j=0; j < p->codebooks[r->classbook].entries; ++j) - setup_free(p, r->classdata[j]); - setup_free(p, r->classdata); - } - setup_free(p, r->residue_books); - } - } - - if (p->codebooks) { - CHECK(p); - for (i=0; i < p->codebook_count; ++i) { - Codebook *c = p->codebooks + i; - setup_free(p, c->codeword_lengths); - setup_free(p, c->multiplicands); - setup_free(p, c->codewords); - setup_free(p, c->sorted_codewords); - // c->sorted_values[-1] is the first entry in the array - setup_free(p, c->sorted_values ? c->sorted_values-1 : NULL); - } - setup_free(p, p->codebooks); - } - setup_free(p, p->floor_config); - setup_free(p, p->residue_config); - if (p->mapping) { - for (i=0; i < p->mapping_count; ++i) - setup_free(p, p->mapping[i].chan); - setup_free(p, p->mapping); - } - CHECK(p); - for (i=0; i < p->channels && i < STB_VORBIS_MAX_CHANNELS; ++i) { - setup_free(p, p->channel_buffers[i]); - setup_free(p, p->previous_window[i]); - #ifdef STB_VORBIS_NO_DEFER_FLOOR - setup_free(p, p->floor_buffers[i]); - #endif - setup_free(p, p->finalY[i]); - } - for (i=0; i < 2; ++i) { - setup_free(p, p->A[i]); - setup_free(p, p->B[i]); - setup_free(p, p->C[i]); - setup_free(p, p->window[i]); - setup_free(p, p->bit_reverse[i]); - } - #ifndef STB_VORBIS_NO_STDIO - if (p->close_on_free) fclose(p->f); - #endif -} - -void stb_vorbis_close(stb_vorbis *p) -{ - if (p == NULL) return; - vorbis_deinit(p); - setup_free(p,p); -} - -static void vorbis_init(stb_vorbis *p, const stb_vorbis_alloc *z) -{ - memset(p, 0, sizeof(*p)); // NULL out all malloc'd pointers to start - if (z) { - p->alloc = *z; - p->alloc.alloc_buffer_length_in_bytes &= ~7; - p->temp_offset = p->alloc.alloc_buffer_length_in_bytes; - } - p->eof = 0; - p->error = VORBIS__no_error; - p->stream = NULL; - p->codebooks = NULL; - p->page_crc_tests = -1; - #ifndef STB_VORBIS_NO_STDIO - p->close_on_free = FALSE; - p->f = NULL; - #endif -} - -int stb_vorbis_get_sample_offset(stb_vorbis *f) -{ - if (f->current_loc_valid) - return f->current_loc; - else - return -1; -} - -stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f) -{ - stb_vorbis_info d; - d.channels = f->channels; - d.sample_rate = f->sample_rate; - d.setup_memory_required = f->setup_memory_required; - d.setup_temp_memory_required = f->setup_temp_memory_required; - d.temp_memory_required = f->temp_memory_required; - d.max_frame_size = f->blocksize_1 >> 1; - return d; -} - -stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f) -{ - stb_vorbis_comment d; - d.vendor = f->vendor; - d.comment_list_length = f->comment_list_length; - d.comment_list = f->comment_list; - return d; -} - -int stb_vorbis_get_error(stb_vorbis *f) -{ - int e = f->error; - f->error = VORBIS__no_error; - return e; -} - -static stb_vorbis * vorbis_alloc(stb_vorbis *f) -{ - stb_vorbis *p = (stb_vorbis *) setup_malloc(f, sizeof(*p)); - return p; -} - -#ifndef STB_VORBIS_NO_PUSHDATA_API - -void stb_vorbis_flush_pushdata(stb_vorbis *f) -{ - f->previous_length = 0; - f->page_crc_tests = 0; - f->discard_samples_deferred = 0; - f->current_loc_valid = FALSE; - f->first_decode = FALSE; - f->samples_output = 0; - f->channel_buffer_start = 0; - f->channel_buffer_end = 0; -} - -static int vorbis_search_for_page_pushdata(vorb *f, uint8 *data, int data_len) -{ - int i,n; - for (i=0; i < f->page_crc_tests; ++i) - f->scan[i].bytes_done = 0; - - // if we have room for more scans, search for them first, because - // they may cause us to stop early if their header is incomplete - if (f->page_crc_tests < STB_VORBIS_PUSHDATA_CRC_COUNT) { - if (data_len < 4) return 0; - data_len -= 3; // need to look for 4-byte sequence, so don't miss - // one that straddles a boundary - for (i=0; i < data_len; ++i) { - if (data[i] == 0x4f) { - if (0==memcmp(data+i, ogg_page_header, 4)) { - int j,len; - uint32 crc; - // make sure we have the whole page header - if (i+26 >= data_len || i+27+data[i+26] >= data_len) { - // only read up to this page start, so hopefully we'll - // have the whole page header start next time - data_len = i; - break; - } - // ok, we have it all; compute the length of the page - len = 27 + data[i+26]; - for (j=0; j < data[i+26]; ++j) - len += data[i+27+j]; - // scan everything up to the embedded crc (which we must 0) - crc = 0; - for (j=0; j < 22; ++j) - crc = crc32_update(crc, data[i+j]); - // now process 4 0-bytes - for ( ; j < 26; ++j) - crc = crc32_update(crc, 0); - // len is the total number of bytes we need to scan - n = f->page_crc_tests++; - f->scan[n].bytes_left = len-j; - f->scan[n].crc_so_far = crc; - f->scan[n].goal_crc = data[i+22] + (data[i+23] << 8) + (data[i+24]<<16) + (data[i+25]<<24); - // if the last frame on a page is continued to the next, then - // we can't recover the sample_loc immediately - if (data[i+27+data[i+26]-1] == 255) - f->scan[n].sample_loc = ~0; - else - f->scan[n].sample_loc = data[i+6] + (data[i+7] << 8) + (data[i+ 8]<<16) + (data[i+ 9]<<24); - f->scan[n].bytes_done = i+j; - if (f->page_crc_tests == STB_VORBIS_PUSHDATA_CRC_COUNT) - break; - // keep going if we still have room for more - } - } - } - } - - for (i=0; i < f->page_crc_tests;) { - uint32 crc; - int j; - int n = f->scan[i].bytes_done; - int m = f->scan[i].bytes_left; - if (m > data_len - n) m = data_len - n; - // m is the bytes to scan in the current chunk - crc = f->scan[i].crc_so_far; - for (j=0; j < m; ++j) - crc = crc32_update(crc, data[n+j]); - f->scan[i].bytes_left -= m; - f->scan[i].crc_so_far = crc; - if (f->scan[i].bytes_left == 0) { - // does it match? - if (f->scan[i].crc_so_far == f->scan[i].goal_crc) { - // Houston, we have page - data_len = n+m; // consumption amount is wherever that scan ended - f->page_crc_tests = -1; // drop out of page scan mode - f->previous_length = 0; // decode-but-don't-output one frame - f->next_seg = -1; // start a new page - f->current_loc = f->scan[i].sample_loc; // set the current sample location - // to the amount we'd have decoded had we decoded this page - f->current_loc_valid = f->current_loc != ~0U; - return data_len; - } - // delete entry - f->scan[i] = f->scan[--f->page_crc_tests]; - } else { - ++i; - } - } - - return data_len; -} - -// return value: number of bytes we used -int stb_vorbis_decode_frame_pushdata( - stb_vorbis *f, // the file we're decoding - const uint8 *data, int data_len, // the memory available for decoding - int *channels, // place to write number of float * buffers - float ***output, // place to write float ** array of float * buffers - int *samples // place to write number of output samples - ) -{ - int i; - int len,right,left; - - if (!IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - - if (f->page_crc_tests >= 0) { - *samples = 0; - return vorbis_search_for_page_pushdata(f, (uint8 *) data, data_len); - } - - f->stream = (uint8 *) data; - f->stream_end = (uint8 *) data + data_len; - f->error = VORBIS__no_error; - - // check that we have the entire packet in memory - if (!is_whole_packet_present(f)) { - *samples = 0; - return 0; - } - - if (!vorbis_decode_packet(f, &len, &left, &right)) { - // save the actual error we encountered - enum STBVorbisError error = f->error; - if (error == VORBIS_bad_packet_type) { - // flush and resynch - f->error = VORBIS__no_error; - while (get8_packet(f) != EOP) - if (f->eof) break; - *samples = 0; - return (int) (f->stream - data); - } - if (error == VORBIS_continued_packet_flag_invalid) { - if (f->previous_length == 0) { - // we may be resynching, in which case it's ok to hit one - // of these; just discard the packet - f->error = VORBIS__no_error; - while (get8_packet(f) != EOP) - if (f->eof) break; - *samples = 0; - return (int) (f->stream - data); - } - } - // if we get an error while parsing, what to do? - // well, it DEFINITELY won't work to continue from where we are! - stb_vorbis_flush_pushdata(f); - // restore the error that actually made us bail - f->error = error; - *samples = 0; - return 1; - } - - // success! - len = vorbis_finish_frame(f, len, left, right); - for (i=0; i < f->channels; ++i) - f->outputs[i] = f->channel_buffers[i] + left; - - if (channels) *channels = f->channels; - *samples = len; - *output = f->outputs; - return (int) (f->stream - data); -} - -stb_vorbis *stb_vorbis_open_pushdata( - const unsigned char *data, int data_len, // the memory available for decoding - int *data_used, // only defined if result is not NULL - int *error, const stb_vorbis_alloc *alloc) -{ - stb_vorbis *f, p; - vorbis_init(&p, alloc); - p.stream = (uint8 *) data; - p.stream_end = (uint8 *) data + data_len; - p.push_mode = TRUE; - if (!start_decoder(&p)) { - if (p.eof) - *error = VORBIS_need_more_data; - else - *error = p.error; - return NULL; - } - f = vorbis_alloc(&p); - if (f) { - *f = p; - *data_used = (int) (f->stream - data); - *error = 0; - return f; - } else { - vorbis_deinit(&p); - return NULL; - } -} -#endif // STB_VORBIS_NO_PUSHDATA_API - -unsigned int stb_vorbis_get_file_offset(stb_vorbis *f) -{ - #ifndef STB_VORBIS_NO_PUSHDATA_API - if (f->push_mode) return 0; - #endif - if (USE_MEMORY(f)) return (unsigned int) (f->stream - f->stream_start); - #ifndef STB_VORBIS_NO_STDIO - return (unsigned int) (ftell(f->f) - f->f_start); - #endif -} - -#ifndef STB_VORBIS_NO_PULLDATA_API -// -// DATA-PULLING API -// - -static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last) -{ - for(;;) { - int n; - if (f->eof) return 0; - n = get8(f); - if (n == 0x4f) { // page header candidate - unsigned int retry_loc = stb_vorbis_get_file_offset(f); - int i; - // check if we're off the end of a file_section stream - if (retry_loc - 25 > f->stream_len) - return 0; - // check the rest of the header - for (i=1; i < 4; ++i) - if (get8(f) != ogg_page_header[i]) - break; - if (f->eof) return 0; - if (i == 4) { - uint8 header[27]; - uint32 i, crc, goal, len; - for (i=0; i < 4; ++i) - header[i] = ogg_page_header[i]; - for (; i < 27; ++i) - header[i] = get8(f); - if (f->eof) return 0; - if (header[4] != 0) goto invalid; - goal = header[22] + (header[23] << 8) + (header[24]<<16) + (header[25]<<24); - for (i=22; i < 26; ++i) - header[i] = 0; - crc = 0; - for (i=0; i < 27; ++i) - crc = crc32_update(crc, header[i]); - len = 0; - for (i=0; i < header[26]; ++i) { - int s = get8(f); - crc = crc32_update(crc, s); - len += s; - } - if (len && f->eof) return 0; - for (i=0; i < len; ++i) - crc = crc32_update(crc, get8(f)); - // finished parsing probable page - if (crc == goal) { - // we could now check that it's either got the last - // page flag set, OR it's followed by the capture - // pattern, but I guess TECHNICALLY you could have - // a file with garbage between each ogg page and recover - // from it automatically? So even though that paranoia - // might decrease the chance of an invalid decode by - // another 2^32, not worth it since it would hose those - // invalid-but-useful files? - if (end) - *end = stb_vorbis_get_file_offset(f); - if (last) { - if (header[5] & 0x04) - *last = 1; - else - *last = 0; - } - set_file_offset(f, retry_loc-1); - return 1; - } - } - invalid: - // not a valid page, so rewind and look for next one - set_file_offset(f, retry_loc); - } - } -} - - -#define SAMPLE_unknown 0xffffffff - -// seeking is implemented with a binary search, which narrows down the range to -// 64K, before using a linear search (because finding the synchronization -// pattern can be expensive, and the chance we'd find the end page again is -// relatively high for small ranges) -// -// two initial interpolation-style probes are used at the start of the search -// to try to bound either side of the binary search sensibly, while still -// working in O(log n) time if they fail. - -static int get_seek_page_info(stb_vorbis *f, ProbedPage *z) -{ - uint8 header[27], lacing[255]; - int i,len; - - // record where the page starts - z->page_start = stb_vorbis_get_file_offset(f); - - // parse the header - getn(f, header, 27); - if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') - return 0; - getn(f, lacing, header[26]); - - // determine the length of the payload - len = 0; - for (i=0; i < header[26]; ++i) - len += lacing[i]; - - // this implies where the page ends - z->page_end = z->page_start + 27 + header[26] + len; - - // read the last-decoded sample out of the data - z->last_decoded_sample = header[6] + (header[7] << 8) + (header[8] << 16) + (header[9] << 24); - - // restore file state to where we were - set_file_offset(f, z->page_start); - return 1; -} - -// rarely used function to seek back to the preceding page while finding the -// start of a packet -static int go_to_page_before(stb_vorbis *f, unsigned int limit_offset) -{ - unsigned int previous_safe, end; - - // now we want to seek back 64K from the limit - if (limit_offset >= 65536 && limit_offset-65536 >= f->first_audio_page_offset) - previous_safe = limit_offset - 65536; - else - previous_safe = f->first_audio_page_offset; - - set_file_offset(f, previous_safe); - - while (vorbis_find_page(f, &end, NULL)) { - if (end >= limit_offset && stb_vorbis_get_file_offset(f) < limit_offset) - return 1; - set_file_offset(f, end); - } - - return 0; -} - -// implements the search logic for finding a page and starting decoding. if -// the function succeeds, current_loc_valid will be true and current_loc will -// be less than or equal to the provided sample number (the closer the -// better). -static int seek_to_sample_coarse(stb_vorbis *f, uint32 sample_number) -{ - ProbedPage left, right, mid; - int i, start_seg_with_known_loc, end_pos, page_start; - uint32 delta, stream_length, padding, last_sample_limit; - double offset = 0.0, bytes_per_sample = 0.0; - int probe = 0; - - // find the last page and validate the target sample - stream_length = stb_vorbis_stream_length_in_samples(f); - if (stream_length == 0) return error(f, VORBIS_seek_without_length); - if (sample_number > stream_length) return error(f, VORBIS_seek_invalid); - - // this is the maximum difference between the window-center (which is the - // actual granule position value), and the right-start (which the spec - // indicates should be the granule position (give or take one)). - padding = ((f->blocksize_1 - f->blocksize_0) >> 2); - if (sample_number < padding) - last_sample_limit = 0; - else - last_sample_limit = sample_number - padding; - - left = f->p_first; - while (left.last_decoded_sample == ~0U) { - // (untested) the first page does not have a 'last_decoded_sample' - set_file_offset(f, left.page_end); - if (!get_seek_page_info(f, &left)) goto error; - } - - right = f->p_last; - assert(right.last_decoded_sample != ~0U); - - // starting from the start is handled differently - if (last_sample_limit <= left.last_decoded_sample) { - if (stb_vorbis_seek_start(f)) { - if (f->current_loc > sample_number) - return error(f, VORBIS_seek_failed); - return 1; - } - return 0; - } - - while (left.page_end != right.page_start) { - assert(left.page_end < right.page_start); - // search range in bytes - delta = right.page_start - left.page_end; - if (delta <= 65536) { - // there's only 64K left to search - handle it linearly - set_file_offset(f, left.page_end); - } else { - if (probe < 2) { - if (probe == 0) { - // first probe (interpolate) - double data_bytes = right.page_end - left.page_start; - bytes_per_sample = data_bytes / right.last_decoded_sample; - offset = left.page_start + bytes_per_sample * (last_sample_limit - left.last_decoded_sample); - } else { - // second probe (try to bound the other side) - double error = ((double) last_sample_limit - mid.last_decoded_sample) * bytes_per_sample; - if (error >= 0 && error < 8000) error = 8000; - if (error < 0 && error > -8000) error = -8000; - offset += error * 2; - } - - // ensure the offset is valid - if (offset < left.page_end) - offset = left.page_end; - if (offset > right.page_start - 65536) - offset = right.page_start - 65536; - - set_file_offset(f, (unsigned int) offset); - } else { - // binary search for large ranges (offset by 32K to ensure - // we don't hit the right page) - set_file_offset(f, left.page_end + (delta / 2) - 32768); - } - - if (!vorbis_find_page(f, NULL, NULL)) goto error; - } - - for (;;) { - if (!get_seek_page_info(f, &mid)) goto error; - if (mid.last_decoded_sample != ~0U) break; - // (untested) no frames end on this page - set_file_offset(f, mid.page_end); - assert(mid.page_start < right.page_start); - } - - // if we've just found the last page again then we're in a tricky file, - // and we're close enough (if it wasn't an interpolation probe). - if (mid.page_start == right.page_start) { - if (probe >= 2 || delta <= 65536) - break; - } else { - if (last_sample_limit < mid.last_decoded_sample) - right = mid; - else - left = mid; - } - - ++probe; - } - - // seek back to start of the last packet - page_start = left.page_start; - set_file_offset(f, page_start); - if (!start_page(f)) return error(f, VORBIS_seek_failed); - end_pos = f->end_seg_with_known_loc; - assert(end_pos >= 0); - - for (;;) { - for (i = end_pos; i > 0; --i) - if (f->segments[i-1] != 255) - break; - - start_seg_with_known_loc = i; - - if (start_seg_with_known_loc > 0 || !(f->page_flag & PAGEFLAG_continued_packet)) - break; - - // (untested) the final packet begins on an earlier page - if (!go_to_page_before(f, page_start)) - goto error; - - page_start = stb_vorbis_get_file_offset(f); - if (!start_page(f)) goto error; - end_pos = f->segment_count - 1; - } - - // prepare to start decoding - f->current_loc_valid = FALSE; - f->last_seg = FALSE; - f->valid_bits = 0; - f->packet_bytes = 0; - f->bytes_in_seg = 0; - f->previous_length = 0; - f->next_seg = start_seg_with_known_loc; - - for (i = 0; i < start_seg_with_known_loc; i++) - skip(f, f->segments[i]); - - // start decoding (optimizable - this frame is generally discarded) - if (!vorbis_pump_first_frame(f)) - return 0; - if (f->current_loc > sample_number) - return error(f, VORBIS_seek_failed); - return 1; - -error: - // try to restore the file to a valid state - stb_vorbis_seek_start(f); - return error(f, VORBIS_seek_failed); -} - -// the same as vorbis_decode_initial, but without advancing -static int peek_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) -{ - int bits_read, bytes_read; - - if (!vorbis_decode_initial(f, p_left_start, p_left_end, p_right_start, p_right_end, mode)) - return 0; - - // either 1 or 2 bytes were read, figure out which so we can rewind - bits_read = 1 + ilog(f->mode_count-1); - if (f->mode_config[*mode].blockflag) - bits_read += 2; - bytes_read = (bits_read + 7) / 8; - - f->bytes_in_seg += bytes_read; - f->packet_bytes -= bytes_read; - skip(f, -bytes_read); - if (f->next_seg == -1) - f->next_seg = f->segment_count - 1; - else - f->next_seg--; - f->valid_bits = 0; - - return 1; -} - -int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number) -{ - uint32 max_frame_samples; - - if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - - // fast page-level search - if (!seek_to_sample_coarse(f, sample_number)) - return 0; - - assert(f->current_loc_valid); - assert(f->current_loc <= sample_number); - - // linear search for the relevant packet - max_frame_samples = (f->blocksize_1*3 - f->blocksize_0) >> 2; - while (f->current_loc < sample_number) { - int left_start, left_end, right_start, right_end, mode, frame_samples; - if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode)) - return error(f, VORBIS_seek_failed); - // calculate the number of samples returned by the next frame - frame_samples = right_start - left_start; - if (f->current_loc + frame_samples > sample_number) { - return 1; // the next frame will contain the sample - } else if (f->current_loc + frame_samples + max_frame_samples > sample_number) { - // there's a chance the frame after this could contain the sample - vorbis_pump_first_frame(f); - } else { - // this frame is too early to be relevant - f->current_loc += frame_samples; - f->previous_length = 0; - maybe_start_packet(f); - flush_packet(f); - } - } - // the next frame should start with the sample - if (f->current_loc != sample_number) return error(f, VORBIS_seek_failed); - return 1; -} - -int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number) -{ - if (!stb_vorbis_seek_frame(f, sample_number)) - return 0; - - if (sample_number != f->current_loc) { - int n; - uint32 frame_start = f->current_loc; - stb_vorbis_get_frame_float(f, &n, NULL); - assert(sample_number > frame_start); - assert(f->channel_buffer_start + (int) (sample_number-frame_start) <= f->channel_buffer_end); - f->channel_buffer_start += (sample_number - frame_start); - } - - return 1; -} - -int stb_vorbis_seek_start(stb_vorbis *f) -{ - if (IS_PUSH_MODE(f)) { return error(f, VORBIS_invalid_api_mixing); } - set_file_offset(f, f->first_audio_page_offset); - f->previous_length = 0; - f->first_decode = TRUE; - f->next_seg = -1; - return vorbis_pump_first_frame(f); -} - -unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) -{ - unsigned int restore_offset, previous_safe; - unsigned int end, last_page_loc; - - if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - if (!f->total_samples) { - unsigned int last; - uint32 lo,hi; - char header[6]; - - // first, store the current decode position so we can restore it - restore_offset = stb_vorbis_get_file_offset(f); - - // now we want to seek back 64K from the end (the last page must - // be at most a little less than 64K, but let's allow a little slop) - if (f->stream_len >= 65536 && f->stream_len-65536 >= f->first_audio_page_offset) - previous_safe = f->stream_len - 65536; - else - previous_safe = f->first_audio_page_offset; - - set_file_offset(f, previous_safe); - // previous_safe is now our candidate 'earliest known place that seeking - // to will lead to the final page' - - if (!vorbis_find_page(f, &end, &last)) { - // if we can't find a page, we're hosed! - f->error = VORBIS_cant_find_last_page; - f->total_samples = 0xffffffff; - goto done; - } - - // check if there are more pages - last_page_loc = stb_vorbis_get_file_offset(f); - - // stop when the last_page flag is set, not when we reach eof; - // this allows us to stop short of a 'file_section' end without - // explicitly checking the length of the section - while (!last) { - set_file_offset(f, end); - if (!vorbis_find_page(f, &end, &last)) { - // the last page we found didn't have the 'last page' flag - // set. whoops! - break; - } - previous_safe = last_page_loc+1; - last_page_loc = stb_vorbis_get_file_offset(f); - } - - set_file_offset(f, last_page_loc); - - // parse the header - getn(f, (unsigned char *)header, 6); - // extract the absolute granule position - lo = get32(f); - hi = get32(f); - if (lo == 0xffffffff && hi == 0xffffffff) { - f->error = VORBIS_cant_find_last_page; - f->total_samples = SAMPLE_unknown; - goto done; - } - if (hi) - lo = 0xfffffffe; // saturate - f->total_samples = lo; - - f->p_last.page_start = last_page_loc; - f->p_last.page_end = end; - f->p_last.last_decoded_sample = lo; - - done: - set_file_offset(f, restore_offset); - } - return f->total_samples == SAMPLE_unknown ? 0 : f->total_samples; -} - -float stb_vorbis_stream_length_in_seconds(stb_vorbis *f) -{ - return stb_vorbis_stream_length_in_samples(f) / (float) f->sample_rate; -} - - - -int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output) -{ - int len, right,left,i; - if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - - if (!vorbis_decode_packet(f, &len, &left, &right)) { - f->channel_buffer_start = f->channel_buffer_end = 0; - return 0; - } - - len = vorbis_finish_frame(f, len, left, right); - for (i=0; i < f->channels; ++i) - f->outputs[i] = f->channel_buffers[i] + left; - - f->channel_buffer_start = left; - f->channel_buffer_end = left+len; - - if (channels) *channels = f->channels; - if (output) *output = f->outputs; - return len; -} - -#ifndef STB_VORBIS_NO_STDIO - -stb_vorbis * stb_vorbis_open_file_section(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc, unsigned int length) -{ - stb_vorbis *f, p; - vorbis_init(&p, alloc); - p.f = file; - p.f_start = (uint32) ftell(file); - p.stream_len = length; - p.close_on_free = close_on_free; - if (start_decoder(&p)) { - f = vorbis_alloc(&p); - if (f) { - *f = p; - vorbis_pump_first_frame(f); - return f; - } - } - if (error) *error = p.error; - vorbis_deinit(&p); - return NULL; -} - -stb_vorbis * stb_vorbis_open_file(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc) -{ - unsigned int len, start; - start = (unsigned int) ftell(file); - fseek(file, 0, SEEK_END); - len = (unsigned int) (ftell(file) - start); - fseek(file, start, SEEK_SET); - return stb_vorbis_open_file_section(file, close_on_free, error, alloc, len); -} - -stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc) -{ - FILE *f; -#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) - if (0 != fopen_s(&f, filename, "rb")) - f = NULL; -#else - f = fopen(filename, "rb"); -#endif - if (f) - return stb_vorbis_open_file(f, TRUE, error, alloc); - if (error) *error = VORBIS_file_open_failure; - return NULL; -} -#endif // STB_VORBIS_NO_STDIO - -stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc) -{ - stb_vorbis *f, p; - if (data == NULL) return NULL; - vorbis_init(&p, alloc); - p.stream = (uint8 *) data; - p.stream_end = (uint8 *) data + len; - p.stream_start = (uint8 *) p.stream; - p.stream_len = len; - p.push_mode = FALSE; - if (start_decoder(&p)) { - f = vorbis_alloc(&p); - if (f) { - *f = p; - vorbis_pump_first_frame(f); - if (error) *error = VORBIS__no_error; - return f; - } - } - if (error) *error = p.error; - vorbis_deinit(&p); - return NULL; -} - -#ifndef STB_VORBIS_NO_INTEGER_CONVERSION -#define PLAYBACK_MONO 1 -#define PLAYBACK_LEFT 2 -#define PLAYBACK_RIGHT 4 - -#define L (PLAYBACK_LEFT | PLAYBACK_MONO) -#define C (PLAYBACK_LEFT | PLAYBACK_RIGHT | PLAYBACK_MONO) -#define R (PLAYBACK_RIGHT | PLAYBACK_MONO) - -static int8 channel_position[7][6] = -{ - { 0 }, - { C }, - { L, R }, - { L, C, R }, - { L, R, L, R }, - { L, C, R, L, R }, - { L, C, R, L, R, C }, -}; - - -#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT - typedef union { - float f; - int i; - } float_conv; - typedef char stb_vorbis_float_size_test[sizeof(float)==4 && sizeof(int) == 4]; - #define FASTDEF(x) float_conv x - // add (1<<23) to convert to int, then divide by 2^SHIFT, then add 0.5/2^SHIFT to round - #define MAGIC(SHIFT) (1.5f * (1 << (23-SHIFT)) + 0.5f/(1 << SHIFT)) - #define ADDEND(SHIFT) (((150-SHIFT) << 23) + (1 << 22)) - #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) (temp.f = (x) + MAGIC(s), temp.i - ADDEND(s)) - #define check_endianness() -#else - #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) ((int) ((x) * (1 << (s)))) - #define check_endianness() - #define FASTDEF(x) -#endif - -static void copy_samples(short *dest, float *src, int len) -{ - int i; - check_endianness(); - for (i=0; i < len; ++i) { - FASTDEF(temp); - int v = FAST_SCALED_FLOAT_TO_INT(temp, src[i],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - dest[i] = v; - } -} - -static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len) -{ - #define BUFFER_SIZE 32 - float buffer[BUFFER_SIZE]; - int i,j,o,n = BUFFER_SIZE; - check_endianness(); - for (o = 0; o < len; o += BUFFER_SIZE) { - memset(buffer, 0, sizeof(buffer)); - if (o + n > len) n = len - o; - for (j=0; j < num_c; ++j) { - if (channel_position[num_c][j] & mask) { - for (i=0; i < n; ++i) - buffer[i] += data[j][d_offset+o+i]; - } - } - for (i=0; i < n; ++i) { - FASTDEF(temp); - int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - output[o+i] = v; - } - } -} - -static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len) -{ - #define BUFFER_SIZE 32 - float buffer[BUFFER_SIZE]; - int i,j,o,n = BUFFER_SIZE >> 1; - // o is the offset in the source data - check_endianness(); - for (o = 0; o < len; o += BUFFER_SIZE >> 1) { - // o2 is the offset in the output data - int o2 = o << 1; - memset(buffer, 0, sizeof(buffer)); - if (o + n > len) n = len - o; - for (j=0; j < num_c; ++j) { - int m = channel_position[num_c][j] & (PLAYBACK_LEFT | PLAYBACK_RIGHT); - if (m == (PLAYBACK_LEFT | PLAYBACK_RIGHT)) { - for (i=0; i < n; ++i) { - buffer[i*2+0] += data[j][d_offset+o+i]; - buffer[i*2+1] += data[j][d_offset+o+i]; - } - } else if (m == PLAYBACK_LEFT) { - for (i=0; i < n; ++i) { - buffer[i*2+0] += data[j][d_offset+o+i]; - } - } else if (m == PLAYBACK_RIGHT) { - for (i=0; i < n; ++i) { - buffer[i*2+1] += data[j][d_offset+o+i]; - } - } - } - for (i=0; i < (n<<1); ++i) { - FASTDEF(temp); - int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - output[o2+i] = v; - } - } -} - -static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples) -{ - int i; - if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { - static int channel_selector[3][2] = { {0}, {PLAYBACK_MONO}, {PLAYBACK_LEFT, PLAYBACK_RIGHT} }; - for (i=0; i < buf_c; ++i) - compute_samples(channel_selector[buf_c][i], buffer[i]+b_offset, data_c, data, d_offset, samples); - } else { - int limit = buf_c < data_c ? buf_c : data_c; - for (i=0; i < limit; ++i) - copy_samples(buffer[i]+b_offset, data[i]+d_offset, samples); - for ( ; i < buf_c; ++i) - memset(buffer[i]+b_offset, 0, sizeof(short) * samples); - } -} - -int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples) -{ - float **output = NULL; - int len = stb_vorbis_get_frame_float(f, NULL, &output); - if (len > num_samples) len = num_samples; - if (len) - convert_samples_short(num_c, buffer, 0, f->channels, output, 0, len); - return len; -} - -static void convert_channels_short_interleaved(int buf_c, short *buffer, int data_c, float **data, int d_offset, int len) -{ - int i; - check_endianness(); - if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { - assert(buf_c == 2); - for (i=0; i < buf_c; ++i) - compute_stereo_samples(buffer, data_c, data, d_offset, len); - } else { - int limit = buf_c < data_c ? buf_c : data_c; - int j; - for (j=0; j < len; ++j) { - for (i=0; i < limit; ++i) { - FASTDEF(temp); - float f = data[i][d_offset+j]; - int v = FAST_SCALED_FLOAT_TO_INT(temp, f,15);//data[i][d_offset+j],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - *buffer++ = v; - } - for ( ; i < buf_c; ++i) - *buffer++ = 0; - } - } -} - -int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts) -{ - float **output; - int len; - if (num_c == 1) return stb_vorbis_get_frame_short(f,num_c,&buffer, num_shorts); - len = stb_vorbis_get_frame_float(f, NULL, &output); - if (len) { - if (len*num_c > num_shorts) len = num_shorts / num_c; - convert_channels_short_interleaved(num_c, buffer, f->channels, output, 0, len); - } - return len; -} - -int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts) -{ - float **outputs; - int len = num_shorts / channels; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < len) { - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= len) k = len - n; - if (k) - convert_channels_short_interleaved(channels, buffer, f->channels, f->channel_buffers, f->channel_buffer_start, k); - buffer += k*channels; - n += k; - f->channel_buffer_start += k; - if (n == len) break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; - } - return n; -} - -int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int len) -{ - float **outputs; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < len) { - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= len) k = len - n; - if (k) - convert_samples_short(channels, buffer, n, f->channels, f->channel_buffers, f->channel_buffer_start, k); - n += k; - f->channel_buffer_start += k; - if (n == len) break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; - } - return n; -} - -#ifndef STB_VORBIS_NO_STDIO -int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output) -{ - int data_len, offset, total, limit, error; - short *data; - stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL); - if (v == NULL) return -1; - limit = v->channels * 4096; - *channels = v->channels; - if (sample_rate) - *sample_rate = v->sample_rate; - offset = data_len = 0; - total = limit; - data = (short *) malloc(total * sizeof(*data)); - if (data == NULL) { - stb_vorbis_close(v); - return -2; - } - for (;;) { - int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); - if (n == 0) break; - data_len += n; - offset += n * v->channels; - if (offset + limit > total) { - short *data2; - total *= 2; - data2 = (short *) realloc(data, total * sizeof(*data)); - if (data2 == NULL) { - free(data); - stb_vorbis_close(v); - return -2; - } - data = data2; - } - } - *output = data; - stb_vorbis_close(v); - return data_len; -} -#endif // NO_STDIO - -int stb_vorbis_decode_memory(const uint8 *mem, int len, int *channels, int *sample_rate, short **output) -{ - int data_len, offset, total, limit, error; - short *data; - stb_vorbis *v = stb_vorbis_open_memory(mem, len, &error, NULL); - if (v == NULL) return -1; - limit = v->channels * 4096; - *channels = v->channels; - if (sample_rate) - *sample_rate = v->sample_rate; - offset = data_len = 0; - total = limit; - data = (short *) malloc(total * sizeof(*data)); - if (data == NULL) { - stb_vorbis_close(v); - return -2; - } - for (;;) { - int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); - if (n == 0) break; - data_len += n; - offset += n * v->channels; - if (offset + limit > total) { - short *data2; - total *= 2; - data2 = (short *) realloc(data, total * sizeof(*data)); - if (data2 == NULL) { - free(data); - stb_vorbis_close(v); - return -2; - } - data = data2; - } - } - *output = data; - stb_vorbis_close(v); - return data_len; -} -#endif // STB_VORBIS_NO_INTEGER_CONVERSION - -int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats) -{ - float **outputs; - int len = num_floats / channels; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < len) { - int i,j; - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= len) k = len - n; - for (j=0; j < k; ++j) { - for (i=0; i < z; ++i) - *buffer++ = f->channel_buffers[i][f->channel_buffer_start+j]; - for ( ; i < channels; ++i) - *buffer++ = 0; - } - n += k; - f->channel_buffer_start += k; - if (n == len) - break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) - break; - } - return n; -} - -int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples) -{ - float **outputs; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < num_samples) { - int i; - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= num_samples) k = num_samples - n; - if (k) { - for (i=0; i < z; ++i) - memcpy(buffer[i]+n, f->channel_buffers[i]+f->channel_buffer_start, sizeof(float)*k); - for ( ; i < channels; ++i) - memset(buffer[i]+n, 0, sizeof(float) * k); - } - n += k; - f->channel_buffer_start += k; - if (n == num_samples) - break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) - break; - } - return n; -} -#endif // STB_VORBIS_NO_PULLDATA_API - -/* Version history - 1.17 - 2019-07-08 - fix CVE-2019-13217, -13218, -13219, -13220, -13221, -13222, -13223 - found with Mayhem by ForAllSecure - 1.16 - 2019-03-04 - fix warnings - 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found - 1.14 - 2018-02-11 - delete bogus dealloca usage - 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) - 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files - 1.11 - 2017-07-23 - fix MinGW compilation - 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory - 1.09 - 2016-04-04 - back out 'avoid discarding last frame' fix from previous version - 1.08 - 2016-04-02 - fixed multiple warnings; fix setup memory leaks; - avoid discarding last frame of audio data - 1.07 - 2015-01-16 - fixed some warnings, fix mingw, const-correct API - some more crash fixes when out of memory or with corrupt files - 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) - some crash fixes when out of memory or with corrupt files - 1.05 - 2015-04-19 - don't define __forceinline if it's redundant - 1.04 - 2014-08-27 - fix missing const-correct case in API - 1.03 - 2014-08-07 - Warning fixes - 1.02 - 2014-07-09 - Declare qsort compare function _cdecl on windows - 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float - 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in multichannel - (API change) report sample rate for decode-full-file funcs - 0.99996 - bracket #include <malloc.h> for macintosh compilation by Laurent Gomila - 0.99995 - use union instead of pointer-cast for fast-float-to-int to avoid alias-optimization problem - 0.99994 - change fast-float-to-int to work in single-precision FPU mode, remove endian-dependence - 0.99993 - remove assert that fired on legal files with empty tables - 0.99992 - rewind-to-start - 0.99991 - bugfix to stb_vorbis_get_samples_short by Bernhard Wodo - 0.9999 - (should have been 0.99990) fix no-CRT support, compiling as C++ - 0.9998 - add a full-decode function with a memory source - 0.9997 - fix a bug in the read-from-FILE case in 0.9996 addition - 0.9996 - query length of vorbis stream in samples/seconds - 0.9995 - bugfix to another optimization that only happened in certain files - 0.9994 - bugfix to one of the optimizations that caused significant (but inaudible?) errors - 0.9993 - performance improvements; runs in 99% to 104% of time of reference implementation - 0.9992 - performance improvement of IMDCT; now performs close to reference implementation - 0.9991 - performance improvement of IMDCT - 0.999 - (should have been 0.9990) performance improvement of IMDCT - 0.998 - no-CRT support from Casey Muratori - 0.997 - bugfixes for bugs found by Terje Mathisen - 0.996 - bugfix: fast-huffman decode initialized incorrectly for sparse codebooks; fixing gives 10% speedup - found by Terje Mathisen - 0.995 - bugfix: fix to 'effective' overrun detection - found by Terje Mathisen - 0.994 - bugfix: garbage decode on final VQ symbol of a non-multiple - found by Terje Mathisen - 0.993 - bugfix: pushdata API required 1 extra byte for empty page (failed to consume final page if empty) - found by Terje Mathisen - 0.992 - fixes for MinGW warning - 0.991 - turn fast-float-conversion on by default - 0.990 - fix push-mode seek recovery if you seek into the headers - 0.98b - fix to bad release of 0.98 - 0.98 - fix push-mode seek recovery; robustify float-to-int and support non-fast mode - 0.97 - builds under c++ (typecasting, don't use 'class' keyword) - 0.96 - somehow MY 0.95 was right, but the web one was wrong, so here's my 0.95 rereleased as 0.96, fixes a typo in the clamping code - 0.95 - clamping code for 16-bit functions - 0.94 - not publically released - 0.93 - fixed all-zero-floor case (was decoding garbage) - 0.92 - fixed a memory leak - 0.91 - conditional compiles to omit parts of the API and the infrastructure to support them: STB_VORBIS_NO_PULLDATA_API, STB_VORBIS_NO_PUSHDATA_API, STB_VORBIS_NO_STDIO, STB_VORBIS_NO_INTEGER_CONVERSION - 0.90 - first public release -*/ - -#endif // STB_VORBIS_HEADER_ONLY - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -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. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -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 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. ------------------------------------------------------------------------------- -*/ diff --git a/thirdparty/misc/stb_vorbis.h b/thirdparty/misc/stb_vorbis.h deleted file mode 100644 index 357efcd5fc..0000000000 --- a/thirdparty/misc/stb_vorbis.h +++ /dev/null @@ -1,2 +0,0 @@ -#define STB_VORBIS_HEADER_ONLY -#include "stb_vorbis.c" diff --git a/thirdparty/msdfgen/CHANGELOG.md b/thirdparty/msdfgen/CHANGELOG.md new file mode 100644 index 0000000000..4b9a752650 --- /dev/null +++ b/thirdparty/msdfgen/CHANGELOG.md @@ -0,0 +1,82 @@ + +## Version 1.9 (2021-05-28) + +- Error correction of multi-channel distance fields has been completely reworked +- Added new edge coloring strategy that optimizes colors based on distances between edges +- Added some minor functions for the library API +- Minor code refactor and optimizations + +## Version 1.8 (2020-10-17) + +- Integrated the Skia library into the project, which is used to preprocess the shape geometry and eliminate any self-intersections and other irregularities previously unsupported by the software + - The scanline pass and overlapping contour mode is made obsolete by this step and has been disabled by default. The preprocess step can be disabled by the new `-nopreprocess` switch and the former enabled by `-scanline` and `-overlap` respectively. + - The project can be built without the Skia library, forgoing the geometry preprocessing feature. This is controlled by the macro definition `MSDFGEN_USE_SKIA` +- Significantly improved performance of the core algorithm by reusing results from previously computed pixels +- Introduced an additional error correction routine which eliminates MSDF artifacts by analytically predicting results of bilinear interpolation +- Added the possibility to load font glyphs by their index rather than a Unicode value (use the prefix `g` before the character code in `-font` argument) +- Added `-distanceshift` argument that can be used to adjust the center of the distance range in the output distance field +- Fixed several errors in the evaluation of curve distances +- Fixed an issue with paths containing convergent corners (those whose inner angle is zero) +- The algorithm for pseudo-distance computation slightly changed, fixing certain rare edge cases and improving consistency +- Added the ability to supply own `FT_Face` handle to the msdfgen library +- Minor refactor of the core algorithm + +### Version 1.7.1 (2020-03-09) + +- Fixed an edge case bug in scanline rasterization + +## Version 1.7 (2020-03-07) + +- Added `mtsdf` mode - a combination of `msdf` with `sdf` in the alpha channel +- Distance fields can now be stored as uncompressed TIFF image files with floating point precision +- Bitmap class refactor - template argument split into data type and number of channels, bitmap reference classes introduced +- Added a secondary "ink trap" edge coloring heuristic, can be selected using `-coloringstrategy inktrap` +- Added computation of estimated rendering error for a given SDF +- Added computation of bounding box that includes sharp mitered corners +- The API for bounds computation of the `Shape` class changed for clarity +- Fixed several edge case bugs + +## Version 1.6 (2019-04-08) + +- Core algorithm rewritten to split up advanced edge selection logic into modular template arguments. +- Pseudo-distance evaluation reworked to eliminate discontinuities at the midpoint between edges. +- MSDF error correction reworked to also fix distances away from edges and consider diagonal pairs. Code simplified. +- Added scanline rasterization support for `Shape`. +- Added a scanline pass in the standalone version, which corrects the signs in the distance field according to the selected fill rule (`-fillrule`). Can be disabled using `-noscanline`. +- Fixed autoframe scaling, which previously caused the output to have unnecessary empty border. +- `-guessorder` switch no longer enabled by default, as the functionality is now provided by the scanline pass. +- Updated FreeType and other libraries, changed to static linkage +- Added 64-bit and static library builds to the Visual Studio solution + +## Version 1.5 (2017-07-23) + +- Fixed rounding error in cubic curve splitting. +- SVG parser fixes and support for additional path commands. +- Added CMake build script. + +## Version 1.4 (2017-02-09) + +- Reworked contour combining logic to support overlapping contours. Original algorithm preserved in functions with `_legacy` suffix, which are invoked by the new `-legacy` switch. +- Fixed a severe bug in cubic curve distance computation, where a control point lies at the endpoint. +- Standalone version now automatically detects if the input has the wrong orientation and adjusts the distance field accordingly. Can be disabled by `-keeporder` or `-reverseorder` switch. +- SVG parser fixes and improvements. + +## Version 1.3 (2016-12-07) + +- Fixed `-reverseorder` switch. +- Fixed glyph loading to use the proper method of acquiring outlines from FreeType. + +## Version 1.2 (2016-07-20) + +- Added option to specify that shape vertices are listed in reverse order (`-reverseorder`). +- Added option to set a seed for the edge coloring heuristic (-seed \<n\>), which can be used to adjust the output. +- Fixed parsing of glyph contours that start with a curve control point. + +## Version 1.1 (2016-05-08) + +- Switched to MIT license due to popular demand. +- Fixed SDF rendering anti-aliasing when the output is smaller than the distance field. + +## Version 1.0 (2016-04-28) + +- Project published. diff --git a/thirdparty/msdfgen/LICENSE.txt b/thirdparty/msdfgen/LICENSE.txt new file mode 100644 index 0000000000..5fb05446bc --- /dev/null +++ b/thirdparty/msdfgen/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Viktor Chlumsky + +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. diff --git a/thirdparty/msdfgen/core/Bitmap.h b/thirdparty/msdfgen/core/Bitmap.h new file mode 100644 index 0000000000..14407d6c34 --- /dev/null +++ b/thirdparty/msdfgen/core/Bitmap.h @@ -0,0 +1,50 @@ + +#pragma once + +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// A 2D image bitmap with N channels of type T. Pixel memory is managed by the class. +template <typename T, int N = 1> +class Bitmap { + +public: + Bitmap(); + Bitmap(int width, int height); + Bitmap(const BitmapConstRef<T, N> &orig); + Bitmap(const Bitmap<T, N> &orig); +#ifdef MSDFGEN_USE_CPP11 + Bitmap(Bitmap<T, N> &&orig); +#endif + ~Bitmap(); + Bitmap<T, N> & operator=(const BitmapConstRef<T, N> &orig); + Bitmap<T, N> & operator=(const Bitmap<T, N> &orig); +#ifdef MSDFGEN_USE_CPP11 + Bitmap<T, N> & operator=(Bitmap<T, N> &&orig); +#endif + /// Bitmap width in pixels. + int width() const; + /// Bitmap height in pixels. + int height() const; + T * operator()(int x, int y); + const T * operator()(int x, int y) const; +#ifdef MSDFGEN_USE_CPP11 + explicit operator T *(); + explicit operator const T *() const; +#else + operator T *(); + operator const T *() const; +#endif + operator BitmapRef<T, N>(); + operator BitmapConstRef<T, N>() const; + +private: + T *pixels; + int w, h; + +}; + +} + +#include "Bitmap.hpp" diff --git a/thirdparty/msdfgen/core/Bitmap.hpp b/thirdparty/msdfgen/core/Bitmap.hpp new file mode 100644 index 0000000000..cb16cac8d4 --- /dev/null +++ b/thirdparty/msdfgen/core/Bitmap.hpp @@ -0,0 +1,117 @@ + +#include "Bitmap.h" + +#include <cstdlib> +#include <cstring> + +namespace msdfgen { + +template <typename T, int N> +Bitmap<T, N>::Bitmap() : pixels(NULL), w(0), h(0) { } + +template <typename T, int N> +Bitmap<T, N>::Bitmap(int width, int height) : w(width), h(height) { + pixels = new T[N*w*h]; +} + +template <typename T, int N> +Bitmap<T, N>::Bitmap(const BitmapConstRef<T, N> &orig) : w(orig.width), h(orig.height) { + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); +} + +template <typename T, int N> +Bitmap<T, N>::Bitmap(const Bitmap<T, N> &orig) : w(orig.w), h(orig.h) { + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); +} + +#ifdef MSDFGEN_USE_CPP11 +template <typename T, int N> +Bitmap<T, N>::Bitmap(Bitmap<T, N> &&orig) : pixels(orig.pixels), w(orig.w), h(orig.h) { + orig.pixels = NULL; + orig.w = 0, orig.h = 0; +} +#endif + +template <typename T, int N> +Bitmap<T, N>::~Bitmap() { + delete [] pixels; +} + +template <typename T, int N> +Bitmap<T, N> & Bitmap<T, N>::operator=(const BitmapConstRef<T, N> &orig) { + if (pixels != orig.pixels) { + delete [] pixels; + w = orig.width, h = orig.height; + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); + } + return *this; +} + +template <typename T, int N> +Bitmap<T, N> & Bitmap<T, N>::operator=(const Bitmap<T, N> &orig) { + if (this != &orig) { + delete [] pixels; + w = orig.w, h = orig.h; + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); + } + return *this; +} + +#ifdef MSDFGEN_USE_CPP11 +template <typename T, int N> +Bitmap<T, N> & Bitmap<T, N>::operator=(Bitmap<T, N> &&orig) { + if (this != &orig) { + delete [] pixels; + pixels = orig.pixels; + w = orig.w, h = orig.h; + orig.pixels = NULL; + } + return *this; +} +#endif + +template <typename T, int N> +int Bitmap<T, N>::width() const { + return w; +} + +template <typename T, int N> +int Bitmap<T, N>::height() const { + return h; +} + +template <typename T, int N> +T * Bitmap<T, N>::operator()(int x, int y) { + return pixels+N*(w*y+x); +} + +template <typename T, int N> +const T * Bitmap<T, N>::operator()(int x, int y) const { + return pixels+N*(w*y+x); +} + +template <typename T, int N> +Bitmap<T, N>::operator T *() { + return pixels; +} + +template <typename T, int N> +Bitmap<T, N>::operator const T *() const { + return pixels; +} + +template <typename T, int N> +Bitmap<T, N>::operator BitmapRef<T, N>() { + return BitmapRef<T, N>(pixels, w, h); +} + +template <typename T, int N> +Bitmap<T, N>::operator BitmapConstRef<T, N>() const { + return BitmapConstRef<T, N>(pixels, w, h); +} + +} diff --git a/thirdparty/msdfgen/core/BitmapRef.hpp b/thirdparty/msdfgen/core/BitmapRef.hpp new file mode 100644 index 0000000000..6f9620dcdf --- /dev/null +++ b/thirdparty/msdfgen/core/BitmapRef.hpp @@ -0,0 +1,43 @@ + +#pragma once + +#include <cstdlib> + +namespace msdfgen { + +typedef unsigned char byte; + +/// Reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object. +template <typename T, int N = 1> +struct BitmapRef { + + T *pixels; + int width, height; + + inline BitmapRef() : pixels(NULL), width(0), height(0) { } + inline BitmapRef(T *pixels, int width, int height) : pixels(pixels), width(width), height(height) { } + + inline T * operator()(int x, int y) const { + return pixels+N*(width*y+x); + } + +}; + +/// Constant reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object. +template <typename T, int N = 1> +struct BitmapConstRef { + + const T *pixels; + int width, height; + + inline BitmapConstRef() : pixels(NULL), width(0), height(0) { } + inline BitmapConstRef(const T *pixels, int width, int height) : pixels(pixels), width(width), height(height) { } + inline BitmapConstRef(const BitmapRef<T, N> &orig) : pixels(orig.pixels), width(orig.width), height(orig.height) { } + + inline const T * operator()(int x, int y) const { + return pixels+N*(width*y+x); + } + +}; + +} diff --git a/thirdparty/msdfgen/core/Contour.cpp b/thirdparty/msdfgen/core/Contour.cpp new file mode 100644 index 0000000000..ca80d3c55a --- /dev/null +++ b/thirdparty/msdfgen/core/Contour.cpp @@ -0,0 +1,90 @@ + +#include "Contour.h" + +#include "arithmetics.hpp" + +namespace msdfgen { + +static double shoelace(const Point2 &a, const Point2 &b) { + return (b.x-a.x)*(a.y+b.y); +} + +void Contour::addEdge(const EdgeHolder &edge) { + edges.push_back(edge); +} + +#ifdef MSDFGEN_USE_CPP11 +void Contour::addEdge(EdgeHolder &&edge) { + edges.push_back((EdgeHolder &&) edge); +} +#endif + +EdgeHolder & Contour::addEdge() { + edges.resize(edges.size()+1); + return edges.back(); +} + +static void boundPoint(double &l, double &b, double &r, double &t, Point2 p) { + if (p.x < l) l = p.x; + if (p.y < b) b = p.y; + if (p.x > r) r = p.x; + if (p.y > t) t = p.y; +} + +void Contour::bound(double &l, double &b, double &r, double &t) const { + for (std::vector<EdgeHolder>::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) + (*edge)->bound(l, b, r, t); +} + +void Contour::boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const { + if (edges.empty()) + return; + Vector2 prevDir = edges.back()->direction(1).normalize(true); + for (std::vector<EdgeHolder>::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) { + Vector2 dir = -(*edge)->direction(0).normalize(true); + if (polarity*crossProduct(prevDir, dir) >= 0) { + double miterLength = miterLimit; + double q = .5*(1-dotProduct(prevDir, dir)); + if (q > 0) + miterLength = min(1/sqrt(q), miterLimit); + Point2 miter = (*edge)->point(0)+border*miterLength*(prevDir+dir).normalize(true); + boundPoint(l, b, r, t, miter); + } + prevDir = (*edge)->direction(1).normalize(true); + } +} + +int Contour::winding() const { + if (edges.empty()) + return 0; + double total = 0; + if (edges.size() == 1) { + Point2 a = edges[0]->point(0), b = edges[0]->point(1/3.), c = edges[0]->point(2/3.); + total += shoelace(a, b); + total += shoelace(b, c); + total += shoelace(c, a); + } else if (edges.size() == 2) { + Point2 a = edges[0]->point(0), b = edges[0]->point(.5), c = edges[1]->point(0), d = edges[1]->point(.5); + total += shoelace(a, b); + total += shoelace(b, c); + total += shoelace(c, d); + total += shoelace(d, a); + } else { + Point2 prev = edges.back()->point(0); + for (std::vector<EdgeHolder>::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) { + Point2 cur = (*edge)->point(0); + total += shoelace(prev, cur); + prev = cur; + } + } + return sign(total); +} + +void Contour::reverse() { + for (int i = (int) edges.size()/2; i > 0; --i) + EdgeHolder::swap(edges[i-1], edges[edges.size()-i]); + for (std::vector<EdgeHolder>::iterator edge = edges.begin(); edge != edges.end(); ++edge) + (*edge)->reverse(); +} + +} diff --git a/thirdparty/msdfgen/core/Contour.h b/thirdparty/msdfgen/core/Contour.h new file mode 100644 index 0000000000..f79b269582 --- /dev/null +++ b/thirdparty/msdfgen/core/Contour.h @@ -0,0 +1,34 @@ + +#pragma once + +#include <vector> +#include "EdgeHolder.h" + +namespace msdfgen { + +/// A single closed contour of a shape. +class Contour { + +public: + /// The sequence of edges that make up the contour. + std::vector<EdgeHolder> edges; + + /// Adds an edge to the contour. + void addEdge(const EdgeHolder &edge); +#ifdef MSDFGEN_USE_CPP11 + void addEdge(EdgeHolder &&edge); +#endif + /// Creates a new edge in the contour and returns its reference. + EdgeHolder & addEdge(); + /// Adjusts the bounding box to fit the contour. + void bound(double &l, double &b, double &r, double &t) const; + /// Adjusts the bounding box to fit the contour border's mitered corners. + void boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const; + /// Computes the winding of the contour. Returns 1 if positive, -1 if negative. + int winding() const; + /// Reverses the sequence of edges on the contour. + void reverse(); + +}; + +} diff --git a/thirdparty/msdfgen/core/EdgeColor.h b/thirdparty/msdfgen/core/EdgeColor.h new file mode 100644 index 0000000000..9d49a5a89e --- /dev/null +++ b/thirdparty/msdfgen/core/EdgeColor.h @@ -0,0 +1,18 @@ + +#pragma once + +namespace msdfgen { + +/// Edge color specifies which color channels an edge belongs to. +enum EdgeColor { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7 +}; + +} diff --git a/thirdparty/msdfgen/core/EdgeHolder.cpp b/thirdparty/msdfgen/core/EdgeHolder.cpp new file mode 100644 index 0000000000..1a8c5f66e9 --- /dev/null +++ b/thirdparty/msdfgen/core/EdgeHolder.cpp @@ -0,0 +1,77 @@ + +#include "EdgeHolder.h" + +namespace msdfgen { + +void EdgeHolder::swap(EdgeHolder &a, EdgeHolder &b) { + EdgeSegment *tmp = a.edgeSegment; + a.edgeSegment = b.edgeSegment; + b.edgeSegment = tmp; +} + +EdgeHolder::EdgeHolder() : edgeSegment(NULL) { } + +EdgeHolder::EdgeHolder(EdgeSegment *segment) : edgeSegment(segment) { } + +EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor) : edgeSegment(new LinearSegment(p0, p1, edgeColor)) { } + +EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) : edgeSegment(new QuadraticSegment(p0, p1, p2, edgeColor)) { } + +EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) : edgeSegment(new CubicSegment(p0, p1, p2, p3, edgeColor)) { } + +EdgeHolder::EdgeHolder(const EdgeHolder &orig) : edgeSegment(orig.edgeSegment ? orig.edgeSegment->clone() : NULL) { } + +#ifdef MSDFGEN_USE_CPP11 +EdgeHolder::EdgeHolder(EdgeHolder &&orig) : edgeSegment(orig.edgeSegment) { + orig.edgeSegment = NULL; +} +#endif + +EdgeHolder::~EdgeHolder() { + delete edgeSegment; +} + +EdgeHolder & EdgeHolder::operator=(const EdgeHolder &orig) { + if (this != &orig) { + delete edgeSegment; + edgeSegment = orig.edgeSegment ? orig.edgeSegment->clone() : NULL; + } + return *this; +} + +#ifdef MSDFGEN_USE_CPP11 +EdgeHolder & EdgeHolder::operator=(EdgeHolder &&orig) { + if (this != &orig) { + delete edgeSegment; + edgeSegment = orig.edgeSegment; + orig.edgeSegment = NULL; + } + return *this; +} +#endif + +EdgeSegment & EdgeHolder::operator*() { + return *edgeSegment; +} + +const EdgeSegment & EdgeHolder::operator*() const { + return *edgeSegment; +} + +EdgeSegment * EdgeHolder::operator->() { + return edgeSegment; +} + +const EdgeSegment * EdgeHolder::operator->() const { + return edgeSegment; +} + +EdgeHolder::operator EdgeSegment *() { + return edgeSegment; +} + +EdgeHolder::operator const EdgeSegment *() const { + return edgeSegment; +} + +} diff --git a/thirdparty/msdfgen/core/EdgeHolder.h b/thirdparty/msdfgen/core/EdgeHolder.h new file mode 100644 index 0000000000..c4c5be7616 --- /dev/null +++ b/thirdparty/msdfgen/core/EdgeHolder.h @@ -0,0 +1,41 @@ + +#pragma once + +#include "edge-segments.h" + +namespace msdfgen { + +/// Container for a single edge of dynamic type. +class EdgeHolder { + +public: + /// Swaps the edges held by a and b. + static void swap(EdgeHolder &a, EdgeHolder &b); + + EdgeHolder(); + EdgeHolder(EdgeSegment *segment); + EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor = WHITE); + EdgeHolder(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor = WHITE); + EdgeHolder(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor = WHITE); + EdgeHolder(const EdgeHolder &orig); +#ifdef MSDFGEN_USE_CPP11 + EdgeHolder(EdgeHolder &&orig); +#endif + ~EdgeHolder(); + EdgeHolder & operator=(const EdgeHolder &orig); +#ifdef MSDFGEN_USE_CPP11 + EdgeHolder & operator=(EdgeHolder &&orig); +#endif + EdgeSegment & operator*(); + const EdgeSegment & operator*() const; + EdgeSegment * operator->(); + const EdgeSegment * operator->() const; + operator EdgeSegment *(); + operator const EdgeSegment *() const; + +private: + EdgeSegment *edgeSegment; + +}; + +} diff --git a/thirdparty/msdfgen/core/MSDFErrorCorrection.cpp b/thirdparty/msdfgen/core/MSDFErrorCorrection.cpp new file mode 100644 index 0000000000..7918597fd2 --- /dev/null +++ b/thirdparty/msdfgen/core/MSDFErrorCorrection.cpp @@ -0,0 +1,495 @@ + +#include "MSDFErrorCorrection.h" + +#include <cstring> +#include "arithmetics.hpp" +#include "equation-solver.h" +#include "EdgeColor.h" +#include "bitmap-interpolation.hpp" +#include "edge-selectors.h" +#include "contour-combiners.h" +#include "ShapeDistanceFinder.h" +#include "generator-config.h" + +namespace msdfgen { + +#define ARTIFACT_T_EPSILON .01 +#define PROTECTION_RADIUS_TOLERANCE 1.001 + +#define CLASSIFIER_FLAG_CANDIDATE 0x01 +#define CLASSIFIER_FLAG_ARTIFACT 0x02 + +const double ErrorCorrectionConfig::defaultMinDeviationRatio = 1.11111111111111111; +const double ErrorCorrectionConfig::defaultMinImproveRatio = 1.11111111111111111; + +/// The base artifact classifier recognizes artifacts based on the contents of the SDF alone. +class BaseArtifactClassifier { +public: + inline BaseArtifactClassifier(double span, bool protectedFlag) : span(span), protectedFlag(protectedFlag) { } + /// Evaluates if the median value xm interpolated at xt in the range between am at at and bm at bt indicates an artifact. + inline int rangeTest(double at, double bt, double xt, float am, float bm, float xm) const { + // For protected texels, only consider inversion artifacts (interpolated median has different sign than boundaries). For the rest, it is sufficient that the interpolated median is outside its boundaries. + if ((am > .5f && bm > .5f && xm <= .5f) || (am < .5f && bm < .5f && xm >= .5f) || (!protectedFlag && median(am, bm, xm) != xm)) { + double axSpan = (xt-at)*span, bxSpan = (bt-xt)*span; + // Check if the interpolated median's value is in the expected range based on its distance (span) from boundaries a, b. + if (!(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan)) + return CLASSIFIER_FLAG_CANDIDATE|CLASSIFIER_FLAG_ARTIFACT; + return CLASSIFIER_FLAG_CANDIDATE; + } + return 0; + } + /// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact. + inline bool evaluate(double t, float m, int flags) const { + return (flags&2) != 0; + } +private: + double span; + bool protectedFlag; +}; + +/// The shape distance checker evaluates the exact shape distance to find additional artifacts at a significant performance cost. +template <template <typename> class ContourCombiner, int N> +class ShapeDistanceChecker { +public: + class ArtifactClassifier : public BaseArtifactClassifier { + public: + inline ArtifactClassifier(ShapeDistanceChecker *parent, const Vector2 &direction, double span) : BaseArtifactClassifier(span, parent->protectedFlag), parent(parent), direction(direction) { } + /// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact. + inline bool evaluate(double t, float m, int flags) const { + if (flags&CLASSIFIER_FLAG_CANDIDATE) { + // Skip expensive distance evaluation if the point has already been classified as an artifact by the base classifier. + if (flags&CLASSIFIER_FLAG_ARTIFACT) + return true; + Vector2 tVector = t*direction; + float oldMSD[N], newMSD[3]; + // Compute the color that would be currently interpolated at the artifact candidate's position. + Point2 sdfCoord = parent->sdfCoord+tVector; + interpolate(oldMSD, parent->sdf, sdfCoord); + // Compute the color that would be interpolated at the artifact candidate's position if error correction was applied on the current texel. + double aWeight = (1-fabs(tVector.x))*(1-fabs(tVector.y)); + float aPSD = median(parent->msd[0], parent->msd[1], parent->msd[2]); + newMSD[0] = float(oldMSD[0]+aWeight*(aPSD-parent->msd[0])); + newMSD[1] = float(oldMSD[1]+aWeight*(aPSD-parent->msd[1])); + newMSD[2] = float(oldMSD[2]+aWeight*(aPSD-parent->msd[2])); + // Compute the evaluated distance (interpolated median) before and after error correction, as well as the exact shape distance. + float oldPSD = median(oldMSD[0], oldMSD[1], oldMSD[2]); + float newPSD = median(newMSD[0], newMSD[1], newMSD[2]); + float refPSD = float(parent->invRange*parent->distanceFinder.distance(parent->shapeCoord+tVector*parent->texelSize)+.5); + // Compare the differences of the exact distance and the before and after distances. + return parent->minImproveRatio*fabsf(newPSD-refPSD) < double(fabsf(oldPSD-refPSD)); + } + return false; + } + private: + ShapeDistanceChecker *parent; + Vector2 direction; + }; + Point2 shapeCoord, sdfCoord; + const float *msd; + bool protectedFlag; + inline ShapeDistanceChecker(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Projection &projection, double invRange, double minImproveRatio) : distanceFinder(shape), sdf(sdf), invRange(invRange), minImproveRatio(minImproveRatio) { + texelSize = projection.unprojectVector(Vector2(1)); + } + inline ArtifactClassifier classifier(const Vector2 &direction, double span) { + return ArtifactClassifier(this, direction, span); + } +private: + ShapeDistanceFinder<ContourCombiner<PseudoDistanceSelector> > distanceFinder; + BitmapConstRef<float, N> sdf; + double invRange; + Vector2 texelSize; + double minImproveRatio; +}; + +MSDFErrorCorrection::MSDFErrorCorrection() { } + +MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const Projection &projection, double range) : stencil(stencil), projection(projection) { + invRange = 1/range; + minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio; + minImproveRatio = ErrorCorrectionConfig::defaultMinImproveRatio; + memset(stencil.pixels, 0, sizeof(byte)*stencil.width*stencil.height); +} + +void MSDFErrorCorrection::setMinDeviationRatio(double minDeviationRatio) { + this->minDeviationRatio = minDeviationRatio; +} + +void MSDFErrorCorrection::setMinImproveRatio(double minImproveRatio) { + this->minImproveRatio = minImproveRatio; +} + +void MSDFErrorCorrection::protectCorners(const Shape &shape) { + for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) + if (!contour->edges.empty()) { + const EdgeSegment *prevEdge = contour->edges.back(); + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + int commonColor = prevEdge->color&(*edge)->color; + // If the color changes from prevEdge to edge, this is a corner. + if (!(commonColor&(commonColor-1))) { + // Find the four texels that envelop the corner and mark them as protected. + Point2 p = projection.project((*edge)->point(0)); + if (shape.inverseYAxis) + p.y = stencil.height-p.y; + int l = (int) floor(p.x-.5); + int b = (int) floor(p.y-.5); + int r = l+1; + int t = b+1; + // Check that the positions are within bounds. + if (l < stencil.width && b < stencil.height && r >= 0 && t >= 0) { + if (l >= 0 && b >= 0) + *stencil(l, b) |= (byte) PROTECTED; + if (r < stencil.width && b >= 0) + *stencil(r, b) |= (byte) PROTECTED; + if (l >= 0 && t < stencil.height) + *stencil(l, t) |= (byte) PROTECTED; + if (r < stencil.width && t < stencil.height) + *stencil(r, t) |= (byte) PROTECTED; + } + } + prevEdge = *edge; + } + } +} + +/// Determines if the channel contributes to an edge between the two texels a, b. +static bool edgeBetweenTexelsChannel(const float *a, const float *b, int channel) { + // Find interpolation ratio t (0 < t < 1) where an edge is expected (mix(a[channel], b[channel], t) == 0.5). + double t = (a[channel]-.5)/(a[channel]-b[channel]); + if (t > 0 && t < 1) { + // Interpolate channel values at t. + float c[3] = { + mix(a[0], b[0], t), + mix(a[1], b[1], t), + mix(a[2], b[2], t) + }; + // This is only an edge if the zero-distance channel is the median. + return median(c[0], c[1], c[2]) == c[channel]; + } + return false; +} + +/// Returns a bit mask of which channels contribute to an edge between the two texels a, b. +static int edgeBetweenTexels(const float *a, const float *b) { + return ( + RED*edgeBetweenTexelsChannel(a, b, 0)+ + GREEN*edgeBetweenTexelsChannel(a, b, 1)+ + BLUE*edgeBetweenTexelsChannel(a, b, 2) + ); +} + +/// Marks texel as protected if one of its non-median channels is present in the channel mask. +static void protectExtremeChannels(byte *stencil, const float *msd, float m, int mask) { + if ( + (mask&RED && msd[0] != m) || + (mask&GREEN && msd[1] != m) || + (mask&BLUE && msd[2] != m) + ) + *stencil |= (byte) MSDFErrorCorrection::PROTECTED; +} + +template <int N> +void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) { + float radius; + // Horizontal texel pairs + radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange, 0)).length()); + for (int y = 0; y < sdf.height; ++y) { + const float *left = sdf(0, y); + const float *right = sdf(1, y); + for (int x = 0; x < sdf.width-1; ++x) { + float lm = median(left[0], left[1], left[2]); + float rm = median(right[0], right[1], right[2]); + if (fabsf(lm-.5f)+fabsf(rm-.5f) < radius) { + int mask = edgeBetweenTexels(left, right); + protectExtremeChannels(stencil(x, y), left, lm, mask); + protectExtremeChannels(stencil(x+1, y), right, rm, mask); + } + left += N, right += N; + } + } + // Vertical texel pairs + radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(0, invRange)).length()); + for (int y = 0; y < sdf.height-1; ++y) { + const float *bottom = sdf(0, y); + const float *top = sdf(0, y+1); + for (int x = 0; x < sdf.width; ++x) { + float bm = median(bottom[0], bottom[1], bottom[2]); + float tm = median(top[0], top[1], top[2]); + if (fabsf(bm-.5f)+fabsf(tm-.5f) < radius) { + int mask = edgeBetweenTexels(bottom, top); + protectExtremeChannels(stencil(x, y), bottom, bm, mask); + protectExtremeChannels(stencil(x, y+1), top, tm, mask); + } + bottom += N, top += N; + } + } + // Diagonal texel pairs + radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange)).length()); + for (int y = 0; y < sdf.height-1; ++y) { + const float *lb = sdf(0, y); + const float *rb = sdf(1, y); + const float *lt = sdf(0, y+1); + const float *rt = sdf(1, y+1); + for (int x = 0; x < sdf.width-1; ++x) { + float mlb = median(lb[0], lb[1], lb[2]); + float mrb = median(rb[0], rb[1], rb[2]); + float mlt = median(lt[0], lt[1], lt[2]); + float mrt = median(rt[0], rt[1], rt[2]); + if (fabsf(mlb-.5f)+fabsf(mrt-.5f) < radius) { + int mask = edgeBetweenTexels(lb, rt); + protectExtremeChannels(stencil(x, y), lb, mlb, mask); + protectExtremeChannels(stencil(x+1, y+1), rt, mrt, mask); + } + if (fabsf(mrb-.5f)+fabsf(mlt-.5f) < radius) { + int mask = edgeBetweenTexels(rb, lt); + protectExtremeChannels(stencil(x+1, y), rb, mrb, mask); + protectExtremeChannels(stencil(x, y+1), lt, mlt, mask); + } + lb += N, rb += N, lt += N, rt += N; + } + } +} + +void MSDFErrorCorrection::protectAll() { + byte *end = stencil.pixels+stencil.width*stencil.height; + for (byte *mask = stencil.pixels; mask < end; ++mask) + *mask |= (byte) PROTECTED; +} + +/// Returns the median of the linear interpolation of texels a, b at t. +static float interpolatedMedian(const float *a, const float *b, double t) { + return median( + mix(a[0], b[0], t), + mix(a[1], b[1], t), + mix(a[2], b[2], t) + ); +} +/// Returns the median of the bilinear interpolation with the given constant, linear, and quadratic terms at t. +static float interpolatedMedian(const float *a, const float *l, const float *q, double t) { + return float(median( + t*(t*q[0]+l[0])+a[0], + t*(t*q[1]+l[1])+a[1], + t*(t*q[2]+l[2])+a[2] + )); +} + +/// Determines if the interpolated median xm is an artifact. +static bool isArtifact(bool isProtected, double axSpan, double bxSpan, float am, float bm, float xm) { + return ( + // For protected texels, only report an artifact if it would cause fill inversion (change between positive and negative distance). + (!isProtected || (am > .5f && bm > .5f && xm <= .5f) || (am < .5f && bm < .5f && xm >= .5f)) && + // This is an artifact if the interpolated median is outside the range of possible values based on its distance from a, b. + !(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan) + ); +} + +/// Checks if a linear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values. +template <class ArtifactClassifier> +static bool hasLinearArtifactInner(const ArtifactClassifier &artifactClassifier, float am, float bm, const float *a, const float *b, float dA, float dB) { + // Find interpolation ratio t (0 < t < 1) where two color channels are equal (mix(dA, dB, t) == 0). + double t = (double) dA/(dA-dB); + if (t > ARTIFACT_T_EPSILON && t < 1-ARTIFACT_T_EPSILON) { + // Interpolate median at t and let the classifier decide if its value indicates an artifact. + float xm = interpolatedMedian(a, b, t); + return artifactClassifier.evaluate(t, xm, artifactClassifier.rangeTest(0, 1, t, am, bm, xm)); + } + return false; +} + +/// Checks if a bilinear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values. +template <class ArtifactClassifier> +static bool hasDiagonalArtifactInner(const ArtifactClassifier &artifactClassifier, float am, float dm, const float *a, const float *l, const float *q, float dA, float dBC, float dD, double tEx0, double tEx1) { + // Find interpolation ratios t (0 < t[i] < 1) where two color channels are equal. + double t[2]; + int solutions = solveQuadratic(t, dD-dBC+dA, dBC-dA-dA, dA); + for (int i = 0; i < solutions; ++i) { + // Solutions t[i] == 0 and t[i] == 1 are singularities and occur very often because two channels are usually equal at texels. + if (t[i] > ARTIFACT_T_EPSILON && t[i] < 1-ARTIFACT_T_EPSILON) { + // Interpolate median xm at t. + float xm = interpolatedMedian(a, l, q, t[i]); + // Determine if xm deviates too much from medians of a, d. + int rangeFlags = artifactClassifier.rangeTest(0, 1, t[i], am, dm, xm); + // Additionally, check xm against the interpolated medians at the local extremes tEx0, tEx1. + double tEnd[2]; + float em[2]; + // tEx0 + if (tEx0 > 0 && tEx0 < 1) { + tEnd[0] = 0, tEnd[1] = 1; + em[0] = am, em[1] = dm; + tEnd[tEx0 > t[i]] = tEx0; + em[tEx0 > t[i]] = interpolatedMedian(a, l, q, tEx0); + rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], am, dm, xm); + } + // tEx1 + if (tEx1 > 0 && tEx1 < 1) { + tEnd[0] = 0, tEnd[1] = 1; + em[0] = am, em[1] = dm; + tEnd[tEx1 > t[i]] = tEx1; + em[tEx1 > t[i]] = interpolatedMedian(a, l, q, tEx1); + rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], am, dm, xm); + } + if (artifactClassifier.evaluate(t[i], xm, rangeFlags)) + return true; + } + } + return false; +} + +/// Checks if a linear interpolation artifact will occur inbetween two horizontally or vertically adjacent texels a, b. +template <class ArtifactClassifier> +static bool hasLinearArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b) { + float bm = median(b[0], b[1], b[2]); + return ( + // Out of the pair, only report artifacts for the texel further from the edge to minimize side effects. + fabsf(am-.5f) >= fabsf(bm-.5f) && ( + // Check points where each pair of color channels meets. + hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[1]-a[0], b[1]-b[0]) || + hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[2]-a[1], b[2]-b[1]) || + hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[0]-a[2], b[0]-b[2]) + ) + ); +} + +/// Checks if a bilinear interpolation artifact will occur inbetween two diagonally adjacent texels a, d (with b, c forming the other diagonal). +template <class ArtifactClassifier> +static bool hasDiagonalArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b, const float *c, const float *d) { + float dm = median(d[0], d[1], d[2]); + // Out of the pair, only report artifacts for the texel further from the edge to minimize side effects. + if (fabsf(am-.5f) >= fabsf(dm-.5f)) { + float abc[3] = { + a[0]-b[0]-c[0], + a[1]-b[1]-c[1], + a[2]-b[2]-c[2] + }; + // Compute the linear terms for bilinear interpolation. + float l[3] = { + -a[0]-abc[0], + -a[1]-abc[1], + -a[2]-abc[2] + }; + // Compute the quadratic terms for bilinear interpolation. + float q[3] = { + d[0]+abc[0], + d[1]+abc[1], + d[2]+abc[2] + }; + // Compute interpolation ratios tEx (0 < tEx[i] < 1) for the local extremes of each color channel (the derivative 2*q[i]*tEx[i]+l[i] == 0). + double tEx[3] = { + -.5*l[0]/q[0], + -.5*l[1]/q[1], + -.5*l[2]/q[2] + }; + // Check points where each pair of color channels meets. + return ( + hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[1]-a[0], b[1]-b[0]+c[1]-c[0], d[1]-d[0], tEx[0], tEx[1]) || + hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[2]-a[1], b[2]-b[1]+c[2]-c[1], d[2]-d[1], tEx[1], tEx[2]) || + hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[0]-a[2], b[0]-b[2]+c[0]-c[2], d[0]-d[2], tEx[2], tEx[0]) + ); + } + return false; +} + +template <int N> +void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf) { + // Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels. + double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length(); + double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length(); + double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length(); + // Inspect all texels. + for (int y = 0; y < sdf.height; ++y) { + for (int x = 0; x < sdf.width; ++x) { + const float *c = sdf(x, y); + float cm = median(c[0], c[1], c[2]); + bool protectedFlag = (*stencil(x, y)&PROTECTED) != 0; + const float *l = NULL, *b = NULL, *r = NULL, *t = NULL; + // Mark current texel c with the error flag if an artifact occurs when it's interpolated with any of its 8 neighbors. + *stencil(x, y) |= (byte) (ERROR*( + (x > 0 && ((l = sdf(x-1, y)), hasLinearArtifact(BaseArtifactClassifier(hSpan, protectedFlag), cm, c, l))) || + (y > 0 && ((b = sdf(x, y-1)), hasLinearArtifact(BaseArtifactClassifier(vSpan, protectedFlag), cm, c, b))) || + (x < sdf.width-1 && ((r = sdf(x+1, y)), hasLinearArtifact(BaseArtifactClassifier(hSpan, protectedFlag), cm, c, r))) || + (y < sdf.height-1 && ((t = sdf(x, y+1)), hasLinearArtifact(BaseArtifactClassifier(vSpan, protectedFlag), cm, c, t))) || + (x > 0 && y > 0 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, l, b, sdf(x-1, y-1))) || + (x < sdf.width-1 && y > 0 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, r, b, sdf(x+1, y-1))) || + (x > 0 && y < sdf.height-1 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, l, t, sdf(x-1, y+1))) || + (x < sdf.width-1 && y < sdf.height-1 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, r, t, sdf(x+1, y+1))) + )); + } + } +} + +template <template <typename> class ContourCombiner, int N> +void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const Shape &shape) { + // Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels. + double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length(); + double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length(); + double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length(); +#ifdef MSDFGEN_USE_OPENMP + #pragma omp parallel +#endif + { + ShapeDistanceChecker<ContourCombiner, N> shapeDistanceChecker(sdf, shape, projection, invRange, minImproveRatio); + bool rightToLeft = false; + // Inspect all texels. +#ifdef MSDFGEN_USE_OPENMP + #pragma omp for +#endif + for (int y = 0; y < sdf.height; ++y) { + int row = shape.inverseYAxis ? sdf.height-y-1 : y; + for (int col = 0; col < sdf.width; ++col) { + int x = rightToLeft ? sdf.width-col-1 : col; + if ((*stencil(x, row)&ERROR)) + continue; + const float *c = sdf(x, row); + shapeDistanceChecker.shapeCoord = projection.unproject(Point2(x+.5, y+.5)); + shapeDistanceChecker.sdfCoord = Point2(x+.5, row+.5); + shapeDistanceChecker.msd = c; + shapeDistanceChecker.protectedFlag = (*stencil(x, row)&PROTECTED) != 0; + float cm = median(c[0], c[1], c[2]); + const float *l = NULL, *b = NULL, *r = NULL, *t = NULL; + // Mark current texel c with the error flag if an artifact occurs when it's interpolated with any of its 8 neighbors. + *stencil(x, row) |= (byte) (ERROR*( + (x > 0 && ((l = sdf(x-1, row)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(-1, 0), hSpan), cm, c, l))) || + (row > 0 && ((b = sdf(x, row-1)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(0, -1), vSpan), cm, c, b))) || + (x < sdf.width-1 && ((r = sdf(x+1, row)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(+1, 0), hSpan), cm, c, r))) || + (row < sdf.height-1 && ((t = sdf(x, row+1)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(0, +1), vSpan), cm, c, t))) || + (x > 0 && row > 0 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(-1, -1), dSpan), cm, c, l, b, sdf(x-1, row-1))) || + (x < sdf.width-1 && row > 0 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(+1, -1), dSpan), cm, c, r, b, sdf(x+1, row-1))) || + (x > 0 && row < sdf.height-1 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(-1, +1), dSpan), cm, c, l, t, sdf(x-1, row+1))) || + (x < sdf.width-1 && row < sdf.height-1 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(+1, +1), dSpan), cm, c, r, t, sdf(x+1, row+1))) + )); + } + } + } +} + +template <int N> +void MSDFErrorCorrection::apply(const BitmapRef<float, N> &sdf) const { + int texelCount = sdf.width*sdf.height; + const byte *mask = stencil.pixels; + float *texel = sdf.pixels; + for (int i = 0; i < texelCount; ++i) { + if (*mask&ERROR) { + // Set all color channels to the median. + float m = median(texel[0], texel[1], texel[2]); + texel[0] = m, texel[1] = m, texel[2] = m; + } + ++mask; + texel += N; + } +} + +BitmapConstRef<byte, 1> MSDFErrorCorrection::getStencil() const { + return stencil; +} + +template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 3> &sdf); +template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 4> &sdf); +template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 3> &sdf); +template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 4> &sdf); +template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape); +template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape); +template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape); +template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape); +template void MSDFErrorCorrection::apply(const BitmapRef<float, 3> &sdf) const; +template void MSDFErrorCorrection::apply(const BitmapRef<float, 4> &sdf) const; + +} diff --git a/thirdparty/msdfgen/core/MSDFErrorCorrection.h b/thirdparty/msdfgen/core/MSDFErrorCorrection.h new file mode 100644 index 0000000000..c2e92fbce7 --- /dev/null +++ b/thirdparty/msdfgen/core/MSDFErrorCorrection.h @@ -0,0 +1,56 @@ + +#pragma once + +#include "Projection.h" +#include "Shape.h" +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// Performs error correction on a computed MSDF to eliminate interpolation artifacts. This is a low-level class, you may want to use the API in msdf-error-correction.h instead. +class MSDFErrorCorrection { + +public: + /// Stencil flags. + enum Flags { + /// Texel marked as potentially causing interpolation errors. + ERROR = 1, + /// Texel marked as protected. Protected texels are only given the error flag if they cause inversion artifacts. + PROTECTED = 2 + }; + + MSDFErrorCorrection(); + explicit MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const Projection &projection, double range); + /// Sets the minimum ratio between the actual and maximum expected distance delta to be considered an error. + void setMinDeviationRatio(double minDeviationRatio); + /// Sets the minimum ratio between the pre-correction distance error and the post-correction distance error. + void setMinImproveRatio(double minImproveRatio); + /// Flags all texels that are interpolated at corners as protected. + void protectCorners(const Shape &shape); + /// Flags all texels that contribute to edges as protected. + template <int N> + void protectEdges(const BitmapConstRef<float, N> &sdf); + /// Flags all texels as protected. + void protectAll(); + /// Flags texels that are expected to cause interpolation artifacts based on analysis of the SDF only. + template <int N> + void findErrors(const BitmapConstRef<float, N> &sdf); + /// Flags texels that are expected to cause interpolation artifacts based on analysis of the SDF and comparison with the exact shape distance. + template <template <typename> class ContourCombiner, int N> + void findErrors(const BitmapConstRef<float, N> &sdf, const Shape &shape); + /// Modifies the MSDF so that all texels with the error flag are converted to single-channel. + template <int N> + void apply(const BitmapRef<float, N> &sdf) const; + /// Returns the stencil in its current state (see Flags). + BitmapConstRef<byte, 1> getStencil() const; + +private: + BitmapRef<byte, 1> stencil; + Projection projection; + double invRange; + double minDeviationRatio; + double minImproveRatio; + +}; + +} diff --git a/thirdparty/msdfgen/core/Projection.cpp b/thirdparty/msdfgen/core/Projection.cpp new file mode 100644 index 0000000000..fa2f5e2592 --- /dev/null +++ b/thirdparty/msdfgen/core/Projection.cpp @@ -0,0 +1,42 @@ + +#include "Projection.h" + +namespace msdfgen { + +Projection::Projection() : scale(1), translate(0) { } + +Projection::Projection(const Vector2 &scale, const Vector2 &translate) : scale(scale), translate(translate) { } + +Point2 Projection::project(const Point2 &coord) const { + return scale*(coord+translate); +} + +Point2 Projection::unproject(const Point2 &coord) const { + return coord/scale-translate; +} + +Vector2 Projection::projectVector(const Vector2 &vector) const { + return scale*vector; +} + +Vector2 Projection::unprojectVector(const Vector2 &vector) const { + return vector/scale; +} + +double Projection::projectX(double x) const { + return scale.x*(x+translate.x); +} + +double Projection::projectY(double y) const { + return scale.y*(y+translate.y); +} + +double Projection::unprojectX(double x) const { + return x/scale.x-translate.x; +} + +double Projection::unprojectY(double y) const { + return y/scale.y-translate.y; +} + +} diff --git a/thirdparty/msdfgen/core/Projection.h b/thirdparty/msdfgen/core/Projection.h new file mode 100644 index 0000000000..7cdb1c307a --- /dev/null +++ b/thirdparty/msdfgen/core/Projection.h @@ -0,0 +1,37 @@ + +#pragma once + +#include "Vector2.h" + +namespace msdfgen { + +/// A transformation from shape coordinates to pixel coordinates. +class Projection { + +public: + Projection(); + Projection(const Vector2 &scale, const Vector2 &translate); + /// Converts the shape coordinate to pixel coordinate. + Point2 project(const Point2 &coord) const; + /// Converts the pixel coordinate to shape coordinate. + Point2 unproject(const Point2 &coord) const; + /// Converts the vector to pixel coordinate space. + Vector2 projectVector(const Vector2 &vector) const; + /// Converts the vector from pixel coordinate space. + Vector2 unprojectVector(const Vector2 &vector) const; + /// Converts the X-coordinate from shape to pixel coordinate space. + double projectX(double x) const; + /// Converts the Y-coordinate from shape to pixel coordinate space. + double projectY(double y) const; + /// Converts the X-coordinate from pixel to shape coordinate space. + double unprojectX(double x) const; + /// Converts the Y-coordinate from pixel to shape coordinate space. + double unprojectY(double y) const; + +private: + Vector2 scale; + Vector2 translate; + +}; + +} diff --git a/thirdparty/msdfgen/core/Scanline.cpp b/thirdparty/msdfgen/core/Scanline.cpp new file mode 100644 index 0000000000..8e5352dbf6 --- /dev/null +++ b/thirdparty/msdfgen/core/Scanline.cpp @@ -0,0 +1,125 @@ + +#include "Scanline.h" + +#include <algorithm> +#include "arithmetics.hpp" + +namespace msdfgen { + +static int compareIntersections(const void *a, const void *b) { + return sign(reinterpret_cast<const Scanline::Intersection *>(a)->x-reinterpret_cast<const Scanline::Intersection *>(b)->x); +} + +bool interpretFillRule(int intersections, FillRule fillRule) { + switch (fillRule) { + case FILL_NONZERO: + return intersections != 0; + case FILL_ODD: + return intersections&1; + case FILL_POSITIVE: + return intersections > 0; + case FILL_NEGATIVE: + return intersections < 0; + } + return false; +} + +double Scanline::overlap(const Scanline &a, const Scanline &b, double xFrom, double xTo, FillRule fillRule) { + double total = 0; + bool aInside = false, bInside = false; + int ai = 0, bi = 0; + double ax = !a.intersections.empty() ? a.intersections[ai].x : xTo; + double bx = !b.intersections.empty() ? b.intersections[bi].x : xTo; + while (ax < xFrom || bx < xFrom) { + double xNext = min(ax, bx); + if (ax == xNext && ai < (int) a.intersections.size()) { + aInside = interpretFillRule(a.intersections[ai].direction, fillRule); + ax = ++ai < (int) a.intersections.size() ? a.intersections[ai].x : xTo; + } + if (bx == xNext && bi < (int) b.intersections.size()) { + bInside = interpretFillRule(b.intersections[bi].direction, fillRule); + bx = ++bi < (int) b.intersections.size() ? b.intersections[bi].x : xTo; + } + } + double x = xFrom; + while (ax < xTo || bx < xTo) { + double xNext = min(ax, bx); + if (aInside == bInside) + total += xNext-x; + if (ax == xNext && ai < (int) a.intersections.size()) { + aInside = interpretFillRule(a.intersections[ai].direction, fillRule); + ax = ++ai < (int) a.intersections.size() ? a.intersections[ai].x : xTo; + } + if (bx == xNext && bi < (int) b.intersections.size()) { + bInside = interpretFillRule(b.intersections[bi].direction, fillRule); + bx = ++bi < (int) b.intersections.size() ? b.intersections[bi].x : xTo; + } + x = xNext; + } + if (aInside == bInside) + total += xTo-x; + return total; +} + +Scanline::Scanline() : lastIndex(0) { } + +void Scanline::preprocess() { + lastIndex = 0; + if (!intersections.empty()) { + qsort(&intersections[0], intersections.size(), sizeof(Intersection), compareIntersections); + int totalDirection = 0; + for (std::vector<Intersection>::iterator intersection = intersections.begin(); intersection != intersections.end(); ++intersection) { + totalDirection += intersection->direction; + intersection->direction = totalDirection; + } + } +} + +void Scanline::setIntersections(const std::vector<Intersection> &intersections) { + this->intersections = intersections; + preprocess(); +} + +#ifdef MSDFGEN_USE_CPP11 +void Scanline::setIntersections(std::vector<Intersection> &&intersections) { + this->intersections = (std::vector<Intersection> &&) intersections; + preprocess(); +} +#endif + +int Scanline::moveTo(double x) const { + if (intersections.empty()) + return -1; + int index = lastIndex; + if (x < intersections[index].x) { + do { + if (index == 0) { + lastIndex = 0; + return -1; + } + --index; + } while (x < intersections[index].x); + } else { + while (index < (int) intersections.size()-1 && x >= intersections[index+1].x) + ++index; + } + lastIndex = index; + return index; +} + +int Scanline::countIntersections(double x) const { + return moveTo(x)+1; +} + +int Scanline::sumIntersections(double x) const { + int index = moveTo(x); + if (index >= 0) + return intersections[index].direction; + return 0; +} + +bool Scanline::filled(double x, FillRule fillRule) const { + return interpretFillRule(sumIntersections(x), fillRule); +} + +} diff --git a/thirdparty/msdfgen/core/Scanline.h b/thirdparty/msdfgen/core/Scanline.h new file mode 100644 index 0000000000..9c8f34044b --- /dev/null +++ b/thirdparty/msdfgen/core/Scanline.h @@ -0,0 +1,55 @@ + +#pragma once + +#include <vector> + +namespace msdfgen { + +/// Fill rule dictates how intersection total is interpreted during rasterization. +enum FillRule { + FILL_NONZERO, + FILL_ODD, // "even-odd" + FILL_POSITIVE, + FILL_NEGATIVE +}; + +/// Resolves the number of intersection into a binary fill value based on fill rule. +bool interpretFillRule(int intersections, FillRule fillRule); + +/// Represents a horizontal scanline intersecting a shape. +class Scanline { + +public: + /// An intersection with the scanline. + struct Intersection { + /// X coordinate. + double x; + /// Normalized Y direction of the oriented edge at the point of intersection. + int direction; + }; + + static double overlap(const Scanline &a, const Scanline &b, double xFrom, double xTo, FillRule fillRule); + + Scanline(); + /// Populates the intersection list. + void setIntersections(const std::vector<Intersection> &intersections); +#ifdef MSDFGEN_USE_CPP11 + void setIntersections(std::vector<Intersection> &&intersections); +#endif + /// Returns the number of intersections left of x. + int countIntersections(double x) const; + /// Returns the total sign of intersections left of x. + int sumIntersections(double x) const; + /// Decides whether the scanline is filled at x based on fill rule. + bool filled(double x, FillRule fillRule) const; + +private: + std::vector<Intersection> intersections; + mutable int lastIndex; + + void preprocess(); + int moveTo(double x) const; + +}; + +} diff --git a/thirdparty/msdfgen/core/Shape.cpp b/thirdparty/msdfgen/core/Shape.cpp new file mode 100644 index 0000000000..8d6f47c807 --- /dev/null +++ b/thirdparty/msdfgen/core/Shape.cpp @@ -0,0 +1,183 @@ + +#include "Shape.h" + +#include <algorithm> +#include "arithmetics.hpp" + +namespace msdfgen { + +Shape::Shape() : inverseYAxis(false) { } + +void Shape::addContour(const Contour &contour) { + contours.push_back(contour); +} + +#ifdef MSDFGEN_USE_CPP11 +void Shape::addContour(Contour &&contour) { + contours.push_back((Contour &&) contour); +} +#endif + +Contour & Shape::addContour() { + contours.resize(contours.size()+1); + return contours.back(); +} + +bool Shape::validate() const { + for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour) { + if (!contour->edges.empty()) { + Point2 corner = contour->edges.back()->point(1); + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + if (!*edge) + return false; + if ((*edge)->point(0) != corner) + return false; + corner = (*edge)->point(1); + } + } + } + return true; +} + +static void deconvergeEdge(EdgeHolder &edgeHolder, int param) { + { + const QuadraticSegment *quadraticSegment = dynamic_cast<const QuadraticSegment *>(&*edgeHolder); + if (quadraticSegment) + edgeHolder = quadraticSegment->convertToCubic(); + } + { + CubicSegment *cubicSegment = dynamic_cast<CubicSegment *>(&*edgeHolder); + if (cubicSegment) + cubicSegment->deconverge(param, MSDFGEN_DECONVERGENCE_FACTOR); + } +} + +void Shape::normalize() { + for (std::vector<Contour>::iterator contour = contours.begin(); contour != contours.end(); ++contour) { + if (contour->edges.size() == 1) { + EdgeSegment *parts[3] = { }; + contour->edges[0]->splitInThirds(parts[0], parts[1], parts[2]); + contour->edges.clear(); + contour->edges.push_back(EdgeHolder(parts[0])); + contour->edges.push_back(EdgeHolder(parts[1])); + contour->edges.push_back(EdgeHolder(parts[2])); + } else { + EdgeHolder *prevEdge = &contour->edges.back(); + for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + Vector2 prevDir = (*prevEdge)->direction(1).normalize(); + Vector2 curDir = (*edge)->direction(0).normalize(); + if (dotProduct(prevDir, curDir) < MSDFGEN_CORNER_DOT_EPSILON-1) { + deconvergeEdge(*prevEdge, 1); + deconvergeEdge(*edge, 0); + } + prevEdge = &*edge; + } + } + } +} + +void Shape::bound(double &l, double &b, double &r, double &t) const { + for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour) + contour->bound(l, b, r, t); +} + +void Shape::boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const { + for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour) + contour->boundMiters(l, b, r, t, border, miterLimit, polarity); +} + +Shape::Bounds Shape::getBounds(double border, double miterLimit, int polarity) const { + static const double LARGE_VALUE = 1e240; + Shape::Bounds bounds = { +LARGE_VALUE, +LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE }; + bound(bounds.l, bounds.b, bounds.r, bounds.t); + if (border > 0) { + bounds.l -= border, bounds.b -= border; + bounds.r += border, bounds.t += border; + if (miterLimit > 0) + boundMiters(bounds.l, bounds.b, bounds.r, bounds.t, border, miterLimit, polarity); + } + return bounds; +} + +void Shape::scanline(Scanline &line, double y) const { + std::vector<Scanline::Intersection> intersections; + double x[3]; + int dy[3]; + for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour) { + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + int n = (*edge)->scanlineIntersections(x, dy, y); + for (int i = 0; i < n; ++i) { + Scanline::Intersection intersection = { x[i], dy[i] }; + intersections.push_back(intersection); + } + } + } +#ifdef MSDFGEN_USE_CPP11 + line.setIntersections((std::vector<Scanline::Intersection> &&) intersections); +#else + line.setIntersections(intersections); +#endif +} + +int Shape::edgeCount() const { + int total = 0; + for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour) + total += (int) contour->edges.size(); + return total; +} + +void Shape::orientContours() { + struct Intersection { + double x; + int direction; + int contourIndex; + + static int compare(const void *a, const void *b) { + return sign(reinterpret_cast<const Intersection *>(a)->x-reinterpret_cast<const Intersection *>(b)->x); + } + }; + + const double ratio = .5*(sqrt(5)-1); // an irrational number to minimize chance of intersecting a corner or other point of interest + std::vector<int> orientations(contours.size()); + std::vector<Intersection> intersections; + for (int i = 0; i < (int) contours.size(); ++i) { + if (!orientations[i] && !contours[i].edges.empty()) { + // Find an Y that crosses the contour + double y0 = contours[i].edges.front()->point(0).y; + double y1 = y0; + for (std::vector<EdgeHolder>::const_iterator edge = contours[i].edges.begin(); edge != contours[i].edges.end() && y0 == y1; ++edge) + y1 = (*edge)->point(1).y; + for (std::vector<EdgeHolder>::const_iterator edge = contours[i].edges.begin(); edge != contours[i].edges.end() && y0 == y1; ++edge) + y1 = (*edge)->point(ratio).y; // in case all endpoints are in a horizontal line + double y = mix(y0, y1, ratio); + // Scanline through whole shape at Y + double x[3]; + int dy[3]; + for (int j = 0; j < (int) contours.size(); ++j) { + for (std::vector<EdgeHolder>::const_iterator edge = contours[j].edges.begin(); edge != contours[j].edges.end(); ++edge) { + int n = (*edge)->scanlineIntersections(x, dy, y); + for (int k = 0; k < n; ++k) { + Intersection intersection = { x[k], dy[k], j }; + intersections.push_back(intersection); + } + } + } + qsort(&intersections[0], intersections.size(), sizeof(Intersection), &Intersection::compare); + // Disqualify multiple intersections + for (int j = 1; j < (int) intersections.size(); ++j) + if (intersections[j].x == intersections[j-1].x) + intersections[j].direction = intersections[j-1].direction = 0; + // Inspect scanline and deduce orientations of intersected contours + for (int j = 0; j < (int) intersections.size(); ++j) + if (intersections[j].direction) + orientations[intersections[j].contourIndex] += 2*((j&1)^(intersections[j].direction > 0))-1; + intersections.clear(); + } + } + // Reverse contours that have the opposite orientation + for (int i = 0; i < (int) contours.size(); ++i) + if (orientations[i] < 0) + contours[i].reverse(); +} + +} diff --git a/thirdparty/msdfgen/core/Shape.h b/thirdparty/msdfgen/core/Shape.h new file mode 100644 index 0000000000..7539921ce7 --- /dev/null +++ b/thirdparty/msdfgen/core/Shape.h @@ -0,0 +1,55 @@ + +#pragma once + +#include <vector> +#include "Contour.h" +#include "Scanline.h" + +namespace msdfgen { + +// Threshold of the dot product of adjacent edge directions to be considered convergent. +#define MSDFGEN_CORNER_DOT_EPSILON .000001 +// The proportional amount by which a curve's control point will be adjusted to eliminate convergent corners. +#define MSDFGEN_DECONVERGENCE_FACTOR .000001 + +/// Vector shape representation. +class Shape { + +public: + struct Bounds { + double l, b, r, t; + }; + + /// The list of contours the shape consists of. + std::vector<Contour> contours; + /// Specifies whether the shape uses bottom-to-top (false) or top-to-bottom (true) Y coordinates. + bool inverseYAxis; + + Shape(); + /// Adds a contour. + void addContour(const Contour &contour); +#ifdef MSDFGEN_USE_CPP11 + void addContour(Contour &&contour); +#endif + /// Adds a blank contour and returns its reference. + Contour & addContour(); + /// Normalizes the shape geometry for distance field generation. + void normalize(); + /// Performs basic checks to determine if the object represents a valid shape. + bool validate() const; + /// Adjusts the bounding box to fit the shape. + void bound(double &l, double &b, double &r, double &t) const; + /// Adjusts the bounding box to fit the shape border's mitered corners. + void boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const; + /// Computes the minimum bounding box that fits the shape, optionally with a (mitered) border. + Bounds getBounds(double border = 0, double miterLimit = 0, int polarity = 0) const; + /// Outputs the scanline that intersects the shape at y. + void scanline(Scanline &line, double y) const; + /// Returns the total number of edge segments + int edgeCount() const; + /// Assumes its contours are unoriented (even-odd fill rule). Attempts to orient them to conform to the non-zero winding rule. + void orientContours(); + +}; + +} diff --git a/thirdparty/msdfgen/core/ShapeDistanceFinder.h b/thirdparty/msdfgen/core/ShapeDistanceFinder.h new file mode 100644 index 0000000000..57df8d8e72 --- /dev/null +++ b/thirdparty/msdfgen/core/ShapeDistanceFinder.h @@ -0,0 +1,37 @@ + +#pragma once + +#include <vector> +#include "Vector2.h" +#include "edge-selectors.h" +#include "contour-combiners.h" + +namespace msdfgen { + +/// Finds the distance between a point and a Shape. ContourCombiner dictates the distance metric and its data type. +template <class ContourCombiner> +class ShapeDistanceFinder { + +public: + typedef typename ContourCombiner::DistanceType DistanceType; + + // Passed shape object must persist until the distance finder is destroyed! + explicit ShapeDistanceFinder(const Shape &shape); + /// Finds the distance from origin. Not thread-safe! Is fastest when subsequent queries are close together. + DistanceType distance(const Point2 &origin); + + /// Finds the distance between shape and origin. Does not allocate result cache used to optimize performance of multiple queries. + static DistanceType oneShotDistance(const Shape &shape, const Point2 &origin); + +private: + const Shape &shape; + ContourCombiner contourCombiner; + std::vector<typename ContourCombiner::EdgeSelectorType::EdgeCache> shapeEdgeCache; + +}; + +typedef ShapeDistanceFinder<SimpleContourCombiner<TrueDistanceSelector> > SimpleTrueShapeDistanceFinder; + +} + +#include "ShapeDistanceFinder.hpp" diff --git a/thirdparty/msdfgen/core/ShapeDistanceFinder.hpp b/thirdparty/msdfgen/core/ShapeDistanceFinder.hpp new file mode 100644 index 0000000000..028738e5c3 --- /dev/null +++ b/thirdparty/msdfgen/core/ShapeDistanceFinder.hpp @@ -0,0 +1,56 @@ + +#include "ShapeDistanceFinder.h" + +namespace msdfgen { + +template <class ContourCombiner> +ShapeDistanceFinder<ContourCombiner>::ShapeDistanceFinder(const Shape &shape) : shape(shape), contourCombiner(shape), shapeEdgeCache(shape.edgeCount()) { } + +template <class ContourCombiner> +typename ShapeDistanceFinder<ContourCombiner>::DistanceType ShapeDistanceFinder<ContourCombiner>::distance(const Point2 &origin) { + contourCombiner.reset(origin); + typename ContourCombiner::EdgeSelectorType::EdgeCache *edgeCache = &shapeEdgeCache[0]; + + for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) { + if (!contour->edges.empty()) { + typename ContourCombiner::EdgeSelectorType &edgeSelector = contourCombiner.edgeSelector(int(contour-shape.contours.begin())); + + const EdgeSegment *prevEdge = contour->edges.size() >= 2 ? *(contour->edges.end()-2) : *contour->edges.begin(); + const EdgeSegment *curEdge = contour->edges.back(); + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + const EdgeSegment *nextEdge = *edge; + edgeSelector.addEdge(*edgeCache++, prevEdge, curEdge, nextEdge); + prevEdge = curEdge; + curEdge = nextEdge; + } + } + } + + return contourCombiner.distance(); +} + +template <class ContourCombiner> +typename ShapeDistanceFinder<ContourCombiner>::DistanceType ShapeDistanceFinder<ContourCombiner>::oneShotDistance(const Shape &shape, const Point2 &origin) { + ContourCombiner contourCombiner(shape); + contourCombiner.reset(origin); + + for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) { + if (!contour->edges.empty()) { + typename ContourCombiner::EdgeSelectorType &edgeSelector = contourCombiner.edgeSelector(int(contour-shape.contours.begin())); + + const EdgeSegment *prevEdge = contour->edges.size() >= 2 ? *(contour->edges.end()-2) : *contour->edges.begin(); + const EdgeSegment *curEdge = contour->edges.back(); + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + const EdgeSegment *nextEdge = *edge; + typename ContourCombiner::EdgeSelectorType::EdgeCache dummy; + edgeSelector.addEdge(dummy, prevEdge, curEdge, nextEdge); + prevEdge = curEdge; + curEdge = nextEdge; + } + } + } + + return contourCombiner.distance(); +} + +} diff --git a/thirdparty/msdfgen/core/SignedDistance.cpp b/thirdparty/msdfgen/core/SignedDistance.cpp new file mode 100644 index 0000000000..18c9d2c424 --- /dev/null +++ b/thirdparty/msdfgen/core/SignedDistance.cpp @@ -0,0 +1,30 @@ + +#include "SignedDistance.h" + +#include <cmath> + +namespace msdfgen { + +const SignedDistance SignedDistance::INFINITE(-1e240, 1); + +SignedDistance::SignedDistance() : distance(-1e240), dot(1) { } + +SignedDistance::SignedDistance(double dist, double d) : distance(dist), dot(d) { } + +bool operator<(SignedDistance a, SignedDistance b) { + return fabs(a.distance) < fabs(b.distance) || (fabs(a.distance) == fabs(b.distance) && a.dot < b.dot); +} + +bool operator>(SignedDistance a, SignedDistance b) { + return fabs(a.distance) > fabs(b.distance) || (fabs(a.distance) == fabs(b.distance) && a.dot > b.dot); +} + +bool operator<=(SignedDistance a, SignedDistance b) { + return fabs(a.distance) < fabs(b.distance) || (fabs(a.distance) == fabs(b.distance) && a.dot <= b.dot); +} + +bool operator>=(SignedDistance a, SignedDistance b) { + return fabs(a.distance) > fabs(b.distance) || (fabs(a.distance) == fabs(b.distance) && a.dot >= b.dot); +} + +} diff --git a/thirdparty/msdfgen/core/SignedDistance.h b/thirdparty/msdfgen/core/SignedDistance.h new file mode 100644 index 0000000000..034210f751 --- /dev/null +++ b/thirdparty/msdfgen/core/SignedDistance.h @@ -0,0 +1,25 @@ + +#pragma once + +namespace msdfgen { + +/// Represents a signed distance and alignment, which together can be compared to uniquely determine the closest edge segment. +class SignedDistance { + +public: + static const SignedDistance INFINITE; + + double distance; + double dot; + + SignedDistance(); + SignedDistance(double dist, double d); + + friend bool operator<(SignedDistance a, SignedDistance b); + friend bool operator>(SignedDistance a, SignedDistance b); + friend bool operator<=(SignedDistance a, SignedDistance b); + friend bool operator>=(SignedDistance a, SignedDistance b); + +}; + +} diff --git a/thirdparty/msdfgen/core/Vector2.cpp b/thirdparty/msdfgen/core/Vector2.cpp new file mode 100644 index 0000000000..896963ff2c --- /dev/null +++ b/thirdparty/msdfgen/core/Vector2.cpp @@ -0,0 +1,146 @@ + +#include "Vector2.h" + +namespace msdfgen { + +Vector2::Vector2(double val) : x(val), y(val) { } + +Vector2::Vector2(double x, double y) : x(x), y(y) { } + +void Vector2::reset() { + x = 0, y = 0; +} + +void Vector2::set(double x, double y) { + Vector2::x = x, Vector2::y = y; +} + +double Vector2::length() const { + return sqrt(x*x+y*y); +} + +double Vector2::direction() const { + return atan2(y, x); +} + +Vector2 Vector2::normalize(bool allowZero) const { + double len = length(); + if (len == 0) + return Vector2(0, !allowZero); + return Vector2(x/len, y/len); +} + +Vector2 Vector2::getOrthogonal(bool polarity) const { + return polarity ? Vector2(-y, x) : Vector2(y, -x); +} + +Vector2 Vector2::getOrthonormal(bool polarity, bool allowZero) const { + double len = length(); + if (len == 0) + return polarity ? Vector2(0, !allowZero) : Vector2(0, -!allowZero); + return polarity ? Vector2(-y/len, x/len) : Vector2(y/len, -x/len); +} + +Vector2 Vector2::project(const Vector2 &vector, bool positive) const { + Vector2 n = normalize(true); + double t = dotProduct(vector, n); + if (positive && t <= 0) + return Vector2(); + return t*n; +} + +Vector2::operator const void*() const { + return x || y ? this : NULL; +} + +bool Vector2::operator!() const { + return !x && !y; +} + +bool Vector2::operator==(const Vector2 &other) const { + return x == other.x && y == other.y; +} + +bool Vector2::operator!=(const Vector2 &other) const { + return x != other.x || y != other.y; +} + +Vector2 Vector2::operator+() const { + return *this; +} + +Vector2 Vector2::operator-() const { + return Vector2(-x, -y); +} + +Vector2 Vector2::operator+(const Vector2 &other) const { + return Vector2(x+other.x, y+other.y); +} + +Vector2 Vector2::operator-(const Vector2 &other) const { + return Vector2(x-other.x, y-other.y); +} + +Vector2 Vector2::operator*(const Vector2 &other) const { + return Vector2(x*other.x, y*other.y); +} + +Vector2 Vector2::operator/(const Vector2 &other) const { + return Vector2(x/other.x, y/other.y); +} + +Vector2 Vector2::operator*(double value) const { + return Vector2(x*value, y*value); +} + +Vector2 Vector2::operator/(double value) const { + return Vector2(x/value, y/value); +} + +Vector2 & Vector2::operator+=(const Vector2 &other) { + x += other.x, y += other.y; + return *this; +} + +Vector2 & Vector2::operator-=(const Vector2 &other) { + x -= other.x, y -= other.y; + return *this; +} + +Vector2 & Vector2::operator*=(const Vector2 &other) { + x *= other.x, y *= other.y; + return *this; +} + +Vector2 & Vector2::operator/=(const Vector2 &other) { + x /= other.x, y /= other.y; + return *this; +} + +Vector2 & Vector2::operator*=(double value) { + x *= value, y *= value; + return *this; +} + +Vector2 & Vector2::operator/=(double value) { + x /= value, y /= value; + return *this; +} + +double dotProduct(const Vector2 &a, const Vector2 &b) { + return a.x*b.x+a.y*b.y; +} + +double crossProduct(const Vector2 &a, const Vector2 &b) { + return a.x*b.y-a.y*b.x; +} + +Vector2 operator*(double value, const Vector2 &vector) { + return Vector2(value*vector.x, value*vector.y); +} + +Vector2 operator/(double value, const Vector2 &vector) { + return Vector2(value/vector.x, value/vector.y); +} + +} diff --git a/thirdparty/msdfgen/core/Vector2.h b/thirdparty/msdfgen/core/Vector2.h new file mode 100644 index 0000000000..47ca637c3d --- /dev/null +++ b/thirdparty/msdfgen/core/Vector2.h @@ -0,0 +1,66 @@ + +#pragma once + +#include <cstdlib> +#include <cmath> + +namespace msdfgen { + +/** +* A 2-dimensional euclidean vector with double precision. +* Implementation based on the Vector2 template from Artery Engine. +* @author Viktor Chlumsky +*/ +struct Vector2 { + + double x, y; + + Vector2(double val = 0); + Vector2(double x, double y); + /// Sets the vector to zero. + void reset(); + /// Sets individual elements of the vector. + void set(double x, double y); + /// Returns the vector's length. + double length() const; + /// Returns the angle of the vector in radians (atan2). + double direction() const; + /// Returns the normalized vector - one that has the same direction but unit length. + Vector2 normalize(bool allowZero = false) const; + /// Returns a vector with the same length that is orthogonal to this one. + Vector2 getOrthogonal(bool polarity = true) const; + /// Returns a vector with unit length that is orthogonal to this one. + Vector2 getOrthonormal(bool polarity = true, bool allowZero = false) const; + /// Returns a vector projected along this one. + Vector2 project(const Vector2 &vector, bool positive = false) const; + operator const void *() const; + bool operator!() const; + bool operator==(const Vector2 &other) const; + bool operator!=(const Vector2 &other) const; + Vector2 operator+() const; + Vector2 operator-() const; + Vector2 operator+(const Vector2 &other) const; + Vector2 operator-(const Vector2 &other) const; + Vector2 operator*(const Vector2 &other) const; + Vector2 operator/(const Vector2 &other) const; + Vector2 operator*(double value) const; + Vector2 operator/(double value) const; + Vector2 & operator+=(const Vector2 &other); + Vector2 & operator-=(const Vector2 &other); + Vector2 & operator*=(const Vector2 &other); + Vector2 & operator/=(const Vector2 &other); + Vector2 & operator*=(double value); + Vector2 & operator/=(double value); + /// Dot product of two vectors. + friend double dotProduct(const Vector2 &a, const Vector2 &b); + /// A special version of the cross product for 2D vectors (returns scalar value). + friend double crossProduct(const Vector2 &a, const Vector2 &b); + friend Vector2 operator*(double value, const Vector2 &vector); + friend Vector2 operator/(double value, const Vector2 &vector); + +}; + +/// A vector may also represent a point, which shall be differentiated semantically using the alias Point2. +typedef Vector2 Point2; + +} diff --git a/thirdparty/msdfgen/core/arithmetics.hpp b/thirdparty/msdfgen/core/arithmetics.hpp new file mode 100644 index 0000000000..78c21d658e --- /dev/null +++ b/thirdparty/msdfgen/core/arithmetics.hpp @@ -0,0 +1,63 @@ + +#pragma once + +#include <cstdlib> +#include <cmath> + +namespace msdfgen { + +/// Returns the smaller of the arguments. +template <typename T> +inline T min(T a, T b) { + return b < a ? b : a; +} + +/// Returns the larger of the arguments. +template <typename T> +inline T max(T a, T b) { + return a < b ? b : a; +} + +/// Returns the middle out of three values +template <typename T> +inline T median(T a, T b, T c) { + return max(min(a, b), min(max(a, b), c)); +} + +/// Returns the weighted average of a and b. +template <typename T, typename S> +inline T mix(T a, T b, S weight) { + return T((S(1)-weight)*a+weight*b); +} + +/// Clamps the number to the interval from 0 to 1. +template <typename T> +inline T clamp(T n) { + return n >= T(0) && n <= T(1) ? n : T(n > T(0)); +} + +/// Clamps the number to the interval from 0 to b. +template <typename T> +inline T clamp(T n, T b) { + return n >= T(0) && n <= b ? n : T(n > T(0))*b; +} + +/// Clamps the number to the interval from a to b. +template <typename T> +inline T clamp(T n, T a, T b) { + return n >= a && n <= b ? n : n < a ? a : b; +} + +/// Returns 1 for positive values, -1 for negative values, and 0 for zero. +template <typename T> +inline int sign(T n) { + return (T(0) < n)-(n < T(0)); +} + +/// Returns 1 for non-negative values and -1 for negative values. +template <typename T> +inline int nonZeroSign(T n) { + return 2*(n > T(0))-1; +} + +} diff --git a/thirdparty/msdfgen/core/bitmap-interpolation.hpp b/thirdparty/msdfgen/core/bitmap-interpolation.hpp new file mode 100644 index 0000000000..a14b0fb534 --- /dev/null +++ b/thirdparty/msdfgen/core/bitmap-interpolation.hpp @@ -0,0 +1,25 @@ + +#pragma once + +#include "arithmetics.hpp" +#include "Vector2.h" +#include "BitmapRef.hpp" + +namespace msdfgen { + +template <typename T, int N> +static void interpolate(T *output, const BitmapConstRef<T, N> &bitmap, Point2 pos) { + pos -= .5; + int l = (int) floor(pos.x); + int b = (int) floor(pos.y); + int r = l+1; + int t = b+1; + double lr = pos.x-l; + double bt = pos.y-b; + l = clamp(l, bitmap.width-1), r = clamp(r, bitmap.width-1); + b = clamp(b, bitmap.height-1), t = clamp(t, bitmap.height-1); + for (int i = 0; i < N; ++i) + output[i] = mix(mix(bitmap(l, b)[i], bitmap(r, b)[i], lr), mix(bitmap(l, t)[i], bitmap(r, t)[i], lr), bt); +} + +} diff --git a/thirdparty/msdfgen/core/contour-combiners.cpp b/thirdparty/msdfgen/core/contour-combiners.cpp new file mode 100644 index 0000000000..d0c5b46d74 --- /dev/null +++ b/thirdparty/msdfgen/core/contour-combiners.cpp @@ -0,0 +1,133 @@ + +#include "contour-combiners.h" + +#include "arithmetics.hpp" + +namespace msdfgen { + +static void initDistance(double &distance) { + distance = SignedDistance::INFINITE.distance; +} + +static void initDistance(MultiDistance &distance) { + distance.r = SignedDistance::INFINITE.distance; + distance.g = SignedDistance::INFINITE.distance; + distance.b = SignedDistance::INFINITE.distance; +} + +static double resolveDistance(double distance) { + return distance; +} + +static double resolveDistance(const MultiDistance &distance) { + return median(distance.r, distance.g, distance.b); +} + +template <class EdgeSelector> +SimpleContourCombiner<EdgeSelector>::SimpleContourCombiner(const Shape &shape) { } + +template <class EdgeSelector> +void SimpleContourCombiner<EdgeSelector>::reset(const Point2 &p) { + shapeEdgeSelector.reset(p); +} + +template <class EdgeSelector> +EdgeSelector & SimpleContourCombiner<EdgeSelector>::edgeSelector(int) { + return shapeEdgeSelector; +} + +template <class EdgeSelector> +typename SimpleContourCombiner<EdgeSelector>::DistanceType SimpleContourCombiner<EdgeSelector>::distance() const { + return shapeEdgeSelector.distance(); +} + +template class SimpleContourCombiner<TrueDistanceSelector>; +template class SimpleContourCombiner<PseudoDistanceSelector>; +template class SimpleContourCombiner<MultiDistanceSelector>; +template class SimpleContourCombiner<MultiAndTrueDistanceSelector>; + +template <class EdgeSelector> +OverlappingContourCombiner<EdgeSelector>::OverlappingContourCombiner(const Shape &shape) { + windings.reserve(shape.contours.size()); + for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) + windings.push_back(contour->winding()); + edgeSelectors.resize(shape.contours.size()); +} + +template <class EdgeSelector> +void OverlappingContourCombiner<EdgeSelector>::reset(const Point2 &p) { + this->p = p; + for (typename std::vector<EdgeSelector>::iterator contourEdgeSelector = edgeSelectors.begin(); contourEdgeSelector != edgeSelectors.end(); ++contourEdgeSelector) + contourEdgeSelector->reset(p); +} + +template <class EdgeSelector> +EdgeSelector & OverlappingContourCombiner<EdgeSelector>::edgeSelector(int i) { + return edgeSelectors[i]; +} + +template <class EdgeSelector> +typename OverlappingContourCombiner<EdgeSelector>::DistanceType OverlappingContourCombiner<EdgeSelector>::distance() const { + int contourCount = (int) edgeSelectors.size(); + EdgeSelector shapeEdgeSelector; + EdgeSelector innerEdgeSelector; + EdgeSelector outerEdgeSelector; + shapeEdgeSelector.reset(p); + innerEdgeSelector.reset(p); + outerEdgeSelector.reset(p); + for (int i = 0; i < contourCount; ++i) { + DistanceType edgeDistance = edgeSelectors[i].distance(); + shapeEdgeSelector.merge(edgeSelectors[i]); + if (windings[i] > 0 && resolveDistance(edgeDistance) >= 0) + innerEdgeSelector.merge(edgeSelectors[i]); + if (windings[i] < 0 && resolveDistance(edgeDistance) <= 0) + outerEdgeSelector.merge(edgeSelectors[i]); + } + + DistanceType shapeDistance = shapeEdgeSelector.distance(); + DistanceType innerDistance = innerEdgeSelector.distance(); + DistanceType outerDistance = outerEdgeSelector.distance(); + double innerScalarDistance = resolveDistance(innerDistance); + double outerScalarDistance = resolveDistance(outerDistance); + DistanceType distance; + initDistance(distance); + + int winding = 0; + if (innerScalarDistance >= 0 && fabs(innerScalarDistance) <= fabs(outerScalarDistance)) { + distance = innerDistance; + winding = 1; + for (int i = 0; i < contourCount; ++i) + if (windings[i] > 0) { + DistanceType contourDistance = edgeSelectors[i].distance(); + if (fabs(resolveDistance(contourDistance)) < fabs(outerScalarDistance) && resolveDistance(contourDistance) > resolveDistance(distance)) + distance = contourDistance; + } + } else if (outerScalarDistance <= 0 && fabs(outerScalarDistance) < fabs(innerScalarDistance)) { + distance = outerDistance; + winding = -1; + for (int i = 0; i < contourCount; ++i) + if (windings[i] < 0) { + DistanceType contourDistance = edgeSelectors[i].distance(); + if (fabs(resolveDistance(contourDistance)) < fabs(innerScalarDistance) && resolveDistance(contourDistance) < resolveDistance(distance)) + distance = contourDistance; + } + } else + return shapeDistance; + + for (int i = 0; i < contourCount; ++i) + if (windings[i] != winding) { + DistanceType contourDistance = edgeSelectors[i].distance(); + if (resolveDistance(contourDistance)*resolveDistance(distance) >= 0 && fabs(resolveDistance(contourDistance)) < fabs(resolveDistance(distance))) + distance = contourDistance; + } + if (resolveDistance(distance) == resolveDistance(shapeDistance)) + distance = shapeDistance; + return distance; +} + +template class OverlappingContourCombiner<TrueDistanceSelector>; +template class OverlappingContourCombiner<PseudoDistanceSelector>; +template class OverlappingContourCombiner<MultiDistanceSelector>; +template class OverlappingContourCombiner<MultiAndTrueDistanceSelector>; + +} diff --git a/thirdparty/msdfgen/core/contour-combiners.h b/thirdparty/msdfgen/core/contour-combiners.h new file mode 100644 index 0000000000..944b119aba --- /dev/null +++ b/thirdparty/msdfgen/core/contour-combiners.h @@ -0,0 +1,47 @@ + +#pragma once + +#include "Shape.h" +#include "edge-selectors.h" + +namespace msdfgen { + +/// Simply selects the nearest contour. +template <class EdgeSelector> +class SimpleContourCombiner { + +public: + typedef EdgeSelector EdgeSelectorType; + typedef typename EdgeSelector::DistanceType DistanceType; + + explicit SimpleContourCombiner(const Shape &shape); + void reset(const Point2 &p); + EdgeSelector & edgeSelector(int i); + DistanceType distance() const; + +private: + EdgeSelector shapeEdgeSelector; + +}; + +/// Selects the nearest contour that actually forms a border between filled and unfilled area. +template <class EdgeSelector> +class OverlappingContourCombiner { + +public: + typedef EdgeSelector EdgeSelectorType; + typedef typename EdgeSelector::DistanceType DistanceType; + + explicit OverlappingContourCombiner(const Shape &shape); + void reset(const Point2 &p); + EdgeSelector & edgeSelector(int i); + DistanceType distance() const; + +private: + Point2 p; + std::vector<int> windings; + std::vector<EdgeSelector> edgeSelectors; + +}; + +} diff --git a/thirdparty/msdfgen/core/edge-coloring.cpp b/thirdparty/msdfgen/core/edge-coloring.cpp new file mode 100644 index 0000000000..370f9aa38d --- /dev/null +++ b/thirdparty/msdfgen/core/edge-coloring.cpp @@ -0,0 +1,499 @@ + +#include "edge-coloring.h" + +#include <cstdlib> +#include <cmath> +#include <cstring> +#include <queue> +#include "arithmetics.hpp" + +namespace msdfgen { + +static bool isCorner(const Vector2 &aDir, const Vector2 &bDir, double crossThreshold) { + return dotProduct(aDir, bDir) <= 0 || fabs(crossProduct(aDir, bDir)) > crossThreshold; +} + +static double estimateEdgeLength(const EdgeSegment *edge) { + double len = 0; + Point2 prev = edge->point(0); + for (int i = 1; i <= MSDFGEN_EDGE_LENGTH_PRECISION; ++i) { + Point2 cur = edge->point(1./MSDFGEN_EDGE_LENGTH_PRECISION*i); + len += (cur-prev).length(); + prev = cur; + } + return len; +} + +static void switchColor(EdgeColor &color, unsigned long long &seed, EdgeColor banned = BLACK) { + EdgeColor combined = EdgeColor(color&banned); + if (combined == RED || combined == GREEN || combined == BLUE) { + color = EdgeColor(combined^WHITE); + return; + } + if (color == BLACK || color == WHITE) { + static const EdgeColor start[3] = { CYAN, MAGENTA, YELLOW }; + color = start[seed%3]; + seed /= 3; + return; + } + int shifted = color<<(1+(seed&1)); + color = EdgeColor((shifted|shifted>>3)&WHITE); + seed >>= 1; +} + +void edgeColoringSimple(Shape &shape, double angleThreshold, unsigned long long seed) { + double crossThreshold = sin(angleThreshold); + std::vector<int> corners; + for (std::vector<Contour>::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) { + // Identify corners + corners.clear(); + if (!contour->edges.empty()) { + Vector2 prevDirection = contour->edges.back()->direction(1); + int index = 0; + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge, ++index) { + if (isCorner(prevDirection.normalize(), (*edge)->direction(0).normalize(), crossThreshold)) + corners.push_back(index); + prevDirection = (*edge)->direction(1); + } + } + + // Smooth contour + if (corners.empty()) + for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) + (*edge)->color = WHITE; + // "Teardrop" case + else if (corners.size() == 1) { + EdgeColor colors[3] = { WHITE, WHITE }; + switchColor(colors[0], seed); + switchColor(colors[2] = colors[0], seed); + int corner = corners[0]; + if (contour->edges.size() >= 3) { + int m = (int) contour->edges.size(); + for (int i = 0; i < m; ++i) + contour->edges[(corner+i)%m]->color = (colors+1)[int(3+2.875*i/(m-1)-1.4375+.5)-3]; + } else if (contour->edges.size() >= 1) { + // Less than three edge segments for three colors => edges must be split + EdgeSegment *parts[7] = { }; + contour->edges[0]->splitInThirds(parts[0+3*corner], parts[1+3*corner], parts[2+3*corner]); + if (contour->edges.size() >= 2) { + contour->edges[1]->splitInThirds(parts[3-3*corner], parts[4-3*corner], parts[5-3*corner]); + parts[0]->color = parts[1]->color = colors[0]; + parts[2]->color = parts[3]->color = colors[1]; + parts[4]->color = parts[5]->color = colors[2]; + } else { + parts[0]->color = colors[0]; + parts[1]->color = colors[1]; + parts[2]->color = colors[2]; + } + contour->edges.clear(); + for (int i = 0; parts[i]; ++i) + contour->edges.push_back(EdgeHolder(parts[i])); + } + } + // Multiple corners + else { + int cornerCount = (int) corners.size(); + int spline = 0; + int start = corners[0]; + int m = (int) contour->edges.size(); + EdgeColor color = WHITE; + switchColor(color, seed); + EdgeColor initialColor = color; + for (int i = 0; i < m; ++i) { + int index = (start+i)%m; + if (spline+1 < cornerCount && corners[spline+1] == index) { + ++spline; + switchColor(color, seed, EdgeColor((spline == cornerCount-1)*initialColor)); + } + contour->edges[index]->color = color; + } + } + } +} + +struct EdgeColoringInkTrapCorner { + int index; + double prevEdgeLengthEstimate; + bool minor; + EdgeColor color; +}; + +void edgeColoringInkTrap(Shape &shape, double angleThreshold, unsigned long long seed) { + typedef EdgeColoringInkTrapCorner Corner; + double crossThreshold = sin(angleThreshold); + std::vector<Corner> corners; + for (std::vector<Contour>::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) { + // Identify corners + double splineLength = 0; + corners.clear(); + if (!contour->edges.empty()) { + Vector2 prevDirection = contour->edges.back()->direction(1); + int index = 0; + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge, ++index) { + if (isCorner(prevDirection.normalize(), (*edge)->direction(0).normalize(), crossThreshold)) { + Corner corner = { index, splineLength }; + corners.push_back(corner); + splineLength = 0; + } + splineLength += estimateEdgeLength(*edge); + prevDirection = (*edge)->direction(1); + } + } + + // Smooth contour + if (corners.empty()) + for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) + (*edge)->color = WHITE; + // "Teardrop" case + else if (corners.size() == 1) { + EdgeColor colors[3] = { WHITE, WHITE }; + switchColor(colors[0], seed); + switchColor(colors[2] = colors[0], seed); + int corner = corners[0].index; + if (contour->edges.size() >= 3) { + int m = (int) contour->edges.size(); + for (int i = 0; i < m; ++i) + contour->edges[(corner+i)%m]->color = (colors+1)[int(3+2.875*i/(m-1)-1.4375+.5)-3]; + } else if (contour->edges.size() >= 1) { + // Less than three edge segments for three colors => edges must be split + EdgeSegment *parts[7] = { }; + contour->edges[0]->splitInThirds(parts[0+3*corner], parts[1+3*corner], parts[2+3*corner]); + if (contour->edges.size() >= 2) { + contour->edges[1]->splitInThirds(parts[3-3*corner], parts[4-3*corner], parts[5-3*corner]); + parts[0]->color = parts[1]->color = colors[0]; + parts[2]->color = parts[3]->color = colors[1]; + parts[4]->color = parts[5]->color = colors[2]; + } else { + parts[0]->color = colors[0]; + parts[1]->color = colors[1]; + parts[2]->color = colors[2]; + } + contour->edges.clear(); + for (int i = 0; parts[i]; ++i) + contour->edges.push_back(EdgeHolder(parts[i])); + } + } + // Multiple corners + else { + int cornerCount = (int) corners.size(); + int majorCornerCount = cornerCount; + if (cornerCount > 3) { + corners.begin()->prevEdgeLengthEstimate += splineLength; + for (int i = 0; i < cornerCount; ++i) { + if ( + corners[i].prevEdgeLengthEstimate > corners[(i+1)%cornerCount].prevEdgeLengthEstimate && + corners[(i+1)%cornerCount].prevEdgeLengthEstimate < corners[(i+2)%cornerCount].prevEdgeLengthEstimate + ) { + corners[i].minor = true; + --majorCornerCount; + } + } + } + EdgeColor color = WHITE; + EdgeColor initialColor = BLACK; + for (int i = 0; i < cornerCount; ++i) { + if (!corners[i].minor) { + --majorCornerCount; + switchColor(color, seed, EdgeColor(!majorCornerCount*initialColor)); + corners[i].color = color; + if (!initialColor) + initialColor = color; + } + } + for (int i = 0; i < cornerCount; ++i) { + if (corners[i].minor) { + EdgeColor nextColor = corners[(i+1)%cornerCount].color; + corners[i].color = EdgeColor((color&nextColor)^WHITE); + } else + color = corners[i].color; + } + int spline = 0; + int start = corners[0].index; + color = corners[0].color; + int m = (int) contour->edges.size(); + for (int i = 0; i < m; ++i) { + int index = (start+i)%m; + if (spline+1 < cornerCount && corners[spline+1].index == index) + color = corners[++spline].color; + contour->edges[index]->color = color; + } + } + } +} + +// EDGE COLORING BY DISTANCE - EXPERIMENTAL IMPLEMENTATION - WORK IN PROGRESS +#define MAX_RECOLOR_STEPS 16 +#define EDGE_DISTANCE_PRECISION 16 + +static double edgeToEdgeDistance(const EdgeSegment &a, const EdgeSegment &b, int precision) { + if (a.point(0) == b.point(0) || a.point(0) == b.point(1) || a.point(1) == b.point(0) || a.point(1) == b.point(1)) + return 0; + double iFac = 1./precision; + double minDistance = (b.point(0)-a.point(0)).length(); + for (int i = 0; i <= precision; ++i) { + double t = iFac*i; + double d = fabs(a.signedDistance(b.point(t), t).distance); + minDistance = min(minDistance, d); + } + for (int i = 0; i <= precision; ++i) { + double t = iFac*i; + double d = fabs(b.signedDistance(a.point(t), t).distance); + minDistance = min(minDistance, d); + } + return minDistance; +} + +static double splineToSplineDistance(EdgeSegment * const *edgeSegments, int aStart, int aEnd, int bStart, int bEnd, int precision) { + double minDistance = fabs(SignedDistance::INFINITE.distance); + for (int ai = aStart; ai < aEnd; ++ai) + for (int bi = bStart; bi < bEnd && minDistance; ++bi) { + double d = edgeToEdgeDistance(*edgeSegments[ai], *edgeSegments[bi], precision); + minDistance = min(minDistance, d); + } + return minDistance; +} + +static void colorSecondDegreeGraph(int *coloring, const int * const *edgeMatrix, int vertexCount, unsigned long long seed) { + for (int i = 0; i < vertexCount; ++i) { + int possibleColors = 7; + for (int j = 0; j < i; ++j) { + if (edgeMatrix[i][j]) + possibleColors &= ~(1<<coloring[j]); + } + int color = 0; + switch (possibleColors) { + case 1: + color = 0; + break; + case 2: + color = 1; + break; + case 3: + color = (int) seed&1; + seed >>= 1; + break; + case 4: + color = 2; + break; + case 5: + color = ((int) seed+1&1)<<1; + seed >>= 1; + break; + case 6: + color = ((int) seed&1)+1; + seed >>= 1; + break; + case 7: + color = int((seed+i)%3); + seed /= 3; + break; + } + coloring[i] = color; + } +} + +static int vertexPossibleColors(const int *coloring, const int *edgeVector, int vertexCount) { + int usedColors = 0; + for (int i = 0; i < vertexCount; ++i) + if (edgeVector[i]) + usedColors |= 1<<coloring[i]; + return 7&~usedColors; +} + +static void uncolorSameNeighbors(std::queue<int> &uncolored, int *coloring, const int * const *edgeMatrix, int vertex, int vertexCount) { + for (int i = vertex+1; i < vertexCount; ++i) { + if (edgeMatrix[vertex][i] && coloring[i] == coloring[vertex]) { + coloring[i] = -1; + uncolored.push(i); + } + } + for (int i = 0; i < vertex; ++i) { + if (edgeMatrix[vertex][i] && coloring[i] == coloring[vertex]) { + coloring[i] = -1; + uncolored.push(i); + } + } +} + +static bool tryAddEdge(int *coloring, int * const *edgeMatrix, int vertexCount, int vertexA, int vertexB, int *coloringBuffer) { + static const int FIRST_POSSIBLE_COLOR[8] = { -1, 0, 1, 0, 2, 2, 1, 0 }; + edgeMatrix[vertexA][vertexB] = 1; + edgeMatrix[vertexB][vertexA] = 1; + if (coloring[vertexA] != coloring[vertexB]) + return true; + int bPossibleColors = vertexPossibleColors(coloring, edgeMatrix[vertexB], vertexCount); + if (bPossibleColors) { + coloring[vertexB] = FIRST_POSSIBLE_COLOR[bPossibleColors]; + return true; + } + memcpy(coloringBuffer, coloring, sizeof(int)*vertexCount); + std::queue<int> uncolored; + { + int *coloring = coloringBuffer; + coloring[vertexB] = FIRST_POSSIBLE_COLOR[7&~(1<<coloring[vertexA])]; + uncolorSameNeighbors(uncolored, coloring, edgeMatrix, vertexB, vertexCount); + int step = 0; + while (!uncolored.empty() && step < MAX_RECOLOR_STEPS) { + int i = uncolored.front(); + uncolored.pop(); + int possibleColors = vertexPossibleColors(coloring, edgeMatrix[i], vertexCount); + if (possibleColors) { + coloring[i] = FIRST_POSSIBLE_COLOR[possibleColors]; + continue; + } + do { + coloring[i] = step++%3; + } while (edgeMatrix[i][vertexA] && coloring[i] == coloring[vertexA]); + uncolorSameNeighbors(uncolored, coloring, edgeMatrix, i, vertexCount); + } + } + if (!uncolored.empty()) { + edgeMatrix[vertexA][vertexB] = 0; + edgeMatrix[vertexB][vertexA] = 0; + return false; + } + memcpy(coloring, coloringBuffer, sizeof(int)*vertexCount); + return true; +} + +static int cmpDoublePtr(const void *a, const void *b) { + return sign(**reinterpret_cast<const double * const *>(a)-**reinterpret_cast<const double * const *>(b)); +} + +void edgeColoringByDistance(Shape &shape, double angleThreshold, unsigned long long seed) { + + std::vector<EdgeSegment *> edgeSegments; + std::vector<int> splineStarts; + + double crossThreshold = sin(angleThreshold); + std::vector<int> corners; + for (std::vector<Contour>::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) + if (!contour->edges.empty()) { + // Identify corners + corners.clear(); + Vector2 prevDirection = contour->edges.back()->direction(1); + int index = 0; + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge, ++index) { + if (isCorner(prevDirection.normalize(), (*edge)->direction(0).normalize(), crossThreshold)) + corners.push_back(index); + prevDirection = (*edge)->direction(1); + } + + splineStarts.push_back((int) edgeSegments.size()); + // Smooth contour + if (corners.empty()) + for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) + edgeSegments.push_back(&**edge); + // "Teardrop" case + else if (corners.size() == 1) { + int corner = corners[0]; + if (contour->edges.size() >= 3) { + int m = (int) contour->edges.size(); + for (int i = 0; i < m; ++i) { + if (i == m/2) + splineStarts.push_back((int) edgeSegments.size()); + if (int(3+2.875*i/(m-1)-1.4375+.5)-3) + edgeSegments.push_back(&*contour->edges[(corner+i)%m]); + else + contour->edges[(corner+i)%m]->color = WHITE; + } + } else if (contour->edges.size() >= 1) { + // Less than three edge segments for three colors => edges must be split + EdgeSegment *parts[7] = { }; + contour->edges[0]->splitInThirds(parts[0+3*corner], parts[1+3*corner], parts[2+3*corner]); + if (contour->edges.size() >= 2) { + contour->edges[1]->splitInThirds(parts[3-3*corner], parts[4-3*corner], parts[5-3*corner]); + edgeSegments.push_back(parts[0]); + edgeSegments.push_back(parts[1]); + parts[2]->color = parts[3]->color = WHITE; + splineStarts.push_back((int) edgeSegments.size()); + edgeSegments.push_back(parts[4]); + edgeSegments.push_back(parts[5]); + } else { + edgeSegments.push_back(parts[0]); + parts[1]->color = WHITE; + splineStarts.push_back((int) edgeSegments.size()); + edgeSegments.push_back(parts[2]); + } + contour->edges.clear(); + for (int i = 0; parts[i]; ++i) + contour->edges.push_back(EdgeHolder(parts[i])); + } + } + // Multiple corners + else { + int cornerCount = (int) corners.size(); + int spline = 0; + int start = corners[0]; + int m = (int) contour->edges.size(); + for (int i = 0; i < m; ++i) { + int index = (start+i)%m; + if (spline+1 < cornerCount && corners[spline+1] == index) { + splineStarts.push_back((int) edgeSegments.size()); + ++spline; + } + edgeSegments.push_back(&*contour->edges[index]); + } + } + } + splineStarts.push_back((int) edgeSegments.size()); + + int segmentCount = (int) edgeSegments.size(); + int splineCount = (int) splineStarts.size()-1; + if (!splineCount) + return; + + std::vector<double> distanceMatrixStorage(splineCount*splineCount); + std::vector<double *> distanceMatrix(splineCount); + for (int i = 0; i < splineCount; ++i) + distanceMatrix[i] = &distanceMatrixStorage[i*splineCount]; + const double *distanceMatrixBase = &distanceMatrixStorage[0]; + + for (int i = 0; i < splineCount; ++i) { + distanceMatrix[i][i] = -1; + for (int j = i+1; j < splineCount; ++j) { + double dist = splineToSplineDistance(&edgeSegments[0], splineStarts[i], splineStarts[i+1], splineStarts[j], splineStarts[j+1], EDGE_DISTANCE_PRECISION); + distanceMatrix[i][j] = dist; + distanceMatrix[j][i] = dist; + } + } + + std::vector<const double *> graphEdgeDistances; + graphEdgeDistances.reserve(splineCount*(splineCount-1)/2); + for (int i = 0; i < splineCount; ++i) + for (int j = i+1; j < splineCount; ++j) + graphEdgeDistances.push_back(&distanceMatrix[i][j]); + int graphEdgeCount = (int) graphEdgeDistances.size(); + if (!graphEdgeDistances.empty()) + qsort(&graphEdgeDistances[0], graphEdgeDistances.size(), sizeof(const double *), &cmpDoublePtr); + + std::vector<int> edgeMatrixStorage(splineCount*splineCount); + std::vector<int *> edgeMatrix(splineCount); + for (int i = 0; i < splineCount; ++i) + edgeMatrix[i] = &edgeMatrixStorage[i*splineCount]; + int nextEdge = 0; + for (; nextEdge < graphEdgeCount && !*graphEdgeDistances[nextEdge]; ++nextEdge) { + int elem = graphEdgeDistances[nextEdge]-distanceMatrixBase; + int row = elem/splineCount; + int col = elem%splineCount; + edgeMatrix[row][col] = 1; + edgeMatrix[col][row] = 1; + } + + std::vector<int> coloring(2*splineCount); + colorSecondDegreeGraph(&coloring[0], &edgeMatrix[0], splineCount, seed); + for (; nextEdge < graphEdgeCount; ++nextEdge) { + int elem = graphEdgeDistances[nextEdge]-distanceMatrixBase; + tryAddEdge(&coloring[0], &edgeMatrix[0], splineCount, elem/splineCount, elem%splineCount, &coloring[splineCount]); + } + + const EdgeColor colors[3] = { YELLOW, CYAN, MAGENTA }; + int spline = -1; + for (int i = 0; i < segmentCount; ++i) { + if (splineStarts[spline+1] == i) + ++spline; + edgeSegments[i]->color = colors[coloring[spline]]; + } +} + +} diff --git a/thirdparty/msdfgen/core/edge-coloring.h b/thirdparty/msdfgen/core/edge-coloring.h new file mode 100644 index 0000000000..ffd5e6dce8 --- /dev/null +++ b/thirdparty/msdfgen/core/edge-coloring.h @@ -0,0 +1,29 @@ + +#pragma once + +#include "Shape.h" + +#define MSDFGEN_EDGE_LENGTH_PRECISION 4 + +namespace msdfgen { + +/** Assigns colors to edges of the shape in accordance to the multi-channel distance field technique. + * May split some edges if necessary. + * angleThreshold specifies the maximum angle (in radians) to be considered a corner, for example 3 (~172 degrees). + * Values below 1/2 PI will be treated as the external angle. + */ +void edgeColoringSimple(Shape &shape, double angleThreshold, unsigned long long seed = 0); + +/** The alternative "ink trap" coloring strategy is designed for better results with typefaces + * that use ink traps as a design feature. It guarantees that even if all edges that are shorter than + * both their neighboring edges are removed, the coloring remains consistent with the established rules. + */ +void edgeColoringInkTrap(Shape &shape, double angleThreshold, unsigned long long seed = 0); + +/** The alternative coloring by distance tries to use different colors for edges that are close together. + * This should theoretically be the best strategy on average. However, since it needs to compute the distance + * between all pairs of edges, and perform a graph optimization task, it is much slower than the rest. + */ +void edgeColoringByDistance(Shape &shape, double angleThreshold, unsigned long long seed = 0); + +} diff --git a/thirdparty/msdfgen/core/edge-segments.cpp b/thirdparty/msdfgen/core/edge-segments.cpp new file mode 100644 index 0000000000..5274a9a5a1 --- /dev/null +++ b/thirdparty/msdfgen/core/edge-segments.cpp @@ -0,0 +1,504 @@ + +#include "edge-segments.h" + +#include "arithmetics.hpp" +#include "equation-solver.h" + +namespace msdfgen { + +void EdgeSegment::distanceToPseudoDistance(SignedDistance &distance, Point2 origin, double param) const { + if (param < 0) { + Vector2 dir = direction(0).normalize(); + Vector2 aq = origin-point(0); + double ts = dotProduct(aq, dir); + if (ts < 0) { + double pseudoDistance = crossProduct(aq, dir); + if (fabs(pseudoDistance) <= fabs(distance.distance)) { + distance.distance = pseudoDistance; + distance.dot = 0; + } + } + } else if (param > 1) { + Vector2 dir = direction(1).normalize(); + Vector2 bq = origin-point(1); + double ts = dotProduct(bq, dir); + if (ts > 0) { + double pseudoDistance = crossProduct(bq, dir); + if (fabs(pseudoDistance) <= fabs(distance.distance)) { + distance.distance = pseudoDistance; + distance.dot = 0; + } + } + } +} + +LinearSegment::LinearSegment(Point2 p0, Point2 p1, EdgeColor edgeColor) : EdgeSegment(edgeColor) { + p[0] = p0; + p[1] = p1; +} + +QuadraticSegment::QuadraticSegment(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) : EdgeSegment(edgeColor) { + if (p1 == p0 || p1 == p2) + p1 = 0.5*(p0+p2); + p[0] = p0; + p[1] = p1; + p[2] = p2; +} + +CubicSegment::CubicSegment(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) : EdgeSegment(edgeColor) { + if ((p1 == p0 || p1 == p3) && (p2 == p0 || p2 == p3)) { + p1 = mix(p0, p3, 1/3.); + p2 = mix(p0, p3, 2/3.); + } + p[0] = p0; + p[1] = p1; + p[2] = p2; + p[3] = p3; +} + +LinearSegment * LinearSegment::clone() const { + return new LinearSegment(p[0], p[1], color); +} + +QuadraticSegment * QuadraticSegment::clone() const { + return new QuadraticSegment(p[0], p[1], p[2], color); +} + +CubicSegment * CubicSegment::clone() const { + return new CubicSegment(p[0], p[1], p[2], p[3], color); +} + +Point2 LinearSegment::point(double param) const { + return mix(p[0], p[1], param); +} + +Point2 QuadraticSegment::point(double param) const { + return mix(mix(p[0], p[1], param), mix(p[1], p[2], param), param); +} + +Point2 CubicSegment::point(double param) const { + Vector2 p12 = mix(p[1], p[2], param); + return mix(mix(mix(p[0], p[1], param), p12, param), mix(p12, mix(p[2], p[3], param), param), param); +} + +Vector2 LinearSegment::direction(double param) const { + return p[1]-p[0]; +} + +Vector2 QuadraticSegment::direction(double param) const { + Vector2 tangent = mix(p[1]-p[0], p[2]-p[1], param); + if (!tangent) + return p[2]-p[0]; + return tangent; +} + +Vector2 CubicSegment::direction(double param) const { + Vector2 tangent = mix(mix(p[1]-p[0], p[2]-p[1], param), mix(p[2]-p[1], p[3]-p[2], param), param); + if (!tangent) { + if (param == 0) return p[2]-p[0]; + if (param == 1) return p[3]-p[1]; + } + return tangent; +} + +Vector2 LinearSegment::directionChange(double param) const { + return Vector2(); +} + +Vector2 QuadraticSegment::directionChange(double param) const { + return (p[2]-p[1])-(p[1]-p[0]); +} + +Vector2 CubicSegment::directionChange(double param) const { + return mix((p[2]-p[1])-(p[1]-p[0]), (p[3]-p[2])-(p[2]-p[1]), param); +} + +double LinearSegment::length() const { + return (p[1]-p[0]).length(); +} + +double QuadraticSegment::length() const { + Vector2 ab = p[1]-p[0]; + Vector2 br = p[2]-p[1]-ab; + double abab = dotProduct(ab, ab); + double abbr = dotProduct(ab, br); + double brbr = dotProduct(br, br); + double abLen = sqrt(abab); + double brLen = sqrt(brbr); + double crs = crossProduct(ab, br); + double h = sqrt(abab+abbr+abbr+brbr); + return ( + brLen*((abbr+brbr)*h-abbr*abLen)+ + crs*crs*log((brLen*h+abbr+brbr)/(brLen*abLen+abbr)) + )/(brbr*brLen); +} + +SignedDistance LinearSegment::signedDistance(Point2 origin, double ¶m) const { + Vector2 aq = origin-p[0]; + Vector2 ab = p[1]-p[0]; + param = dotProduct(aq, ab)/dotProduct(ab, ab); + Vector2 eq = p[param > .5]-origin; + double endpointDistance = eq.length(); + if (param > 0 && param < 1) { + double orthoDistance = dotProduct(ab.getOrthonormal(false), aq); + if (fabs(orthoDistance) < endpointDistance) + return SignedDistance(orthoDistance, 0); + } + return SignedDistance(nonZeroSign(crossProduct(aq, ab))*endpointDistance, fabs(dotProduct(ab.normalize(), eq.normalize()))); +} + +SignedDistance QuadraticSegment::signedDistance(Point2 origin, double ¶m) const { + Vector2 qa = p[0]-origin; + Vector2 ab = p[1]-p[0]; + Vector2 br = p[2]-p[1]-ab; + double a = dotProduct(br, br); + double b = 3*dotProduct(ab, br); + double c = 2*dotProduct(ab, ab)+dotProduct(qa, br); + double d = dotProduct(qa, ab); + double t[3]; + int solutions = solveCubic(t, a, b, c, d); + + Vector2 epDir = direction(0); + double minDistance = nonZeroSign(crossProduct(epDir, qa))*qa.length(); // distance from A + param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir); + { + epDir = direction(1); + double distance = (p[2]-origin).length(); // distance from B + if (distance < fabs(minDistance)) { + minDistance = nonZeroSign(crossProduct(epDir, p[2]-origin))*distance; + param = dotProduct(origin-p[1], epDir)/dotProduct(epDir, epDir); + } + } + for (int i = 0; i < solutions; ++i) { + if (t[i] > 0 && t[i] < 1) { + Point2 qe = qa+2*t[i]*ab+t[i]*t[i]*br; + double distance = qe.length(); + if (distance <= fabs(minDistance)) { + minDistance = nonZeroSign(crossProduct(ab+t[i]*br, qe))*distance; + param = t[i]; + } + } + } + + if (param >= 0 && param <= 1) + return SignedDistance(minDistance, 0); + if (param < .5) + return SignedDistance(minDistance, fabs(dotProduct(direction(0).normalize(), qa.normalize()))); + else + return SignedDistance(minDistance, fabs(dotProduct(direction(1).normalize(), (p[2]-origin).normalize()))); +} + +SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const { + Vector2 qa = p[0]-origin; + Vector2 ab = p[1]-p[0]; + Vector2 br = p[2]-p[1]-ab; + Vector2 as = (p[3]-p[2])-(p[2]-p[1])-br; + + Vector2 epDir = direction(0); + double minDistance = nonZeroSign(crossProduct(epDir, qa))*qa.length(); // distance from A + param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir); + { + epDir = direction(1); + double distance = (p[3]-origin).length(); // distance from B + if (distance < fabs(minDistance)) { + minDistance = nonZeroSign(crossProduct(epDir, p[3]-origin))*distance; + param = dotProduct(epDir-(p[3]-origin), epDir)/dotProduct(epDir, epDir); + } + } + // Iterative minimum distance search + for (int i = 0; i <= MSDFGEN_CUBIC_SEARCH_STARTS; ++i) { + double t = (double) i/MSDFGEN_CUBIC_SEARCH_STARTS; + Vector2 qe = qa+3*t*ab+3*t*t*br+t*t*t*as; + for (int step = 0; step < MSDFGEN_CUBIC_SEARCH_STEPS; ++step) { + // Improve t + Vector2 d1 = 3*ab+6*t*br+3*t*t*as; + Vector2 d2 = 6*br+6*t*as; + t -= dotProduct(qe, d1)/(dotProduct(d1, d1)+dotProduct(qe, d2)); + if (t <= 0 || t >= 1) + break; + qe = qa+3*t*ab+3*t*t*br+t*t*t*as; + double distance = qe.length(); + if (distance < fabs(minDistance)) { + minDistance = nonZeroSign(crossProduct(d1, qe))*distance; + param = t; + } + } + } + + if (param >= 0 && param <= 1) + return SignedDistance(minDistance, 0); + if (param < .5) + return SignedDistance(minDistance, fabs(dotProduct(direction(0).normalize(), qa.normalize()))); + else + return SignedDistance(minDistance, fabs(dotProduct(direction(1).normalize(), (p[3]-origin).normalize()))); +} + +int LinearSegment::scanlineIntersections(double x[3], int dy[3], double y) const { + if ((y >= p[0].y && y < p[1].y) || (y >= p[1].y && y < p[0].y)) { + double param = (y-p[0].y)/(p[1].y-p[0].y); + x[0] = mix(p[0].x, p[1].x, param); + dy[0] = sign(p[1].y-p[0].y); + return 1; + } + return 0; +} + +int QuadraticSegment::scanlineIntersections(double x[3], int dy[3], double y) const { + int total = 0; + int nextDY = y > p[0].y ? 1 : -1; + x[total] = p[0].x; + if (p[0].y == y) { + if (p[0].y < p[1].y || (p[0].y == p[1].y && p[0].y < p[2].y)) + dy[total++] = 1; + else + nextDY = 1; + } + { + Vector2 ab = p[1]-p[0]; + Vector2 br = p[2]-p[1]-ab; + double t[2]; + int solutions = solveQuadratic(t, br.y, 2*ab.y, p[0].y-y); + // Sort solutions + double tmp; + if (solutions >= 2 && t[0] > t[1]) + tmp = t[0], t[0] = t[1], t[1] = tmp; + for (int i = 0; i < solutions && total < 2; ++i) { + if (t[i] >= 0 && t[i] <= 1) { + x[total] = p[0].x+2*t[i]*ab.x+t[i]*t[i]*br.x; + if (nextDY*(ab.y+t[i]*br.y) >= 0) { + dy[total++] = nextDY; + nextDY = -nextDY; + } + } + } + } + if (p[2].y == y) { + if (nextDY > 0 && total > 0) { + --total; + nextDY = -1; + } + if ((p[2].y < p[1].y || (p[2].y == p[1].y && p[2].y < p[0].y)) && total < 2) { + x[total] = p[2].x; + if (nextDY < 0) { + dy[total++] = -1; + nextDY = 1; + } + } + } + if (nextDY != (y >= p[2].y ? 1 : -1)) { + if (total > 0) + --total; + else { + if (fabs(p[2].y-y) < fabs(p[0].y-y)) + x[total] = p[2].x; + dy[total++] = nextDY; + } + } + return total; +} + +int CubicSegment::scanlineIntersections(double x[3], int dy[3], double y) const { + int total = 0; + int nextDY = y > p[0].y ? 1 : -1; + x[total] = p[0].x; + if (p[0].y == y) { + if (p[0].y < p[1].y || (p[0].y == p[1].y && (p[0].y < p[2].y || (p[0].y == p[2].y && p[0].y < p[3].y)))) + dy[total++] = 1; + else + nextDY = 1; + } + { + Vector2 ab = p[1]-p[0]; + Vector2 br = p[2]-p[1]-ab; + Vector2 as = (p[3]-p[2])-(p[2]-p[1])-br; + double t[3]; + int solutions = solveCubic(t, as.y, 3*br.y, 3*ab.y, p[0].y-y); + // Sort solutions + double tmp; + if (solutions >= 2) { + if (t[0] > t[1]) + tmp = t[0], t[0] = t[1], t[1] = tmp; + if (solutions >= 3 && t[1] > t[2]) { + tmp = t[1], t[1] = t[2], t[2] = tmp; + if (t[0] > t[1]) + tmp = t[0], t[0] = t[1], t[1] = tmp; + } + } + for (int i = 0; i < solutions && total < 3; ++i) { + if (t[i] >= 0 && t[i] <= 1) { + x[total] = p[0].x+3*t[i]*ab.x+3*t[i]*t[i]*br.x+t[i]*t[i]*t[i]*as.x; + if (nextDY*(ab.y+2*t[i]*br.y+t[i]*t[i]*as.y) >= 0) { + dy[total++] = nextDY; + nextDY = -nextDY; + } + } + } + } + if (p[3].y == y) { + if (nextDY > 0 && total > 0) { + --total; + nextDY = -1; + } + if ((p[3].y < p[2].y || (p[3].y == p[2].y && (p[3].y < p[1].y || (p[3].y == p[1].y && p[3].y < p[0].y)))) && total < 3) { + x[total] = p[3].x; + if (nextDY < 0) { + dy[total++] = -1; + nextDY = 1; + } + } + } + if (nextDY != (y >= p[3].y ? 1 : -1)) { + if (total > 0) + --total; + else { + if (fabs(p[3].y-y) < fabs(p[0].y-y)) + x[total] = p[3].x; + dy[total++] = nextDY; + } + } + return total; +} + +static void pointBounds(Point2 p, double &l, double &b, double &r, double &t) { + if (p.x < l) l = p.x; + if (p.y < b) b = p.y; + if (p.x > r) r = p.x; + if (p.y > t) t = p.y; +} + +void LinearSegment::bound(double &l, double &b, double &r, double &t) const { + pointBounds(p[0], l, b, r, t); + pointBounds(p[1], l, b, r, t); +} + +void QuadraticSegment::bound(double &l, double &b, double &r, double &t) const { + pointBounds(p[0], l, b, r, t); + pointBounds(p[2], l, b, r, t); + Vector2 bot = (p[1]-p[0])-(p[2]-p[1]); + if (bot.x) { + double param = (p[1].x-p[0].x)/bot.x; + if (param > 0 && param < 1) + pointBounds(point(param), l, b, r, t); + } + if (bot.y) { + double param = (p[1].y-p[0].y)/bot.y; + if (param > 0 && param < 1) + pointBounds(point(param), l, b, r, t); + } +} + +void CubicSegment::bound(double &l, double &b, double &r, double &t) const { + pointBounds(p[0], l, b, r, t); + pointBounds(p[3], l, b, r, t); + Vector2 a0 = p[1]-p[0]; + Vector2 a1 = 2*(p[2]-p[1]-a0); + Vector2 a2 = p[3]-3*p[2]+3*p[1]-p[0]; + double params[2]; + int solutions; + solutions = solveQuadratic(params, a2.x, a1.x, a0.x); + for (int i = 0; i < solutions; ++i) + if (params[i] > 0 && params[i] < 1) + pointBounds(point(params[i]), l, b, r, t); + solutions = solveQuadratic(params, a2.y, a1.y, a0.y); + for (int i = 0; i < solutions; ++i) + if (params[i] > 0 && params[i] < 1) + pointBounds(point(params[i]), l, b, r, t); +} + +void LinearSegment::reverse() { + Point2 tmp = p[0]; + p[0] = p[1]; + p[1] = tmp; +} + +void QuadraticSegment::reverse() { + Point2 tmp = p[0]; + p[0] = p[2]; + p[2] = tmp; +} + +void CubicSegment::reverse() { + Point2 tmp = p[0]; + p[0] = p[3]; + p[3] = tmp; + tmp = p[1]; + p[1] = p[2]; + p[2] = tmp; +} + +void LinearSegment::moveStartPoint(Point2 to) { + p[0] = to; +} + +void QuadraticSegment::moveStartPoint(Point2 to) { + Vector2 origSDir = p[0]-p[1]; + Point2 origP1 = p[1]; + p[1] += crossProduct(p[0]-p[1], to-p[0])/crossProduct(p[0]-p[1], p[2]-p[1])*(p[2]-p[1]); + p[0] = to; + if (dotProduct(origSDir, p[0]-p[1]) < 0) + p[1] = origP1; +} + +void CubicSegment::moveStartPoint(Point2 to) { + p[1] += to-p[0]; + p[0] = to; +} + +void LinearSegment::moveEndPoint(Point2 to) { + p[1] = to; +} + +void QuadraticSegment::moveEndPoint(Point2 to) { + Vector2 origEDir = p[2]-p[1]; + Point2 origP1 = p[1]; + p[1] += crossProduct(p[2]-p[1], to-p[2])/crossProduct(p[2]-p[1], p[0]-p[1])*(p[0]-p[1]); + p[2] = to; + if (dotProduct(origEDir, p[2]-p[1]) < 0) + p[1] = origP1; +} + +void CubicSegment::moveEndPoint(Point2 to) { + p[2] += to-p[3]; + p[3] = to; +} + +void LinearSegment::splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const { + part1 = new LinearSegment(p[0], point(1/3.), color); + part2 = new LinearSegment(point(1/3.), point(2/3.), color); + part3 = new LinearSegment(point(2/3.), p[1], color); +} + +void QuadraticSegment::splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const { + part1 = new QuadraticSegment(p[0], mix(p[0], p[1], 1/3.), point(1/3.), color); + part2 = new QuadraticSegment(point(1/3.), mix(mix(p[0], p[1], 5/9.), mix(p[1], p[2], 4/9.), .5), point(2/3.), color); + part3 = new QuadraticSegment(point(2/3.), mix(p[1], p[2], 2/3.), p[2], color); +} + +void CubicSegment::splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const { + part1 = new CubicSegment(p[0], p[0] == p[1] ? p[0] : mix(p[0], p[1], 1/3.), mix(mix(p[0], p[1], 1/3.), mix(p[1], p[2], 1/3.), 1/3.), point(1/3.), color); + part2 = new CubicSegment(point(1/3.), + mix(mix(mix(p[0], p[1], 1/3.), mix(p[1], p[2], 1/3.), 1/3.), mix(mix(p[1], p[2], 1/3.), mix(p[2], p[3], 1/3.), 1/3.), 2/3.), + mix(mix(mix(p[0], p[1], 2/3.), mix(p[1], p[2], 2/3.), 2/3.), mix(mix(p[1], p[2], 2/3.), mix(p[2], p[3], 2/3.), 2/3.), 1/3.), + point(2/3.), color); + part3 = new CubicSegment(point(2/3.), mix(mix(p[1], p[2], 2/3.), mix(p[2], p[3], 2/3.), 2/3.), p[2] == p[3] ? p[3] : mix(p[2], p[3], 2/3.), p[3], color); +} + +EdgeSegment * QuadraticSegment::convertToCubic() const { + return new CubicSegment(p[0], mix(p[0], p[1], 2/3.), mix(p[1], p[2], 1/3.), p[2], color); +} + +void CubicSegment::deconverge(int param, double amount) { + Vector2 dir = direction(param); + Vector2 normal = dir.getOrthonormal(); + double h = dotProduct(directionChange(param)-dir, normal); + switch (param) { + case 0: + p[1] += amount*(dir+sign(h)*sqrt(fabs(h))*normal); + break; + case 1: + p[2] -= amount*(dir-sign(h)*sqrt(fabs(h))*normal); + break; + } +} + +} diff --git a/thirdparty/msdfgen/core/edge-segments.h b/thirdparty/msdfgen/core/edge-segments.h new file mode 100644 index 0000000000..1c8fb599ff --- /dev/null +++ b/thirdparty/msdfgen/core/edge-segments.h @@ -0,0 +1,122 @@ + +#pragma once + +#include "Vector2.h" +#include "SignedDistance.h" +#include "EdgeColor.h" + +namespace msdfgen { + +// Parameters for iterative search of closest point on a cubic Bezier curve. Increase for higher precision. +#define MSDFGEN_CUBIC_SEARCH_STARTS 4 +#define MSDFGEN_CUBIC_SEARCH_STEPS 4 + +/// An abstract edge segment. +class EdgeSegment { + +public: + EdgeColor color; + + EdgeSegment(EdgeColor edgeColor = WHITE) : color(edgeColor) { } + virtual ~EdgeSegment() { } + /// Creates a copy of the edge segment. + virtual EdgeSegment * clone() const = 0; + /// Returns the point on the edge specified by the parameter (between 0 and 1). + virtual Point2 point(double param) const = 0; + /// Returns the direction the edge has at the point specified by the parameter. + virtual Vector2 direction(double param) const = 0; + /// Returns the change of direction (second derivative) at the point specified by the parameter. + virtual Vector2 directionChange(double param) const = 0; + /// Returns the minimum signed distance between origin and the edge. + virtual SignedDistance signedDistance(Point2 origin, double ¶m) const = 0; + /// Converts a previously retrieved signed distance from origin to pseudo-distance. + virtual void distanceToPseudoDistance(SignedDistance &distance, Point2 origin, double param) const; + /// Outputs a list of (at most three) intersections (their X coordinates) with an infinite horizontal scanline at y and returns how many there are. + virtual int scanlineIntersections(double x[3], int dy[3], double y) const = 0; + /// Adjusts the bounding box to fit the edge segment. + virtual void bound(double &l, double &b, double &r, double &t) const = 0; + + /// Reverses the edge (swaps its start point and end point). + virtual void reverse() = 0; + /// Moves the start point of the edge segment. + virtual void moveStartPoint(Point2 to) = 0; + /// Moves the end point of the edge segment. + virtual void moveEndPoint(Point2 to) = 0; + /// Splits the edge segments into thirds which together represent the original edge. + virtual void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const = 0; + +}; + +/// A line segment. +class LinearSegment : public EdgeSegment { + +public: + Point2 p[2]; + + LinearSegment(Point2 p0, Point2 p1, EdgeColor edgeColor = WHITE); + LinearSegment * clone() const; + Point2 point(double param) const; + Vector2 direction(double param) const; + Vector2 directionChange(double param) const; + double length() const; + SignedDistance signedDistance(Point2 origin, double ¶m) const; + int scanlineIntersections(double x[3], int dy[3], double y) const; + void bound(double &l, double &b, double &r, double &t) const; + + void reverse(); + void moveStartPoint(Point2 to); + void moveEndPoint(Point2 to); + void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const; + +}; + +/// A quadratic Bezier curve. +class QuadraticSegment : public EdgeSegment { + +public: + Point2 p[3]; + + QuadraticSegment(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor = WHITE); + QuadraticSegment * clone() const; + Point2 point(double param) const; + Vector2 direction(double param) const; + Vector2 directionChange(double param) const; + double length() const; + SignedDistance signedDistance(Point2 origin, double ¶m) const; + int scanlineIntersections(double x[3], int dy[3], double y) const; + void bound(double &l, double &b, double &r, double &t) const; + + void reverse(); + void moveStartPoint(Point2 to); + void moveEndPoint(Point2 to); + void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const; + + EdgeSegment * convertToCubic() const; + +}; + +/// A cubic Bezier curve. +class CubicSegment : public EdgeSegment { + +public: + Point2 p[4]; + + CubicSegment(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor = WHITE); + CubicSegment * clone() const; + Point2 point(double param) const; + Vector2 direction(double param) const; + Vector2 directionChange(double param) const; + SignedDistance signedDistance(Point2 origin, double ¶m) const; + int scanlineIntersections(double x[3], int dy[3], double y) const; + void bound(double &l, double &b, double &r, double &t) const; + + void reverse(); + void moveStartPoint(Point2 to); + void moveEndPoint(Point2 to); + void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const; + + void deconverge(int param, double amount); + +}; + +} diff --git a/thirdparty/msdfgen/core/edge-selectors.cpp b/thirdparty/msdfgen/core/edge-selectors.cpp new file mode 100644 index 0000000000..aee78847fb --- /dev/null +++ b/thirdparty/msdfgen/core/edge-selectors.cpp @@ -0,0 +1,261 @@ + +#include "edge-selectors.h" + +#include "arithmetics.hpp" + +namespace msdfgen { + +#define DISTANCE_DELTA_FACTOR 1.001 + +TrueDistanceSelector::EdgeCache::EdgeCache() : absDistance(0) { } + +void TrueDistanceSelector::reset(const Point2 &p) { + double delta = DISTANCE_DELTA_FACTOR*(p-this->p).length(); + minDistance.distance += nonZeroSign(minDistance.distance)*delta; + this->p = p; +} + +void TrueDistanceSelector::addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge) { + double delta = DISTANCE_DELTA_FACTOR*(p-cache.point).length(); + if (cache.absDistance-delta <= fabs(minDistance.distance)) { + double dummy; + SignedDistance distance = edge->signedDistance(p, dummy); + if (distance < minDistance) + minDistance = distance; + cache.point = p; + cache.absDistance = fabs(distance.distance); + } +} + +void TrueDistanceSelector::merge(const TrueDistanceSelector &other) { + if (other.minDistance < minDistance) + minDistance = other.minDistance; +} + +TrueDistanceSelector::DistanceType TrueDistanceSelector::distance() const { + return minDistance.distance; +} + +PseudoDistanceSelectorBase::EdgeCache::EdgeCache() : absDistance(0), aDomainDistance(0), bDomainDistance(0), aPseudoDistance(0), bPseudoDistance(0) { } + +bool PseudoDistanceSelectorBase::getPseudoDistance(double &distance, const Vector2 &ep, const Vector2 &edgeDir) { + double ts = dotProduct(ep, edgeDir); + if (ts > 0) { + double pseudoDistance = crossProduct(ep, edgeDir); + if (fabs(pseudoDistance) < fabs(distance)) { + distance = pseudoDistance; + return true; + } + } + return false; +} + +PseudoDistanceSelectorBase::PseudoDistanceSelectorBase() : minNegativePseudoDistance(-fabs(minTrueDistance.distance)), minPositivePseudoDistance(fabs(minTrueDistance.distance)), nearEdge(NULL), nearEdgeParam(0) { } + +void PseudoDistanceSelectorBase::reset(double delta) { + minTrueDistance.distance += nonZeroSign(minTrueDistance.distance)*delta; + minNegativePseudoDistance = -fabs(minTrueDistance.distance); + minPositivePseudoDistance = fabs(minTrueDistance.distance); + nearEdge = NULL; + nearEdgeParam = 0; +} + +bool PseudoDistanceSelectorBase::isEdgeRelevant(const EdgeCache &cache, const EdgeSegment *edge, const Point2 &p) const { + double delta = DISTANCE_DELTA_FACTOR*(p-cache.point).length(); + return ( + cache.absDistance-delta <= fabs(minTrueDistance.distance) || + fabs(cache.aDomainDistance) < delta || + fabs(cache.bDomainDistance) < delta || + (cache.aDomainDistance > 0 && (cache.aPseudoDistance < 0 ? + cache.aPseudoDistance+delta >= minNegativePseudoDistance : + cache.aPseudoDistance-delta <= minPositivePseudoDistance + )) || + (cache.bDomainDistance > 0 && (cache.bPseudoDistance < 0 ? + cache.bPseudoDistance+delta >= minNegativePseudoDistance : + cache.bPseudoDistance-delta <= minPositivePseudoDistance + )) + ); +} + +void PseudoDistanceSelectorBase::addEdgeTrueDistance(const EdgeSegment *edge, const SignedDistance &distance, double param) { + if (distance < minTrueDistance) { + minTrueDistance = distance; + nearEdge = edge; + nearEdgeParam = param; + } +} + +void PseudoDistanceSelectorBase::addEdgePseudoDistance(double distance) { + if (distance <= 0 && distance > minNegativePseudoDistance) + minNegativePseudoDistance = distance; + if (distance >= 0 && distance < minPositivePseudoDistance) + minPositivePseudoDistance = distance; +} + +void PseudoDistanceSelectorBase::merge(const PseudoDistanceSelectorBase &other) { + if (other.minTrueDistance < minTrueDistance) { + minTrueDistance = other.minTrueDistance; + nearEdge = other.nearEdge; + nearEdgeParam = other.nearEdgeParam; + } + if (other.minNegativePseudoDistance > minNegativePseudoDistance) + minNegativePseudoDistance = other.minNegativePseudoDistance; + if (other.minPositivePseudoDistance < minPositivePseudoDistance) + minPositivePseudoDistance = other.minPositivePseudoDistance; +} + +double PseudoDistanceSelectorBase::computeDistance(const Point2 &p) const { + double minDistance = minTrueDistance.distance < 0 ? minNegativePseudoDistance : minPositivePseudoDistance; + if (nearEdge) { + SignedDistance distance = minTrueDistance; + nearEdge->distanceToPseudoDistance(distance, p, nearEdgeParam); + if (fabs(distance.distance) < fabs(minDistance)) + minDistance = distance.distance; + } + return minDistance; +} + +SignedDistance PseudoDistanceSelectorBase::trueDistance() const { + return minTrueDistance; +} + +void PseudoDistanceSelector::reset(const Point2 &p) { + double delta = DISTANCE_DELTA_FACTOR*(p-this->p).length(); + PseudoDistanceSelectorBase::reset(delta); + this->p = p; +} + +void PseudoDistanceSelector::addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge) { + if (isEdgeRelevant(cache, edge, p)) { + double param; + SignedDistance distance = edge->signedDistance(p, param); + addEdgeTrueDistance(edge, distance, param); + cache.point = p; + cache.absDistance = fabs(distance.distance); + + Vector2 ap = p-edge->point(0); + Vector2 bp = p-edge->point(1); + Vector2 aDir = edge->direction(0).normalize(true); + Vector2 bDir = edge->direction(1).normalize(true); + Vector2 prevDir = prevEdge->direction(1).normalize(true); + Vector2 nextDir = nextEdge->direction(0).normalize(true); + double add = dotProduct(ap, (prevDir+aDir).normalize(true)); + double bdd = -dotProduct(bp, (bDir+nextDir).normalize(true)); + if (add > 0) { + double pd = distance.distance; + if (getPseudoDistance(pd, ap, -aDir)) + addEdgePseudoDistance(pd = -pd); + cache.aPseudoDistance = pd; + } + if (bdd > 0) { + double pd = distance.distance; + if (getPseudoDistance(pd, bp, bDir)) + addEdgePseudoDistance(pd); + cache.bPseudoDistance = pd; + } + cache.aDomainDistance = add; + cache.bDomainDistance = bdd; + } +} + +PseudoDistanceSelector::DistanceType PseudoDistanceSelector::distance() const { + return computeDistance(p); +} + +void MultiDistanceSelector::reset(const Point2 &p) { + double delta = DISTANCE_DELTA_FACTOR*(p-this->p).length(); + r.reset(delta); + g.reset(delta); + b.reset(delta); + this->p = p; +} + +void MultiDistanceSelector::addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge) { + if ( + (edge->color&RED && r.isEdgeRelevant(cache, edge, p)) || + (edge->color&GREEN && g.isEdgeRelevant(cache, edge, p)) || + (edge->color&BLUE && b.isEdgeRelevant(cache, edge, p)) + ) { + double param; + SignedDistance distance = edge->signedDistance(p, param); + if (edge->color&RED) + r.addEdgeTrueDistance(edge, distance, param); + if (edge->color&GREEN) + g.addEdgeTrueDistance(edge, distance, param); + if (edge->color&BLUE) + b.addEdgeTrueDistance(edge, distance, param); + cache.point = p; + cache.absDistance = fabs(distance.distance); + + Vector2 ap = p-edge->point(0); + Vector2 bp = p-edge->point(1); + Vector2 aDir = edge->direction(0).normalize(true); + Vector2 bDir = edge->direction(1).normalize(true); + Vector2 prevDir = prevEdge->direction(1).normalize(true); + Vector2 nextDir = nextEdge->direction(0).normalize(true); + double add = dotProduct(ap, (prevDir+aDir).normalize(true)); + double bdd = -dotProduct(bp, (bDir+nextDir).normalize(true)); + if (add > 0) { + double pd = distance.distance; + if (PseudoDistanceSelectorBase::getPseudoDistance(pd, ap, -aDir)) { + pd = -pd; + if (edge->color&RED) + r.addEdgePseudoDistance(pd); + if (edge->color&GREEN) + g.addEdgePseudoDistance(pd); + if (edge->color&BLUE) + b.addEdgePseudoDistance(pd); + } + cache.aPseudoDistance = pd; + } + if (bdd > 0) { + double pd = distance.distance; + if (PseudoDistanceSelectorBase::getPseudoDistance(pd, bp, bDir)) { + if (edge->color&RED) + r.addEdgePseudoDistance(pd); + if (edge->color&GREEN) + g.addEdgePseudoDistance(pd); + if (edge->color&BLUE) + b.addEdgePseudoDistance(pd); + } + cache.bPseudoDistance = pd; + } + cache.aDomainDistance = add; + cache.bDomainDistance = bdd; + } +} + +void MultiDistanceSelector::merge(const MultiDistanceSelector &other) { + r.merge(other.r); + g.merge(other.g); + b.merge(other.b); +} + +MultiDistanceSelector::DistanceType MultiDistanceSelector::distance() const { + MultiDistance multiDistance; + multiDistance.r = r.computeDistance(p); + multiDistance.g = g.computeDistance(p); + multiDistance.b = b.computeDistance(p); + return multiDistance; +} + +SignedDistance MultiDistanceSelector::trueDistance() const { + SignedDistance distance = r.trueDistance(); + if (g.trueDistance() < distance) + distance = g.trueDistance(); + if (b.trueDistance() < distance) + distance = b.trueDistance(); + return distance; +} + +MultiAndTrueDistanceSelector::DistanceType MultiAndTrueDistanceSelector::distance() const { + MultiDistance multiDistance = MultiDistanceSelector::distance(); + MultiAndTrueDistance mtd; + mtd.r = multiDistance.r; + mtd.g = multiDistance.g; + mtd.b = multiDistance.b; + mtd.a = trueDistance().distance; + return mtd; +} + +} diff --git a/thirdparty/msdfgen/core/edge-selectors.h b/thirdparty/msdfgen/core/edge-selectors.h new file mode 100644 index 0000000000..3620999f82 --- /dev/null +++ b/thirdparty/msdfgen/core/edge-selectors.h @@ -0,0 +1,117 @@ + +#pragma once + +#include "Vector2.h" +#include "SignedDistance.h" +#include "edge-segments.h" + +namespace msdfgen { + +struct MultiDistance { + double r, g, b; +}; +struct MultiAndTrueDistance : MultiDistance { + double a; +}; + +/// Selects the nearest edge by its true distance. +class TrueDistanceSelector { + +public: + typedef double DistanceType; + + struct EdgeCache { + Point2 point; + double absDistance; + + EdgeCache(); + }; + + void reset(const Point2 &p); + void addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge); + void merge(const TrueDistanceSelector &other); + DistanceType distance() const; + +private: + Point2 p; + SignedDistance minDistance; + +}; + +class PseudoDistanceSelectorBase { + +public: + struct EdgeCache { + Point2 point; + double absDistance; + double aDomainDistance, bDomainDistance; + double aPseudoDistance, bPseudoDistance; + + EdgeCache(); + }; + + static bool getPseudoDistance(double &distance, const Vector2 &ep, const Vector2 &edgeDir); + + PseudoDistanceSelectorBase(); + void reset(double delta); + bool isEdgeRelevant(const EdgeCache &cache, const EdgeSegment *edge, const Point2 &p) const; + void addEdgeTrueDistance(const EdgeSegment *edge, const SignedDistance &distance, double param); + void addEdgePseudoDistance(double distance); + void merge(const PseudoDistanceSelectorBase &other); + double computeDistance(const Point2 &p) const; + SignedDistance trueDistance() const; + +private: + SignedDistance minTrueDistance; + double minNegativePseudoDistance; + double minPositivePseudoDistance; + const EdgeSegment *nearEdge; + double nearEdgeParam; + +}; + +/// Selects the nearest edge by its pseudo-distance. +class PseudoDistanceSelector : public PseudoDistanceSelectorBase { + +public: + typedef double DistanceType; + + void reset(const Point2 &p); + void addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge); + DistanceType distance() const; + +private: + Point2 p; + +}; + +/// Selects the nearest edge for each of the three channels by its pseudo-distance. +class MultiDistanceSelector { + +public: + typedef MultiDistance DistanceType; + typedef PseudoDistanceSelectorBase::EdgeCache EdgeCache; + + void reset(const Point2 &p); + void addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge); + void merge(const MultiDistanceSelector &other); + DistanceType distance() const; + SignedDistance trueDistance() const; + +private: + Point2 p; + PseudoDistanceSelectorBase r, g, b; + +}; + +/// Selects the nearest edge for each of the three color channels by its pseudo-distance and by true distance for the alpha channel. +class MultiAndTrueDistanceSelector : public MultiDistanceSelector { + +public: + typedef MultiAndTrueDistance DistanceType; + + DistanceType distance() const; + +}; + +} diff --git a/thirdparty/msdfgen/core/equation-solver.cpp b/thirdparty/msdfgen/core/equation-solver.cpp new file mode 100644 index 0000000000..fbe906428b --- /dev/null +++ b/thirdparty/msdfgen/core/equation-solver.cpp @@ -0,0 +1,77 @@ + +#include "equation-solver.h" + +#define _USE_MATH_DEFINES +#include <cmath> + +#define TOO_LARGE_RATIO 1e12 + +namespace msdfgen { + +int solveQuadratic(double x[2], double a, double b, double c) { + // a = 0 -> linear equation + if (a == 0 || fabs(b)+fabs(c) > TOO_LARGE_RATIO*fabs(a)) { + // a, b = 0 -> no solution + if (b == 0 || fabs(c) > TOO_LARGE_RATIO*fabs(b)) { + if (c == 0) + return -1; // 0 = 0 + return 0; + } + x[0] = -c/b; + return 1; + } + double dscr = b*b-4*a*c; + if (dscr > 0) { + dscr = sqrt(dscr); + x[0] = (-b+dscr)/(2*a); + x[1] = (-b-dscr)/(2*a); + return 2; + } else if (dscr == 0) { + x[0] = -b/(2*a); + return 1; + } else + return 0; +} + +static int solveCubicNormed(double x[3], double a, double b, double c) { + double a2 = a*a; + double q = (a2 - 3*b)/9; + double r = (a*(2*a2-9*b) + 27*c)/54; + double r2 = r*r; + double q3 = q*q*q; + double A, B; + if (r2 < q3) { + double t = r/sqrt(q3); + if (t < -1) t = -1; + if (t > 1) t = 1; + t = acos(t); + a /= 3; q = -2*sqrt(q); + x[0] = q*cos(t/3)-a; + x[1] = q*cos((t+2*M_PI)/3)-a; + x[2] = q*cos((t-2*M_PI)/3)-a; + return 3; + } else { + A = -pow(fabs(r)+sqrt(r2-q3), 1/3.); + if (r < 0) A = -A; + B = A == 0 ? 0 : q/A; + a /= 3; + x[0] = (A+B)-a; + x[1] = -0.5*(A+B)-a; + x[2] = 0.5*sqrt(3.)*(A-B); + if (fabs(x[2]) < 1e-14) + return 2; + return 1; + } +} + +int solveCubic(double x[3], double a, double b, double c, double d) { + if (a != 0) { + double bn = b/a, cn = c/a, dn = d/a; + // Check that a isn't "almost zero" + if (fabs(bn) < TOO_LARGE_RATIO && fabs(cn) < TOO_LARGE_RATIO && fabs(dn) < TOO_LARGE_RATIO) + return solveCubicNormed(x, bn, cn, dn); + } + return solveQuadratic(x, b, c, d); +} + +} diff --git a/thirdparty/msdfgen/core/equation-solver.h b/thirdparty/msdfgen/core/equation-solver.h new file mode 100644 index 0000000000..bae097b2b9 --- /dev/null +++ b/thirdparty/msdfgen/core/equation-solver.h @@ -0,0 +1,12 @@ + +#pragma once + +namespace msdfgen { + +// ax^2 + bx + c = 0 +int solveQuadratic(double x[2], double a, double b, double c); + +// ax^3 + bx^2 + cx + d = 0 +int solveCubic(double x[3], double a, double b, double c, double d); + +} diff --git a/thirdparty/msdfgen/core/generator-config.h b/thirdparty/msdfgen/core/generator-config.h new file mode 100644 index 0000000000..ddcad961f2 --- /dev/null +++ b/thirdparty/msdfgen/core/generator-config.h @@ -0,0 +1,63 @@ + +#pragma once + +#include <cstdlib> +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// The configuration of the MSDF error correction pass. +struct ErrorCorrectionConfig { + /// The default value of minDeviationRatio. + static const double defaultMinDeviationRatio; + /// The default value of minImproveRatio. + static const double defaultMinImproveRatio; + + /// Mode of operation. + enum Mode { + /// Skips error correction pass. + DISABLED, + /// Corrects all discontinuities of the distance field regardless if edges are adversely affected. + INDISCRIMINATE, + /// Corrects artifacts at edges and other discontinuous distances only if it does not affect edges or corners. + EDGE_PRIORITY, + /// Only corrects artifacts at edges. + EDGE_ONLY + } mode; + /// Configuration of whether to use an algorithm that computes the exact shape distance at the positions of suspected artifacts. This algorithm can be much slower. + enum DistanceCheckMode { + /// Never computes exact shape distance. + DO_NOT_CHECK_DISTANCE, + /// Only computes exact shape distance at edges. Provides a good balance between speed and precision. + CHECK_DISTANCE_AT_EDGE, + /// Computes and compares the exact shape distance for each suspected artifact. + ALWAYS_CHECK_DISTANCE + } distanceCheckMode; + /// The minimum ratio between the actual and maximum expected distance delta to be considered an error. + double minDeviationRatio; + /// The minimum ratio between the pre-correction distance error and the post-correction distance error. Has no effect for DO_NOT_CHECK_DISTANCE. + double minImproveRatio; + /// An optional buffer to avoid dynamic allocation. Must have at least as many bytes as the MSDF has pixels. + byte *buffer; + + inline explicit ErrorCorrectionConfig(Mode mode = EDGE_PRIORITY, DistanceCheckMode distanceCheckMode = CHECK_DISTANCE_AT_EDGE, double minDeviationRatio = defaultMinDeviationRatio, double minImproveRatio = defaultMinImproveRatio, byte *buffer = NULL) : mode(mode), distanceCheckMode(distanceCheckMode), minDeviationRatio(minDeviationRatio), minImproveRatio(minImproveRatio), buffer(buffer) { } +}; + +/// The configuration of the distance field generator algorithm. +struct GeneratorConfig { + /// Specifies whether to use the version of the algorithm that supports overlapping contours with the same winding. May be set to false to improve performance when no such contours are present. + bool overlapSupport; + + inline explicit GeneratorConfig(bool overlapSupport = true) : overlapSupport(overlapSupport) { } +}; + +/// The configuration of the multi-channel distance field generator algorithm. +struct MSDFGeneratorConfig : GeneratorConfig { + /// Configuration of the error correction pass. + ErrorCorrectionConfig errorCorrection; + + inline MSDFGeneratorConfig() { } + inline explicit MSDFGeneratorConfig(bool overlapSupport, const ErrorCorrectionConfig &errorCorrection = ErrorCorrectionConfig()) : GeneratorConfig(overlapSupport), errorCorrection(errorCorrection) { } +}; + +} diff --git a/thirdparty/msdfgen/core/msdf-error-correction.cpp b/thirdparty/msdfgen/core/msdf-error-correction.cpp new file mode 100644 index 0000000000..21ddff8c85 --- /dev/null +++ b/thirdparty/msdfgen/core/msdf-error-correction.cpp @@ -0,0 +1,154 @@ + +#include "msdf-error-correction.h" + +#include <vector> +#include "arithmetics.hpp" +#include "Bitmap.h" +#include "contour-combiners.h" +#include "MSDFErrorCorrection.h" + +namespace msdfgen { + +template <int N> +static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) { + if (config.errorCorrection.mode == ErrorCorrectionConfig::DISABLED) + return; + Bitmap<byte, 1> stencilBuffer; + if (!config.errorCorrection.buffer) + stencilBuffer = Bitmap<byte, 1>(sdf.width, sdf.height); + BitmapRef<byte, 1> stencil; + stencil.pixels = config.errorCorrection.buffer ? config.errorCorrection.buffer : (byte *) stencilBuffer; + stencil.width = sdf.width, stencil.height = sdf.height; + MSDFErrorCorrection ec(stencil, projection, range); + ec.setMinDeviationRatio(config.errorCorrection.minDeviationRatio); + ec.setMinImproveRatio(config.errorCorrection.minImproveRatio); + switch (config.errorCorrection.mode) { + case ErrorCorrectionConfig::DISABLED: + case ErrorCorrectionConfig::INDISCRIMINATE: + break; + case ErrorCorrectionConfig::EDGE_PRIORITY: + ec.protectCorners(shape); + ec.protectEdges<N>(sdf); + break; + case ErrorCorrectionConfig::EDGE_ONLY: + ec.protectAll(); + break; + } + if (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE || (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE && config.errorCorrection.mode != ErrorCorrectionConfig::EDGE_ONLY)) { + ec.findErrors<N>(sdf); + if (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE) + ec.protectAll(); + } + if (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE || config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE) { + if (config.overlapSupport) + ec.findErrors<OverlappingContourCombiner, N>(sdf, shape); + else + ec.findErrors<SimpleContourCombiner, N>(sdf, shape); + } + ec.apply(sdf); +} + +template <int N> +static void msdfErrorCorrectionShapeless(const BitmapRef<float, N> &sdf, const Projection &projection, double range, double minDeviationRatio, bool protectAll) { + Bitmap<byte, 1> stencilBuffer(sdf.width, sdf.height); + MSDFErrorCorrection ec(stencilBuffer, projection, range); + ec.setMinDeviationRatio(minDeviationRatio); + if (protectAll) + ec.protectAll(); + ec.findErrors<N>(sdf); + ec.apply(sdf); +} + +void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) { + msdfErrorCorrectionInner(sdf, shape, projection, range, config); +} +void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) { + msdfErrorCorrectionInner(sdf, shape, projection, range, config); +} + +void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio) { + msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, false); +} +void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio) { + msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, false); +} + +void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio) { + msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, true); +} +void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio) { + msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, true); +} + + +// Legacy version + +inline static bool detectClash(const float *a, const float *b, double threshold) { + // Sort channels so that pairs (a0, b0), (a1, b1), (a2, b2) go from biggest to smallest absolute difference + float a0 = a[0], a1 = a[1], a2 = a[2]; + float b0 = b[0], b1 = b[1], b2 = b[2]; + float tmp; + if (fabsf(b0-a0) < fabsf(b1-a1)) { + tmp = a0, a0 = a1, a1 = tmp; + tmp = b0, b0 = b1, b1 = tmp; + } + if (fabsf(b1-a1) < fabsf(b2-a2)) { + tmp = a1, a1 = a2, a2 = tmp; + tmp = b1, b1 = b2, b2 = tmp; + if (fabsf(b0-a0) < fabsf(b1-a1)) { + tmp = a0, a0 = a1, a1 = tmp; + tmp = b0, b0 = b1, b1 = tmp; + } + } + return (fabsf(b1-a1) >= threshold) && + !(b0 == b1 && b0 == b2) && // Ignore if other pixel has been equalized + fabsf(a2-.5f) >= fabsf(b2-.5f); // Out of the pair, only flag the pixel farther from a shape edge +} + +template <int N> +static void msdfErrorCorrectionInner_legacy(const BitmapRef<float, N> &output, const Vector2 &threshold) { + std::vector<std::pair<int, int> > clashes; + int w = output.width, h = output.height; + for (int y = 0; y < h; ++y) + for (int x = 0; x < w; ++x) { + if ( + (x > 0 && detectClash(output(x, y), output(x-1, y), threshold.x)) || + (x < w-1 && detectClash(output(x, y), output(x+1, y), threshold.x)) || + (y > 0 && detectClash(output(x, y), output(x, y-1), threshold.y)) || + (y < h-1 && detectClash(output(x, y), output(x, y+1), threshold.y)) + ) + clashes.push_back(std::make_pair(x, y)); + } + for (std::vector<std::pair<int, int> >::const_iterator clash = clashes.begin(); clash != clashes.end(); ++clash) { + float *pixel = output(clash->first, clash->second); + float med = median(pixel[0], pixel[1], pixel[2]); + pixel[0] = med, pixel[1] = med, pixel[2] = med; + } +#ifndef MSDFGEN_NO_DIAGONAL_CLASH_DETECTION + clashes.clear(); + for (int y = 0; y < h; ++y) + for (int x = 0; x < w; ++x) { + if ( + (x > 0 && y > 0 && detectClash(output(x, y), output(x-1, y-1), threshold.x+threshold.y)) || + (x < w-1 && y > 0 && detectClash(output(x, y), output(x+1, y-1), threshold.x+threshold.y)) || + (x > 0 && y < h-1 && detectClash(output(x, y), output(x-1, y+1), threshold.x+threshold.y)) || + (x < w-1 && y < h-1 && detectClash(output(x, y), output(x+1, y+1), threshold.x+threshold.y)) + ) + clashes.push_back(std::make_pair(x, y)); + } + for (std::vector<std::pair<int, int> >::const_iterator clash = clashes.begin(); clash != clashes.end(); ++clash) { + float *pixel = output(clash->first, clash->second); + float med = median(pixel[0], pixel[1], pixel[2]); + pixel[0] = med, pixel[1] = med, pixel[2] = med; + } +#endif +} + +void msdfErrorCorrection_legacy(const BitmapRef<float, 3> &output, const Vector2 &threshold) { + msdfErrorCorrectionInner_legacy(output, threshold); +} +void msdfErrorCorrection_legacy(const BitmapRef<float, 4> &output, const Vector2 &threshold) { + msdfErrorCorrectionInner_legacy(output, threshold); +} + +} diff --git a/thirdparty/msdfgen/core/msdf-error-correction.h b/thirdparty/msdfgen/core/msdf-error-correction.h new file mode 100644 index 0000000000..d5384c9329 --- /dev/null +++ b/thirdparty/msdfgen/core/msdf-error-correction.h @@ -0,0 +1,28 @@ + +#pragma once + +#include "Vector2.h" +#include "Projection.h" +#include "Shape.h" +#include "BitmapRef.hpp" +#include "generator-config.h" + +namespace msdfgen { + +/// Predicts potential artifacts caused by the interpolation of the MSDF and corrects them by converting nearby texels to single-channel. +void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig()); +void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig()); + +/// Applies the simplified error correction to all discontiunous distances (INDISCRIMINATE mode). Does not need shape or translation. +void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio); +void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio); + +/// Applies the simplified error correction to edges only (EDGE_ONLY mode). Does not need shape or translation. +void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio); +void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio); + +/// The original version of the error correction algorithm. +void msdfErrorCorrection_legacy(const BitmapRef<float, 3> &output, const Vector2 &threshold); +void msdfErrorCorrection_legacy(const BitmapRef<float, 4> &output, const Vector2 &threshold); + +} diff --git a/thirdparty/msdfgen/core/msdfgen.cpp b/thirdparty/msdfgen/core/msdfgen.cpp new file mode 100644 index 0000000000..0289295f14 --- /dev/null +++ b/thirdparty/msdfgen/core/msdfgen.cpp @@ -0,0 +1,288 @@ + +#include "../msdfgen.h" + +#include <vector> +#include "edge-selectors.h" +#include "contour-combiners.h" +#include "ShapeDistanceFinder.h" + +namespace msdfgen { + +template <typename DistanceType> +class DistancePixelConversion; + +template <> +class DistancePixelConversion<double> { + double invRange; +public: + typedef BitmapRef<float, 1> BitmapRefType; + inline explicit DistancePixelConversion(double range) : invRange(1/range) { } + inline void operator()(float *pixels, double distance) const { + *pixels = float(invRange*distance+.5); + } +}; + +template <> +class DistancePixelConversion<MultiDistance> { + double invRange; +public: + typedef BitmapRef<float, 3> BitmapRefType; + inline explicit DistancePixelConversion(double range) : invRange(1/range) { } + inline void operator()(float *pixels, const MultiDistance &distance) const { + pixels[0] = float(invRange*distance.r+.5); + pixels[1] = float(invRange*distance.g+.5); + pixels[2] = float(invRange*distance.b+.5); + } +}; + +template <> +class DistancePixelConversion<MultiAndTrueDistance> { + double invRange; +public: + typedef BitmapRef<float, 4> BitmapRefType; + inline explicit DistancePixelConversion(double range) : invRange(1/range) { } + inline void operator()(float *pixels, const MultiAndTrueDistance &distance) const { + pixels[0] = float(invRange*distance.r+.5); + pixels[1] = float(invRange*distance.g+.5); + pixels[2] = float(invRange*distance.b+.5); + pixels[3] = float(invRange*distance.a+.5); + } +}; + +template <class ContourCombiner> +void generateDistanceField(const typename DistancePixelConversion<typename ContourCombiner::DistanceType>::BitmapRefType &output, const Shape &shape, const Projection &projection, double range) { + DistancePixelConversion<typename ContourCombiner::DistanceType> distancePixelConversion(range); +#ifdef MSDFGEN_USE_OPENMP + #pragma omp parallel +#endif + { + ShapeDistanceFinder<ContourCombiner> distanceFinder(shape); + bool rightToLeft = false; +#ifdef MSDFGEN_USE_OPENMP + #pragma omp for +#endif + for (int y = 0; y < output.height; ++y) { + int row = shape.inverseYAxis ? output.height-y-1 : y; + for (int col = 0; col < output.width; ++col) { + int x = rightToLeft ? output.width-col-1 : col; + Point2 p = projection.unproject(Point2(x+.5, y+.5)); + typename ContourCombiner::DistanceType distance = distanceFinder.distance(p); + distancePixelConversion(output(x, row), distance); + } + rightToLeft = !rightToLeft; + } + } +} + +void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config) { + if (config.overlapSupport) + generateDistanceField<OverlappingContourCombiner<TrueDistanceSelector> >(output, shape, projection, range); + else + generateDistanceField<SimpleContourCombiner<TrueDistanceSelector> >(output, shape, projection, range); +} + +void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config) { + if (config.overlapSupport) + generateDistanceField<OverlappingContourCombiner<PseudoDistanceSelector> >(output, shape, projection, range); + else + generateDistanceField<SimpleContourCombiner<PseudoDistanceSelector> >(output, shape, projection, range); +} + +void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) { + if (config.overlapSupport) + generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, projection, range); + else + generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, projection, range); + msdfErrorCorrection(output, shape, projection, range, config); +} + +void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) { + if (config.overlapSupport) + generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range); + else + generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range); + msdfErrorCorrection(output, shape, projection, range, config); +} + +// Legacy API + +void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) { + generateSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport)); +} + +void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) { + generatePseudoSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport)); +} + +void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) { + generateMSDF(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(overlapSupport, errorCorrectionConfig)); +} + +void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) { + generateMTSDF(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(overlapSupport, errorCorrectionConfig)); +} + +// Legacy version + +void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) { +#ifdef MSDFGEN_USE_OPENMP + #pragma omp parallel for +#endif + for (int y = 0; y < output.height; ++y) { + int row = shape.inverseYAxis ? output.height-y-1 : y; + for (int x = 0; x < output.width; ++x) { + double dummy; + Point2 p = Vector2(x+.5, y+.5)/scale-translate; + SignedDistance minDistance; + for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + SignedDistance distance = (*edge)->signedDistance(p, dummy); + if (distance < minDistance) + minDistance = distance; + } + *output(x, row) = float(minDistance.distance/range+.5); + } + } +} + +void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) { +#ifdef MSDFGEN_USE_OPENMP + #pragma omp parallel for +#endif + for (int y = 0; y < output.height; ++y) { + int row = shape.inverseYAxis ? output.height-y-1 : y; + for (int x = 0; x < output.width; ++x) { + Point2 p = Vector2(x+.5, y+.5)/scale-translate; + SignedDistance minDistance; + const EdgeHolder *nearEdge = NULL; + double nearParam = 0; + for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + double param; + SignedDistance distance = (*edge)->signedDistance(p, param); + if (distance < minDistance) { + minDistance = distance; + nearEdge = &*edge; + nearParam = param; + } + } + if (nearEdge) + (*nearEdge)->distanceToPseudoDistance(minDistance, p, nearParam); + *output(x, row) = float(minDistance.distance/range+.5); + } + } +} + +void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) { +#ifdef MSDFGEN_USE_OPENMP + #pragma omp parallel for +#endif + for (int y = 0; y < output.height; ++y) { + int row = shape.inverseYAxis ? output.height-y-1 : y; + for (int x = 0; x < output.width; ++x) { + Point2 p = Vector2(x+.5, y+.5)/scale-translate; + + struct { + SignedDistance minDistance; + const EdgeHolder *nearEdge; + double nearParam; + } r, g, b; + r.nearEdge = g.nearEdge = b.nearEdge = NULL; + r.nearParam = g.nearParam = b.nearParam = 0; + + for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + double param; + SignedDistance distance = (*edge)->signedDistance(p, param); + if ((*edge)->color&RED && distance < r.minDistance) { + r.minDistance = distance; + r.nearEdge = &*edge; + r.nearParam = param; + } + if ((*edge)->color&GREEN && distance < g.minDistance) { + g.minDistance = distance; + g.nearEdge = &*edge; + g.nearParam = param; + } + if ((*edge)->color&BLUE && distance < b.minDistance) { + b.minDistance = distance; + b.nearEdge = &*edge; + b.nearParam = param; + } + } + + if (r.nearEdge) + (*r.nearEdge)->distanceToPseudoDistance(r.minDistance, p, r.nearParam); + if (g.nearEdge) + (*g.nearEdge)->distanceToPseudoDistance(g.minDistance, p, g.nearParam); + if (b.nearEdge) + (*b.nearEdge)->distanceToPseudoDistance(b.minDistance, p, b.nearParam); + output(x, row)[0] = float(r.minDistance.distance/range+.5); + output(x, row)[1] = float(g.minDistance.distance/range+.5); + output(x, row)[2] = float(b.minDistance.distance/range+.5); + } + } + + errorCorrectionConfig.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; + msdfErrorCorrection(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(false, errorCorrectionConfig)); +} + +void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) { +#ifdef MSDFGEN_USE_OPENMP + #pragma omp parallel for +#endif + for (int y = 0; y < output.height; ++y) { + int row = shape.inverseYAxis ? output.height-y-1 : y; + for (int x = 0; x < output.width; ++x) { + Point2 p = Vector2(x+.5, y+.5)/scale-translate; + + SignedDistance minDistance; + struct { + SignedDistance minDistance; + const EdgeHolder *nearEdge; + double nearParam; + } r, g, b; + r.nearEdge = g.nearEdge = b.nearEdge = NULL; + r.nearParam = g.nearParam = b.nearParam = 0; + + for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + double param; + SignedDistance distance = (*edge)->signedDistance(p, param); + if (distance < minDistance) + minDistance = distance; + if ((*edge)->color&RED && distance < r.minDistance) { + r.minDistance = distance; + r.nearEdge = &*edge; + r.nearParam = param; + } + if ((*edge)->color&GREEN && distance < g.minDistance) { + g.minDistance = distance; + g.nearEdge = &*edge; + g.nearParam = param; + } + if ((*edge)->color&BLUE && distance < b.minDistance) { + b.minDistance = distance; + b.nearEdge = &*edge; + b.nearParam = param; + } + } + + if (r.nearEdge) + (*r.nearEdge)->distanceToPseudoDistance(r.minDistance, p, r.nearParam); + if (g.nearEdge) + (*g.nearEdge)->distanceToPseudoDistance(g.minDistance, p, g.nearParam); + if (b.nearEdge) + (*b.nearEdge)->distanceToPseudoDistance(b.minDistance, p, b.nearParam); + output(x, row)[0] = float(r.minDistance.distance/range+.5); + output(x, row)[1] = float(g.minDistance.distance/range+.5); + output(x, row)[2] = float(b.minDistance.distance/range+.5); + output(x, row)[3] = float(minDistance.distance/range+.5); + } + } + + errorCorrectionConfig.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; + msdfErrorCorrection(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(false, errorCorrectionConfig)); +} + +} diff --git a/thirdparty/msdfgen/core/pixel-conversion.hpp b/thirdparty/msdfgen/core/pixel-conversion.hpp new file mode 100644 index 0000000000..7e9b6d08f0 --- /dev/null +++ b/thirdparty/msdfgen/core/pixel-conversion.hpp @@ -0,0 +1,18 @@ + +#pragma once + +#include "arithmetics.hpp" + +namespace msdfgen { + +typedef unsigned char byte; + +inline byte pixelFloatToByte(float x) { + return byte(clamp(256.f*x, 255.f)); +} + +inline float pixelByteToFloat(byte x) { + return 1.f/255.f*float(x); +} + +} diff --git a/thirdparty/msdfgen/core/rasterization.cpp b/thirdparty/msdfgen/core/rasterization.cpp new file mode 100644 index 0000000000..9aa695a8c1 --- /dev/null +++ b/thirdparty/msdfgen/core/rasterization.cpp @@ -0,0 +1,115 @@ + +#include "rasterization.h" + +#include <vector> +#include "arithmetics.hpp" + +namespace msdfgen { + +void rasterize(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, FillRule fillRule) { + Scanline scanline; + for (int y = 0; y < output.height; ++y) { + int row = shape.inverseYAxis ? output.height-y-1 : y; + shape.scanline(scanline, projection.unprojectY(y+.5)); + for (int x = 0; x < output.width; ++x) + *output(x, row) = (float) scanline.filled(projection.unprojectX(x+.5), fillRule); + } +} + +void distanceSignCorrection(const BitmapRef<float, 1> &sdf, const Shape &shape, const Projection &projection, FillRule fillRule) { + Scanline scanline; + for (int y = 0; y < sdf.height; ++y) { + int row = shape.inverseYAxis ? sdf.height-y-1 : y; + shape.scanline(scanline, projection.unprojectY(y+.5)); + for (int x = 0; x < sdf.width; ++x) { + bool fill = scanline.filled(projection.unprojectX(x+.5), fillRule); + float &sd = *sdf(x, row); + if ((sd > .5f) != fill) + sd = 1.f-sd; + } + } +} + +template <int N> +static void multiDistanceSignCorrection(const BitmapRef<float, N> &sdf, const Shape &shape, const Projection &projection, FillRule fillRule) { + int w = sdf.width, h = sdf.height; + if (!(w*h)) + return; + Scanline scanline; + bool ambiguous = false; + std::vector<char> matchMap; + matchMap.resize(w*h); + char *match = &matchMap[0]; + for (int y = 0; y < h; ++y) { + int row = shape.inverseYAxis ? h-y-1 : y; + shape.scanline(scanline, projection.unprojectY(y+.5)); + for (int x = 0; x < w; ++x) { + bool fill = scanline.filled(projection.unprojectX(x+.5), fillRule); + float *msd = sdf(x, row); + float sd = median(msd[0], msd[1], msd[2]); + if (sd == .5f) + ambiguous = true; + else if ((sd > .5f) != fill) { + msd[0] = 1.f-msd[0]; + msd[1] = 1.f-msd[1]; + msd[2] = 1.f-msd[2]; + *match = -1; + } else + *match = 1; + if (N >= 4 && (msd[3] > .5f) != fill) + msd[3] = 1.f-msd[3]; + ++match; + } + } + // This step is necessary to avoid artifacts when whole shape is inverted + if (ambiguous) { + match = &matchMap[0]; + for (int y = 0; y < h; ++y) { + int row = shape.inverseYAxis ? h-y-1 : y; + for (int x = 0; x < w; ++x) { + if (!*match) { + int neighborMatch = 0; + if (x > 0) neighborMatch += *(match-1); + if (x < w-1) neighborMatch += *(match+1); + if (y > 0) neighborMatch += *(match-w); + if (y < h-1) neighborMatch += *(match+w); + if (neighborMatch < 0) { + float *msd = sdf(x, row); + msd[0] = 1.f-msd[0]; + msd[1] = 1.f-msd[1]; + msd[2] = 1.f-msd[2]; + } + } + ++match; + } + } + } +} + +void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, FillRule fillRule) { + multiDistanceSignCorrection(sdf, shape, projection, fillRule); +} + +void distanceSignCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, FillRule fillRule) { + multiDistanceSignCorrection(sdf, shape, projection, fillRule); +} + +// Legacy API + +void rasterize(const BitmapRef<float, 1> &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { + rasterize(output, shape, Projection(scale, translate), fillRule); +} + +void distanceSignCorrection(const BitmapRef<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { + distanceSignCorrection(sdf, shape, Projection(scale, translate), fillRule); +} + +void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { + distanceSignCorrection(sdf, shape, Projection(scale, translate), fillRule); +} + +void distanceSignCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { + distanceSignCorrection(sdf, shape, Projection(scale, translate), fillRule); +} + +} diff --git a/thirdparty/msdfgen/core/rasterization.h b/thirdparty/msdfgen/core/rasterization.h new file mode 100644 index 0000000000..82d0c73d95 --- /dev/null +++ b/thirdparty/msdfgen/core/rasterization.h @@ -0,0 +1,25 @@ + +#pragma once + +#include "Vector2.h" +#include "Shape.h" +#include "Projection.h" +#include "Scanline.h" +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// Rasterizes the shape into a monochrome bitmap. +void rasterize(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO); +/// Fixes the sign of the input signed distance field, so that it matches the shape's rasterized fill. +void distanceSignCorrection(const BitmapRef<float, 1> &sdf, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO); +void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO); +void distanceSignCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO); + +// Old version of the function API's kept for backwards compatibility +void rasterize(const BitmapRef<float, 1> &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); +void distanceSignCorrection(const BitmapRef<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); +void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); +void distanceSignCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); + +} diff --git a/thirdparty/msdfgen/core/render-sdf.cpp b/thirdparty/msdfgen/core/render-sdf.cpp new file mode 100644 index 0000000000..e282285e59 --- /dev/null +++ b/thirdparty/msdfgen/core/render-sdf.cpp @@ -0,0 +1,108 @@ + +#include "render-sdf.h" + +#include "arithmetics.hpp" +#include "pixel-conversion.hpp" +#include "bitmap-interpolation.hpp" + +namespace msdfgen { + +static float distVal(float dist, double pxRange, float midValue) { + if (!pxRange) + return (float) (dist > midValue); + return (float) clamp((dist-midValue)*pxRange+.5); +} + +void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, double pxRange, float midValue) { + Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height); + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd; + interpolate(&sd, sdf, scale*Point2(x+.5, y+.5)); + *output(x, y) = distVal(sd, pxRange, midValue); + } +} + +void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, double pxRange, float midValue) { + Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height); + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd; + interpolate(&sd, sdf, scale*Point2(x+.5, y+.5)); + float v = distVal(sd, pxRange, midValue); + output(x, y)[0] = v; + output(x, y)[1] = v; + output(x, y)[2] = v; + } +} + +void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, double pxRange, float midValue) { + Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height); + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd[3]; + interpolate(sd, sdf, scale*Point2(x+.5, y+.5)); + *output(x, y) = distVal(median(sd[0], sd[1], sd[2]), pxRange, midValue); + } +} + +void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, double pxRange, float midValue) { + Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height); + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd[3]; + interpolate(sd, sdf, scale*Point2(x+.5, y+.5)); + output(x, y)[0] = distVal(sd[0], pxRange, midValue); + output(x, y)[1] = distVal(sd[1], pxRange, midValue); + output(x, y)[2] = distVal(sd[2], pxRange, midValue); + } +} + +void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, double pxRange, float midValue) { + Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height); + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd[4]; + interpolate(sd, sdf, scale*Point2(x+.5, y+.5)); + *output(x, y) = distVal(median(sd[0], sd[1], sd[2]), pxRange, midValue); + } +} + +void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &sdf, double pxRange, float midValue) { + Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height); + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd[4]; + interpolate(sd, sdf, scale*Point2(x+.5, y+.5)); + output(x, y)[0] = distVal(sd[0], pxRange, midValue); + output(x, y)[1] = distVal(sd[1], pxRange, midValue); + output(x, y)[2] = distVal(sd[2], pxRange, midValue); + output(x, y)[3] = distVal(sd[3], pxRange, midValue); + } +} + +void simulate8bit(const BitmapRef<float, 1> &bitmap) { + const float *end = bitmap.pixels+1*bitmap.width*bitmap.height; + for (float *p = bitmap.pixels; p < end; ++p) + *p = pixelByteToFloat(pixelFloatToByte(*p)); +} + +void simulate8bit(const BitmapRef<float, 3> &bitmap) { + const float *end = bitmap.pixels+3*bitmap.width*bitmap.height; + for (float *p = bitmap.pixels; p < end; ++p) + *p = pixelByteToFloat(pixelFloatToByte(*p)); +} + +void simulate8bit(const BitmapRef<float, 4> &bitmap) { + const float *end = bitmap.pixels+4*bitmap.width*bitmap.height; + for (float *p = bitmap.pixels; p < end; ++p) + *p = pixelByteToFloat(pixelFloatToByte(*p)); +} + +} diff --git a/thirdparty/msdfgen/core/render-sdf.h b/thirdparty/msdfgen/core/render-sdf.h new file mode 100644 index 0000000000..7f2d270b67 --- /dev/null +++ b/thirdparty/msdfgen/core/render-sdf.h @@ -0,0 +1,22 @@ + +#pragma once + +#include "Vector2.h" +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// Reconstructs the shape's appearance into output from the distance field sdf. +void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, double pxRange = 0, float midValue = .5f); +void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, double pxRange = 0, float midValue = .5f); +void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, double pxRange = 0, float midValue = .5f); +void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, double pxRange = 0, float midValue = .5f); +void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, double pxRange = 0, float midValue = .5f); +void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &sdf, double pxRange = 0, float midValue = .5f); + +/// Snaps the values of the floating-point bitmaps into one of the 256 values representable in a standard 8-bit bitmap. +void simulate8bit(const BitmapRef<float, 1> &bitmap); +void simulate8bit(const BitmapRef<float, 3> &bitmap); +void simulate8bit(const BitmapRef<float, 4> &bitmap); + +} diff --git a/thirdparty/msdfgen/core/save-bmp.cpp b/thirdparty/msdfgen/core/save-bmp.cpp new file mode 100644 index 0000000000..f761ee4f88 --- /dev/null +++ b/thirdparty/msdfgen/core/save-bmp.cpp @@ -0,0 +1,169 @@ + +#define _CRT_SECURE_NO_WARNINGS + +#include "save-bmp.h" + +#include <cstdio> + +#ifdef MSDFGEN_USE_CPP11 + #include <cstdint> +#else + typedef int int32_t; + typedef unsigned uint32_t; + typedef unsigned short uint16_t; + typedef unsigned char uint8_t; +#endif + +#include "pixel-conversion.hpp" + +namespace msdfgen { + +template <typename T> +static bool writeValue(FILE *file, T value) { + #ifdef __BIG_ENDIAN__ + T reverse = 0; + for (int i = 0; i < sizeof(T); ++i) { + reverse <<= 8; + reverse |= value&T(0xff); + value >>= 8; + } + return fwrite(&reverse, sizeof(T), 1, file) == 1; + #else + return fwrite(&value, sizeof(T), 1, file) == 1; + #endif +} + +static bool writeBmpHeader(FILE *file, int width, int height, int &paddedWidth) { + paddedWidth = (3*width+3)&~3; + const uint32_t bitmapStart = 54; + const uint32_t bitmapSize = paddedWidth*height; + const uint32_t fileSize = bitmapStart+bitmapSize; + + writeValue<uint16_t>(file, 0x4d42u); + writeValue<uint32_t>(file, fileSize); + writeValue<uint16_t>(file, 0); + writeValue<uint16_t>(file, 0); + writeValue<uint32_t>(file, bitmapStart); + + writeValue<uint32_t>(file, 40); + writeValue<int32_t>(file, width); + writeValue<int32_t>(file, height); + writeValue<uint16_t>(file, 1); + writeValue<uint16_t>(file, 24); + writeValue<uint32_t>(file, 0); + writeValue<uint32_t>(file, bitmapSize); + writeValue<uint32_t>(file, 2835); + writeValue<uint32_t>(file, 2835); + writeValue<uint32_t>(file, 0); + writeValue<uint32_t>(file, 0); + + return true; +} + +bool saveBmp(const BitmapConstRef<byte, 1> &bitmap, const char *filename) { + FILE *file = fopen(filename, "wb"); + if (!file) + return false; + + int paddedWidth; + writeBmpHeader(file, bitmap.width, bitmap.height, paddedWidth); + + const uint8_t padding[4] = { }; + int padLength = paddedWidth-3*bitmap.width; + for (int y = 0; y < bitmap.height; ++y) { + for (int x = 0; x < bitmap.width; ++x) { + uint8_t px = (uint8_t) *bitmap(x, y); + fwrite(&px, sizeof(uint8_t), 1, file); + fwrite(&px, sizeof(uint8_t), 1, file); + fwrite(&px, sizeof(uint8_t), 1, file); + } + fwrite(padding, 1, padLength, file); + } + + return !fclose(file); +} + +bool saveBmp(const BitmapConstRef<byte, 3> &bitmap, const char *filename) { + FILE *file = fopen(filename, "wb"); + if (!file) + return false; + + int paddedWidth; + writeBmpHeader(file, bitmap.width, bitmap.height, paddedWidth); + + const uint8_t padding[4] = { }; + int padLength = paddedWidth-3*bitmap.width; + for (int y = 0; y < bitmap.height; ++y) { + for (int x = 0; x < bitmap.width; ++x) { + uint8_t bgr[3] = { + (uint8_t) bitmap(x, y)[2], + (uint8_t) bitmap(x, y)[1], + (uint8_t) bitmap(x, y)[0] + }; + fwrite(bgr, sizeof(uint8_t), 3, file); + } + fwrite(padding, 1, padLength, file); + } + + return !fclose(file); +} + +bool saveBmp(const BitmapConstRef<byte, 4> &bitmap, const char *filename) { + // RGBA not supported by the BMP format + return false; +} + +bool saveBmp(const BitmapConstRef<float, 1> &bitmap, const char *filename) { + FILE *file = fopen(filename, "wb"); + if (!file) + return false; + + int paddedWidth; + writeBmpHeader(file, bitmap.width, bitmap.height, paddedWidth); + + const uint8_t padding[4] = { }; + int padLength = paddedWidth-3*bitmap.width; + for (int y = 0; y < bitmap.height; ++y) { + for (int x = 0; x < bitmap.width; ++x) { + uint8_t px = (uint8_t) pixelFloatToByte(*bitmap(x, y)); + fwrite(&px, sizeof(uint8_t), 1, file); + fwrite(&px, sizeof(uint8_t), 1, file); + fwrite(&px, sizeof(uint8_t), 1, file); + } + fwrite(padding, 1, padLength, file); + } + + return !fclose(file); +} + +bool saveBmp(const BitmapConstRef<float, 3> &bitmap, const char *filename) { + FILE *file = fopen(filename, "wb"); + if (!file) + return false; + + int paddedWidth; + writeBmpHeader(file, bitmap.width, bitmap.height, paddedWidth); + + const uint8_t padding[4] = { }; + int padLength = paddedWidth-3*bitmap.width; + for (int y = 0; y < bitmap.height; ++y) { + for (int x = 0; x < bitmap.width; ++x) { + uint8_t bgr[3] = { + (uint8_t) pixelFloatToByte(bitmap(x, y)[2]), + (uint8_t) pixelFloatToByte(bitmap(x, y)[1]), + (uint8_t) pixelFloatToByte(bitmap(x, y)[0]) + }; + fwrite(bgr, sizeof(uint8_t), 3, file); + } + fwrite(padding, 1, padLength, file); + } + + return !fclose(file); +} + +bool saveBmp(const BitmapConstRef<float, 4> &bitmap, const char *filename) { + // RGBA not supported by the BMP format + return false; +} + +} diff --git a/thirdparty/msdfgen/core/save-bmp.h b/thirdparty/msdfgen/core/save-bmp.h new file mode 100644 index 0000000000..98f852921f --- /dev/null +++ b/thirdparty/msdfgen/core/save-bmp.h @@ -0,0 +1,16 @@ + +#pragma once + +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// Saves the bitmap as a BMP file. +bool saveBmp(const BitmapConstRef<byte, 1> &bitmap, const char *filename); +bool saveBmp(const BitmapConstRef<byte, 3> &bitmap, const char *filename); +bool saveBmp(const BitmapConstRef<byte, 4> &bitmap, const char *filename); +bool saveBmp(const BitmapConstRef<float, 1> &bitmap, const char *filename); +bool saveBmp(const BitmapConstRef<float, 3> &bitmap, const char *filename); +bool saveBmp(const BitmapConstRef<float, 4> &bitmap, const char *filename); + +} diff --git a/thirdparty/msdfgen/core/save-tiff.cpp b/thirdparty/msdfgen/core/save-tiff.cpp new file mode 100644 index 0000000000..71405e00e5 --- /dev/null +++ b/thirdparty/msdfgen/core/save-tiff.cpp @@ -0,0 +1,190 @@ + +#define _CRT_SECURE_NO_WARNINGS + +#include "save-tiff.h" + +#include <cstdio> + +#ifdef MSDFGEN_USE_CPP11 + #include <cstdint> +#else + typedef int int32_t; + typedef unsigned uint32_t; + typedef unsigned short uint16_t; + typedef unsigned char uint8_t; +#endif + +namespace msdfgen { + +template <typename T> +static bool writeValue(FILE *file, T value) { + return fwrite(&value, sizeof(T), 1, file) == 1; +} +template <typename T> +static void writeValueRepeated(FILE *file, T value, int times) { + for (int i = 0; i < times; ++i) + writeValue(file, value); +} + +static bool writeTiffHeader(FILE *file, int width, int height, int channels) { + #ifdef __BIG_ENDIAN__ + writeValue<uint16_t>(file, 0x4d4du); + #else + writeValue<uint16_t>(file, 0x4949u); + #endif + writeValue<uint16_t>(file, 42); + writeValue<uint32_t>(file, 0x0008u); // Offset of first IFD + // Offset = 0x0008 + + writeValue<uint16_t>(file, 15); // Number of IFD entries + + // ImageWidth + writeValue<uint16_t>(file, 0x0100u); + writeValue<uint16_t>(file, 0x0004u); + writeValue<uint32_t>(file, 1); + writeValue<int32_t>(file, width); + // ImageLength + writeValue<uint16_t>(file, 0x0101u); + writeValue<uint16_t>(file, 0x0004u); + writeValue<uint32_t>(file, 1); + writeValue<int32_t>(file, height); + // BitsPerSample + writeValue<uint16_t>(file, 0x0102u); + writeValue<uint16_t>(file, 0x0003u); + writeValue<uint32_t>(file, channels); + if (channels > 1) + writeValue<uint32_t>(file, 0x00c2u); // Offset of 32, 32, ... + else { + writeValue<uint16_t>(file, 32); + writeValue<uint16_t>(file, 0); + } + // Compression + writeValue<uint16_t>(file, 0x0103u); + writeValue<uint16_t>(file, 0x0003u); + writeValue<uint32_t>(file, 1); + writeValue<uint16_t>(file, 1); + writeValue<uint16_t>(file, 0); + // PhotometricInterpretation + writeValue<uint16_t>(file, 0x0106u); + writeValue<uint16_t>(file, 0x0003u); + writeValue<uint32_t>(file, 1); + writeValue<uint16_t>(file, channels >= 3 ? 2 : 1); + writeValue<uint16_t>(file, 0); + // StripOffsets + writeValue<uint16_t>(file, 0x0111u); + writeValue<uint16_t>(file, 0x0004u); + writeValue<uint32_t>(file, 1); + writeValue<uint32_t>(file, 0x00d2u+(channels > 1)*channels*12); // Offset of pixel data + // SamplesPerPixel + writeValue<uint16_t>(file, 0x0115u); + writeValue<uint16_t>(file, 0x0003u); + writeValue<uint32_t>(file, 1); + writeValue<uint16_t>(file, channels); + writeValue<uint16_t>(file, 0); + // RowsPerStrip + writeValue<uint16_t>(file, 0x0116u); + writeValue<uint16_t>(file, 0x0004u); + writeValue<uint32_t>(file, 1); + writeValue<int32_t>(file, height); + // StripByteCounts + writeValue<uint16_t>(file, 0x0117u); + writeValue<uint16_t>(file, 0x0004u); + writeValue<uint32_t>(file, 1); + writeValue<int32_t>(file, sizeof(float)*channels*width*height); + // XResolution + writeValue<uint16_t>(file, 0x011au); + writeValue<uint16_t>(file, 0x0005u); + writeValue<uint32_t>(file, 1); + writeValue<uint32_t>(file, 0x00c2u+(channels > 1)*channels*2); // Offset of 300, 1 + // YResolution + writeValue<uint16_t>(file, 0x011bu); + writeValue<uint16_t>(file, 0x0005u); + writeValue<uint32_t>(file, 1); + writeValue<uint32_t>(file, 0x00cau+(channels > 1)*channels*2); // Offset of 300, 1 + // ResolutionUnit + writeValue<uint16_t>(file, 0x0128u); + writeValue<uint16_t>(file, 0x0003u); + writeValue<uint32_t>(file, 1); + writeValue<uint16_t>(file, 2); + writeValue<uint16_t>(file, 0); + // SampleFormat + writeValue<uint16_t>(file, 0x0153u); + writeValue<uint16_t>(file, 0x0003u); + writeValue<uint32_t>(file, channels); + if (channels > 1) + writeValue<uint32_t>(file, 0x00d2u+channels*2); // Offset of 3, 3, ... + else { + writeValue<uint16_t>(file, 3); + writeValue<uint16_t>(file, 0); + } + // SMinSampleValue + writeValue<uint16_t>(file, 0x0154u); + writeValue<uint16_t>(file, 0x000bu); + writeValue<uint32_t>(file, channels); + if (channels > 1) + writeValue<uint32_t>(file, 0x00d2u+channels*4); // Offset of 0.f, 0.f, ... + else + writeValue<float>(file, 0.f); + // SMaxSampleValue + writeValue<uint16_t>(file, 0x0155u); + writeValue<uint16_t>(file, 0x000bu); + writeValue<uint32_t>(file, channels); + if (channels > 1) + writeValue<uint32_t>(file, 0x00d2u+channels*8); // Offset of 1.f, 1.f, ... + else + writeValue<float>(file, 1.f); + // Offset = 0x00be + + writeValue<uint32_t>(file, 0); + + if (channels > 1) { + // 0x00c2 BitsPerSample data + writeValueRepeated<uint16_t>(file, 32, channels); + // 0x00c2 + 2*N XResolution data + writeValue<uint32_t>(file, 300); + writeValue<uint32_t>(file, 1); + // 0x00ca + 2*N YResolution data + writeValue<uint32_t>(file, 300); + writeValue<uint32_t>(file, 1); + // 0x00d2 + 2*N SampleFormat data + writeValueRepeated<uint16_t>(file, 3, channels); + // 0x00d2 + 4*N SMinSampleValue data + writeValueRepeated<float>(file, 0.f, channels); + // 0x00d2 + 8*N SMaxSampleValue data + writeValueRepeated<float>(file, 1.f, channels); + // Offset = 0x00d2 + 12*N + } else { + // 0x00c2 XResolution data + writeValue<uint32_t>(file, 300); + writeValue<uint32_t>(file, 1); + // 0x00ca YResolution data + writeValue<uint32_t>(file, 300); + writeValue<uint32_t>(file, 1); + // Offset = 0x00d2 + } + + return true; +} + +template <int N> +bool saveTiffFloat(const BitmapConstRef<float, N> &bitmap, const char *filename) { + FILE *file = fopen(filename, "wb"); + if (!file) + return false; + writeTiffHeader(file, bitmap.width, bitmap.height, N); + for (int y = bitmap.height-1; y >= 0; --y) + fwrite(bitmap(0, y), sizeof(float), N*bitmap.width, file); + return !fclose(file); +} + +bool saveTiff(const BitmapConstRef<float, 1> &bitmap, const char *filename) { + return saveTiffFloat(bitmap, filename); +} +bool saveTiff(const BitmapConstRef<float, 3> &bitmap, const char *filename) { + return saveTiffFloat(bitmap, filename); +} +bool saveTiff(const BitmapConstRef<float, 4> &bitmap, const char *filename) { + return saveTiffFloat(bitmap, filename); +} + +} diff --git a/thirdparty/msdfgen/core/save-tiff.h b/thirdparty/msdfgen/core/save-tiff.h new file mode 100644 index 0000000000..072cd71d50 --- /dev/null +++ b/thirdparty/msdfgen/core/save-tiff.h @@ -0,0 +1,13 @@ + +#pragma once + +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// Saves the bitmap as an uncompressed floating-point TIFF file. +bool saveTiff(const BitmapConstRef<float, 1> &bitmap, const char *filename); +bool saveTiff(const BitmapConstRef<float, 3> &bitmap, const char *filename); +bool saveTiff(const BitmapConstRef<float, 4> &bitmap, const char *filename); + +} diff --git a/thirdparty/msdfgen/core/sdf-error-estimation.cpp b/thirdparty/msdfgen/core/sdf-error-estimation.cpp new file mode 100644 index 0000000000..7c00c449a9 --- /dev/null +++ b/thirdparty/msdfgen/core/sdf-error-estimation.cpp @@ -0,0 +1,192 @@ + +#include "sdf-error-estimation.h" + +#include <cmath> +#include "arithmetics.hpp" + +namespace msdfgen { + +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 1> &sdf, const Projection &projection, double y, bool inverseYAxis) { + if (!(sdf.width > 0 && sdf.height > 0)) + return line.setIntersections(std::vector<Scanline::Intersection>()); + double pixelY = clamp(projection.projectY(y)-.5, double(sdf.height-1)); + if (inverseYAxis) + pixelY = sdf.height-1-pixelY; + int b = (int) floor(pixelY); + int t = b+1; + double bt = pixelY-b; + if (t >= sdf.height) { + b = sdf.height-1; + t = sdf.height-1; + bt = 1; + } + bool inside = false; + std::vector<Scanline::Intersection> intersections; + float lv, rv = mix(*sdf(0, b), *sdf(0, t), bt); + if ((inside = rv > .5f)) { + Scanline::Intersection intersection = { -1e240, 1 }; + intersections.push_back(intersection); + } + for (int l = 0, r = 1; r < sdf.width; ++l, ++r) { + lv = rv; + rv = mix(*sdf(r, b), *sdf(r, t), bt); + if (lv != rv) { + double lr = double(.5f-lv)/double(rv-lv); + if (lr >= 0 && lr <= 1) { + Scanline::Intersection intersection = { projection.unprojectX(l+lr+.5), sign(rv-lv) }; + intersections.push_back(intersection); + } + } + } +#ifdef MSDFGEN_USE_CPP11 + line.setIntersections((std::vector<Scanline::Intersection> &&) intersections); +#else + line.setIntersections(intersections); +#endif +} + +template <int N> +void scanlineMSDF(Scanline &line, const BitmapConstRef<float, N> &sdf, const Projection &projection, double y, bool inverseYAxis) { + if (!(sdf.width > 0 && sdf.height > 0)) + return line.setIntersections(std::vector<Scanline::Intersection>()); + double pixelY = clamp(projection.projectY(y)-.5, double(sdf.height-1)); + if (inverseYAxis) + pixelY = sdf.height-1-pixelY; + int b = (int) floor(pixelY); + int t = b+1; + double bt = pixelY-b; + if (t >= sdf.height) { + b = sdf.height-1; + t = sdf.height-1; + bt = 1; + } + bool inside = false; + std::vector<Scanline::Intersection> intersections; + float lv[3], rv[3]; + rv[0] = mix(sdf(0, b)[0], sdf(0, t)[0], bt); + rv[1] = mix(sdf(0, b)[1], sdf(0, t)[1], bt); + rv[2] = mix(sdf(0, b)[2], sdf(0, t)[2], bt); + if ((inside = median(rv[0], rv[1], rv[2]) > .5f)) { + Scanline::Intersection intersection = { -1e240, 1 }; + intersections.push_back(intersection); + } + for (int l = 0, r = 1; r < sdf.width; ++l, ++r) { + lv[0] = rv[0], lv[1] = rv[1], lv[2] = rv[2]; + rv[0] = mix(sdf(r, b)[0], sdf(r, t)[0], bt); + rv[1] = mix(sdf(r, b)[1], sdf(r, t)[1], bt); + rv[2] = mix(sdf(r, b)[2], sdf(r, t)[2], bt); + Scanline::Intersection newIntersections[4]; + int newIntersectionCount = 0; + for (int i = 0; i < 3; ++i) { + if (lv[i] != rv[i]) { + double lr = double(.5f-lv[i])/double(rv[i]-lv[i]); + if (lr >= 0 && lr <= 1) { + float v[3] = { + mix(lv[0], rv[0], lr), + mix(lv[1], rv[1], lr), + mix(lv[2], rv[2], lr) + }; + if (median(v[0], v[1], v[2]) == v[i]) { + newIntersections[newIntersectionCount].x = projection.unprojectX(l+lr+.5); + newIntersections[newIntersectionCount].direction = sign(rv[i]-lv[i]); + ++newIntersectionCount; + } + } + } + } + // Sort new intersections + if (newIntersectionCount >= 2) { + if (newIntersections[0].x > newIntersections[1].x) + newIntersections[3] = newIntersections[0], newIntersections[0] = newIntersections[1], newIntersections[1] = newIntersections[3]; + if (newIntersectionCount >= 3 && newIntersections[1].x > newIntersections[2].x) { + newIntersections[3] = newIntersections[1], newIntersections[1] = newIntersections[2], newIntersections[2] = newIntersections[3]; + if (newIntersections[0].x > newIntersections[1].x) + newIntersections[3] = newIntersections[0], newIntersections[0] = newIntersections[1], newIntersections[1] = newIntersections[3]; + } + } + for (int i = 0; i < newIntersectionCount; ++i) { + if ((newIntersections[i].direction > 0) == !inside) { + intersections.push_back(newIntersections[i]); + inside = !inside; + } + } + // Consistency check + float rvScalar = median(rv[0], rv[1], rv[2]); + if ((rvScalar > .5f) != inside && rvScalar != .5f && !intersections.empty()) { + intersections.pop_back(); + inside = !inside; + } + } +#ifdef MSDFGEN_USE_CPP11 + line.setIntersections((std::vector<Scanline::Intersection> &&) intersections); +#else + line.setIntersections(intersections); +#endif +} + +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Projection &projection, double y, bool inverseYAxis) { + scanlineMSDF(line, sdf, projection, y, inverseYAxis); +} +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 4> &sdf, const Projection &projection, double y, bool inverseYAxis) { + scanlineMSDF(line, sdf, projection, y, inverseYAxis); +} + +template <int N> +double estimateSDFErrorInner(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Projection &projection, int scanlinesPerRow, FillRule fillRule) { + if (sdf.width <= 1 || sdf.height <= 1 || scanlinesPerRow < 1) + return 0; + double subRowSize = 1./scanlinesPerRow; + double xFrom = projection.unprojectX(.5); + double xTo = projection.unprojectX(sdf.width-.5); + double overlapFactor = 1/(xTo-xFrom); + double error = 0; + Scanline refScanline, sdfScanline; + for (int row = 0; row < sdf.height-1; ++row) { + for (int subRow = 0; subRow < scanlinesPerRow; ++subRow) { + double bt = (subRow+.5)*subRowSize; + double y = projection.unprojectY(row+bt+.5); + shape.scanline(refScanline, y); + scanlineSDF(sdfScanline, sdf, projection, y, shape.inverseYAxis); + error += 1-overlapFactor*Scanline::overlap(refScanline, sdfScanline, xFrom, xTo, fillRule); + } + } + return error/((sdf.height-1)*scanlinesPerRow); +} + +double estimateSDFError(const BitmapConstRef<float, 1> &sdf, const Shape &shape, const Projection &projection, int scanlinesPerRow, FillRule fillRule) { + return estimateSDFErrorInner(sdf, shape, projection, scanlinesPerRow, fillRule); +} +double estimateSDFError(const BitmapConstRef<float, 3> &sdf, const Shape &shape, const Projection &projection, int scanlinesPerRow, FillRule fillRule) { + return estimateSDFErrorInner(sdf, shape, projection, scanlinesPerRow, fillRule); +} +double estimateSDFError(const BitmapConstRef<float, 4> &sdf, const Shape &shape, const Projection &projection, int scanlinesPerRow, FillRule fillRule) { + return estimateSDFErrorInner(sdf, shape, projection, scanlinesPerRow, fillRule); +} + +// Legacy API + +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 1> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) { + scanlineSDF(line, sdf, Projection(scale, translate), y, inverseYAxis); +} + +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) { + scanlineSDF(line, sdf, Projection(scale, translate), y, inverseYAxis); +} + +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 4> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) { + scanlineSDF(line, sdf, Projection(scale, translate), y, inverseYAxis); +} + +double estimateSDFError(const BitmapConstRef<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) { + return estimateSDFError(sdf, shape, Projection(scale, translate), scanlinesPerRow, fillRule); +} + +double estimateSDFError(const BitmapConstRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) { + return estimateSDFError(sdf, shape, Projection(scale, translate), scanlinesPerRow, fillRule); +} + +double estimateSDFError(const BitmapConstRef<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) { + return estimateSDFError(sdf, shape, Projection(scale, translate), scanlinesPerRow, fillRule); +} + +} diff --git a/thirdparty/msdfgen/core/sdf-error-estimation.h b/thirdparty/msdfgen/core/sdf-error-estimation.h new file mode 100644 index 0000000000..d2fd40d2b8 --- /dev/null +++ b/thirdparty/msdfgen/core/sdf-error-estimation.h @@ -0,0 +1,30 @@ + +#pragma once + +#include "Vector2.h" +#include "Shape.h" +#include "Projection.h" +#include "Scanline.h" +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// Analytically constructs a scanline at y evaluating fill by linear interpolation of the SDF. +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 1> &sdf, const Projection &projection, double y, bool inverseYAxis = false); +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Projection &projection, double y, bool inverseYAxis = false); +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 4> &sdf, const Projection &projection, double y, bool inverseYAxis = false); + +/// Estimates the portion of the area that will be filled incorrectly when rendering using the SDF. +double estimateSDFError(const BitmapConstRef<float, 1> &sdf, const Shape &shape, const Projection &projection, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO); +double estimateSDFError(const BitmapConstRef<float, 3> &sdf, const Shape &shape, const Projection &projection, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO); +double estimateSDFError(const BitmapConstRef<float, 4> &sdf, const Shape &shape, const Projection &projection, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO); + +// Old version of the function API's kept for backwards compatibility +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 1> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y); +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y); +void scanlineSDF(Scanline &line, const BitmapConstRef<float, 4> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y); +double estimateSDFError(const BitmapConstRef<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO); +double estimateSDFError(const BitmapConstRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO); +double estimateSDFError(const BitmapConstRef<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO); + +} diff --git a/thirdparty/msdfgen/core/shape-description.cpp b/thirdparty/msdfgen/core/shape-description.cpp new file mode 100644 index 0000000000..a096fa2541 --- /dev/null +++ b/thirdparty/msdfgen/core/shape-description.cpp @@ -0,0 +1,284 @@ + +#define _CRT_SECURE_NO_WARNINGS +#include "shape-description.h" + +namespace msdfgen { + +int readCharF(FILE *input) { + int c = '\0'; + do { + c = fgetc(input); + } while (c == ' ' || c == '\t' || c == '\r' || c == '\n'); + return c; +} + +int readCharS(const char **input) { + int c = '\0'; + do { + c = *(*input)++; + } while (c == ' ' || c == '\t' || c == '\r' || c == '\n'); + if (!c) { + --c; + return EOF; + } + return c; +} + +int readCoordF(FILE *input, Point2 &coord) { + return fscanf(input, "%lf,%lf", &coord.x, &coord.y); +} + +int readCoordS(const char **input, Point2 &coord) { + int read = 0; + int result = sscanf(*input, "%lf,%lf%n", &coord.x, &coord.y, &read); + *input += read; + return result; +} + +static bool writeCoord(FILE *output, Point2 coord) { + fprintf(output, "%.12g, %.12g", coord.x, coord.y); + return true; +} + +template <typename T, int (*readChar)(T *), int (*readCoord)(T *, Point2 &)> +static int readControlPoints(T *input, Point2 *output) { + int result = readCoord(input, output[0]); + if (result == 2) { + switch (readChar(input)) { + case ')': + return 1; + case ';': + break; + default: + return -1; + } + result = readCoord(input, output[1]); + if (result == 2 && readChar(input) == ')') + return 2; + } else if (result != 1 && readChar(input) == ')') + return 0; + return -1; +} + +template <typename T, int (*readChar)(T *), int (*readCoord)(T *, Point2 &)> +static bool readContour(T *input, Contour &output, const Point2 *first, int terminator, bool &colorsSpecified) { + Point2 p[4], start; + if (first) + p[0] = *first; + else { + int result = readCoord(input, p[0]); + if (result != 2) + return result != 1 && readChar(input) == terminator; + } + start = p[0]; + int c = '\0'; + while ((c = readChar(input)) != terminator) { + if (c != ';') + return false; + EdgeColor color = WHITE; + int result = readCoord(input, p[1]); + if (result == 2) { + output.addEdge(EdgeHolder(p[0], p[1], color)); + p[0] = p[1]; + continue; + } else if (result == 1) + return false; + else { + int controlPoints = 0; + switch ((c = readChar(input))) { + case '#': + output.addEdge(EdgeHolder(p[0], start, color)); + p[0] = start; + continue; + case ';': + goto FINISH_EDGE; + case '(': + goto READ_CONTROL_POINTS; + case 'C': case 'c': + color = CYAN; + colorsSpecified = true; + break; + case 'M': case 'm': + color = MAGENTA; + colorsSpecified = true; + break; + case 'Y': case 'y': + color = YELLOW; + colorsSpecified = true; + break; + case 'W': case 'w': + color = WHITE; + colorsSpecified = true; + break; + default: + return c == terminator; + } + switch (readChar(input)) { + case ';': + goto FINISH_EDGE; + case '(': + READ_CONTROL_POINTS: + if ((controlPoints = readControlPoints<T, readChar, readCoord>(input, p+1)) < 0) + return false; + break; + default: + return false; + } + if (readChar(input) != ';') + return false; + FINISH_EDGE: + result = readCoord(input, p[1+controlPoints]); + if (result != 2) { + if (result == 1) + return false; + else { + if (readChar(input) == '#') + p[1+controlPoints] = start; + else + return false; + } + } + switch (controlPoints) { + case 0: + output.addEdge(EdgeHolder(p[0], p[1], color)); + p[0] = p[1]; + continue; + case 1: + output.addEdge(EdgeHolder(p[0], p[1], p[2], color)); + p[0] = p[2]; + continue; + case 2: + output.addEdge(EdgeHolder(p[0], p[1], p[2], p[3], color)); + p[0] = p[3]; + continue; + } + } + } + return true; +} + +bool readShapeDescription(FILE *input, Shape &output, bool *colorsSpecified) { + bool locColorsSpec = false; + output.contours.clear(); + output.inverseYAxis = false; + Point2 p; + int result = readCoordF(input, p); + if (result == 2) { + return readContour<FILE, readCharF, readCoordF>(input, output.addContour(), &p, EOF, locColorsSpec); + } else if (result == 1) + return false; + else { + int c = readCharF(input); + if (c == '@') { + char after = '\0'; + if (fscanf(input, "invert-y%c", &after) != 1) + return feof(input) != 0; + output.inverseYAxis = true; + c = after; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + c = readCharF(input); + } + for (; c == '{'; c = readCharF(input)) + if (!readContour<FILE, readCharF, readCoordF>(input, output.addContour(), NULL, '}', locColorsSpec)) + return false; + if (colorsSpecified) + *colorsSpecified = locColorsSpec; + return c == EOF && feof(input); + } +} + +bool readShapeDescription(const char *input, Shape &output, bool *colorsSpecified) { + bool locColorsSpec = false; + output.contours.clear(); + output.inverseYAxis = false; + Point2 p; + int result = readCoordS(&input, p); + if (result == 2) { + return readContour<const char *, readCharS, readCoordS>(&input, output.addContour(), &p, EOF, locColorsSpec); + } else if (result == 1) + return false; + else { + int c = readCharS(&input); + if (c == '@') { + for (int i = 0; i < (int) sizeof("invert-y")-1; ++i) + if (input[i] != "invert-y"[i]) + return false; + output.inverseYAxis = true; + input += sizeof("invert-y")-1; + c = readCharS(&input); + } + for (; c == '{'; c = readCharS(&input)) + if (!readContour<const char *, readCharS, readCoordS>(&input, output.addContour(), NULL, '}', locColorsSpec)) + return false; + if (colorsSpecified) + *colorsSpecified = locColorsSpec; + return c == EOF; + } +} + +static bool isColored(const Shape &shape) { + for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) + if ((*edge)->color != WHITE) + return true; + return false; +} + +bool writeShapeDescription(FILE *output, const Shape &shape) { + if (!shape.validate()) + return false; + bool writeColors = isColored(shape); + if (shape.inverseYAxis) + fprintf(output, "@invert-y\n"); + for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) { + fprintf(output, "{\n"); + if (!contour->edges.empty()) { + for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + char colorCode = '\0'; + if (writeColors) { + switch ((*edge)->color) { + case YELLOW: colorCode = 'y'; break; + case MAGENTA: colorCode = 'm'; break; + case CYAN: colorCode = 'c'; break; + case WHITE: colorCode = 'w'; break; + default:; + } + } + if (const LinearSegment *e = dynamic_cast<const LinearSegment *>(&**edge)) { + fprintf(output, "\t"); + writeCoord(output, e->p[0]); + fprintf(output, ";\n"); + if (colorCode) + fprintf(output, "\t\t%c;\n", colorCode); + } + if (const QuadraticSegment *e = dynamic_cast<const QuadraticSegment *>(&**edge)) { + fprintf(output, "\t"); + writeCoord(output, e->p[0]); + fprintf(output, ";\n\t\t"); + if (colorCode) + fprintf(output, "%c", colorCode); + fprintf(output, "("); + writeCoord(output, e->p[1]); + fprintf(output, ");\n"); + } + if (const CubicSegment *e = dynamic_cast<const CubicSegment *>(&**edge)) { + fprintf(output, "\t"); + writeCoord(output, e->p[0]); + fprintf(output, ";\n\t\t"); + if (colorCode) + fprintf(output, "%c", colorCode); + fprintf(output, "("); + writeCoord(output, e->p[1]); + fprintf(output, "; "); + writeCoord(output, e->p[2]); + fprintf(output, ");\n"); + } + } + fprintf(output, "\t#\n"); + } + fprintf(output, "}\n"); + } + return true; +} + +} diff --git a/thirdparty/msdfgen/core/shape-description.h b/thirdparty/msdfgen/core/shape-description.h new file mode 100644 index 0000000000..5df7c50a03 --- /dev/null +++ b/thirdparty/msdfgen/core/shape-description.h @@ -0,0 +1,16 @@ + +#pragma once + +#include <cstdlib> +#include <cstdio> +#include "Shape.h" + +namespace msdfgen { + +/// Deserializes a text description of a vector shape into output. +bool readShapeDescription(FILE *input, Shape &output, bool *colorsSpecified = NULL); +bool readShapeDescription(const char *input, Shape &output, bool *colorsSpecified = NULL); +/// Serializes a shape object into a text description. +bool writeShapeDescription(FILE *output, const Shape &shape); + +} diff --git a/thirdparty/msdfgen/msdfgen.h b/thirdparty/msdfgen/msdfgen.h new file mode 100644 index 0000000000..fb36bd7e1d --- /dev/null +++ b/thirdparty/msdfgen/msdfgen.h @@ -0,0 +1,65 @@ + +#pragma once + +/* + * MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.9 (2021-05-28) + * --------------------------------------------------------------- + * A utility by Viktor Chlumsky, (c) 2014 - 2021 + * + * The technique used to generate multi-channel distance fields in this code + * has been developed by Viktor Chlumsky in 2014 for his master's thesis, + * "Shape Decomposition for Multi-Channel Distance Fields". It provides improved + * quality of sharp corners in glyphs and other 2D shapes compared to monochrome + * distance fields. To reconstruct an image of the shape, apply the median of three + * operation on the triplet of sampled signed distance values. + * + */ + +#include "core/arithmetics.hpp" +#include "core/Vector2.h" +#include "core/Projection.h" +#include "core/Scanline.h" +#include "core/Shape.h" +#include "core/BitmapRef.hpp" +#include "core/Bitmap.h" +#include "core/bitmap-interpolation.hpp" +#include "core/pixel-conversion.hpp" +#include "core/edge-coloring.h" +#include "core/generator-config.h" +#include "core/msdf-error-correction.h" +#include "core/render-sdf.h" +#include "core/rasterization.h" +#include "core/sdf-error-estimation.h" +#include "core/save-bmp.h" +#include "core/save-tiff.h" +#include "core/shape-description.h" + +#define MSDFGEN_VERSION "1.9" + +namespace msdfgen { + +/// Generates a conventional single-channel signed distance field. +void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig()); + +/// Generates a single-channel signed pseudo-distance field. +void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig()); + +/// Generates a multi-channel signed distance field. Edge colors must be assigned first! (See edgeColoringSimple) +void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig()); + +/// Generates a multi-channel signed distance field with true distance in the alpha channel. Edge colors must be assigned first. +void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig()); + +// Old version of the function API's kept for backwards compatibility +void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true); +void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true); +void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true); +void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true); + +// Original simpler versions of the previous functions, which work well under normal circumstances, but cannot deal with overlapping contours. +void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate); +void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate); +void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig()); +void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig()); + +} |