diff options
769 files changed, 29720 insertions, 14704 deletions
diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml index 63930aa9e2..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" @@ -39,6 +43,7 @@ jobs: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + continue-on-error: true # Use python 3.x release (works cross platform) - name: Set up Python 3.x diff --git a/.github/workflows/ios_builds.yml b/.github/workflows/ios_builds.yml index c4da3cb683..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" @@ -26,6 +30,7 @@ jobs: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + continue-on-error: true # Use python 3.x release (works cross platform) - name: Set up Python 3.x diff --git a/.github/workflows/javascript_builds.yml b/.github/workflows/javascript_builds.yml index 7f7ebf680f..25a063c3b2 100644 --- a/.github/workflows/javascript_builds.yml +++ b/.github/workflows/javascript_builds.yml @@ -6,9 +6,13 @@ env: GODOT_BASE_BRANCH: master SCONSFLAGS: platform=javascript verbose=yes warnings=extra werror=yes debug_symbols=no --jobs=2 SCONS_CACHE_LIMIT: 4096 - EM_VERSION: 2.0.25 + 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" @@ -35,6 +39,7 @@ jobs: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + continue-on-error: true # Additional cache for Emscripten generated system libraries - name: Load Emscripten cache diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 679362d749..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" @@ -39,6 +43,7 @@ jobs: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + continue-on-error: true # Use python 3.x release (works cross platform; best to keep self contained in it's own step) - name: Set up Python 3.x @@ -125,6 +130,7 @@ jobs: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + continue-on-error: true # Use python 3.x release (works cross platform; best to keep self contained in it's own step) - name: Set up Python 3.x @@ -215,6 +221,7 @@ jobs: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + continue-on-error: true # Use python 3.x release (works cross platform) - name: Set up Python 3.x diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index a15ab92052..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" @@ -27,6 +31,7 @@ jobs: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + continue-on-error: true # Use python 3.x release (works cross platform; best to keep self contained in it's own step) - name: Set up Python 3.x @@ -82,6 +87,7 @@ jobs: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + continue-on-error: true # Use python 3.x release (works cross platform) - name: Set up Python 3.x 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 d1db449ab0..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 @@ -32,6 +36,7 @@ jobs: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + continue-on-error: true # Use python 3.x release (works cross platform; best to keep self contained in it's own step) - name: Set up Python 3.x @@ -88,6 +93,7 @@ jobs: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + continue-on-error: true # Use python 3.x release (works cross platform) - name: Set up Python 3.x 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..4a38d0457c 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -377,6 +377,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 +1140,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 +1766,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/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/core_bind.cpp b/core/core_bind.cpp index f3f51e57ee..83a3b80803 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -41,39 +41,41 @@ #include "core/os/keyboard.h" #include "core/os/os.h" -////// _ResourceLoader ////// +namespace core_bind { -_ResourceLoader *_ResourceLoader::singleton = nullptr; +////// ResourceLoader ////// -Error _ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads) { - return ResourceLoader::load_threaded_request(p_path, p_type_hint, p_use_sub_threads); +ResourceLoader *ResourceLoader::singleton = nullptr; + +Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads) { + return ::ResourceLoader::load_threaded_request(p_path, p_type_hint, p_use_sub_threads); } -_ResourceLoader::ThreadLoadStatus _ResourceLoader::load_threaded_get_status(const String &p_path, Array r_progress) { +ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, Array r_progress) { float progress = 0; - ResourceLoader::ThreadLoadStatus tls = ResourceLoader::load_threaded_get_status(p_path, &progress); + ::ResourceLoader::ThreadLoadStatus tls = ::ResourceLoader::load_threaded_get_status(p_path, &progress); r_progress.resize(1); r_progress[0] = progress; return (ThreadLoadStatus)tls; } -RES _ResourceLoader::load_threaded_get(const String &p_path) { +RES ResourceLoader::load_threaded_get(const String &p_path) { Error error; - RES res = ResourceLoader::load_threaded_get(p_path, &error); + RES res = ::ResourceLoader::load_threaded_get(p_path, &error); return res; } -RES _ResourceLoader::load(const String &p_path, const String &p_type_hint, CacheMode p_cache_mode) { +RES ResourceLoader::load(const String &p_path, const String &p_type_hint, CacheMode p_cache_mode) { Error err = OK; - RES ret = ResourceLoader::load(p_path, p_type_hint, ResourceFormatLoader::CacheMode(p_cache_mode), &err); + RES ret = ::ResourceLoader::load(p_path, p_type_hint, ResourceFormatLoader::CacheMode(p_cache_mode), &err); ERR_FAIL_COND_V_MSG(err != OK, ret, "Error loading resource: '" + p_path + "'."); return ret; } -Vector<String> _ResourceLoader::get_recognized_extensions_for_type(const String &p_type) { +Vector<String> ResourceLoader::get_recognized_extensions_for_type(const String &p_type) { List<String> exts; - ResourceLoader::get_recognized_extensions_for_type(p_type, &exts); + ::ResourceLoader::get_recognized_extensions_for_type(p_type, &exts); Vector<String> ret; for (const String &E : exts) { ret.push_back(E); @@ -82,13 +84,13 @@ Vector<String> _ResourceLoader::get_recognized_extensions_for_type(const String return ret; } -void _ResourceLoader::set_abort_on_missing_resources(bool p_abort) { - ResourceLoader::set_abort_on_missing_resources(p_abort); +void ResourceLoader::set_abort_on_missing_resources(bool p_abort) { + ::ResourceLoader::set_abort_on_missing_resources(p_abort); } -PackedStringArray _ResourceLoader::get_dependencies(const String &p_path) { +PackedStringArray ResourceLoader::get_dependencies(const String &p_path) { List<String> deps; - ResourceLoader::get_dependencies(p_path, &deps); + ::ResourceLoader::get_dependencies(p_path, &deps); PackedStringArray ret; for (const String &E : deps) { @@ -98,31 +100,31 @@ PackedStringArray _ResourceLoader::get_dependencies(const String &p_path) { return ret; } -bool _ResourceLoader::has_cached(const String &p_path) { +bool ResourceLoader::has_cached(const String &p_path) { String local_path = ProjectSettings::get_singleton()->localize_path(p_path); return ResourceCache::has(local_path); } -bool _ResourceLoader::exists(const String &p_path, const String &p_type_hint) { - return ResourceLoader::exists(p_path, p_type_hint); +bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) { + return ::ResourceLoader::exists(p_path, p_type_hint); } -ResourceUID::ID _ResourceLoader::get_resource_uid(const String &p_path) { - return ResourceLoader::get_resource_uid(p_path); +ResourceUID::ID ResourceLoader::get_resource_uid(const String &p_path) { + return ::ResourceLoader::get_resource_uid(p_path); } -void _ResourceLoader::_bind_methods() { - ClassDB::bind_method(D_METHOD("load_threaded_request", "path", "type_hint", "use_sub_threads"), &_ResourceLoader::load_threaded_request, DEFVAL(""), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &_ResourceLoader::load_threaded_get_status, DEFVAL(Array())); - ClassDB::bind_method(D_METHOD("load_threaded_get", "path"), &_ResourceLoader::load_threaded_get); +void ResourceLoader::_bind_methods() { + ClassDB::bind_method(D_METHOD("load_threaded_request", "path", "type_hint", "use_sub_threads"), &ResourceLoader::load_threaded_request, DEFVAL(""), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("load_threaded_get", "path"), &ResourceLoader::load_threaded_get); - ClassDB::bind_method(D_METHOD("load", "path", "type_hint", "cache_mode"), &_ResourceLoader::load, DEFVAL(""), DEFVAL(CACHE_MODE_REUSE)); - ClassDB::bind_method(D_METHOD("get_recognized_extensions_for_type", "type"), &_ResourceLoader::get_recognized_extensions_for_type); - ClassDB::bind_method(D_METHOD("set_abort_on_missing_resources", "abort"), &_ResourceLoader::set_abort_on_missing_resources); - ClassDB::bind_method(D_METHOD("get_dependencies", "path"), &_ResourceLoader::get_dependencies); - ClassDB::bind_method(D_METHOD("has_cached", "path"), &_ResourceLoader::has_cached); - ClassDB::bind_method(D_METHOD("exists", "path", "type_hint"), &_ResourceLoader::exists, DEFVAL("")); - ClassDB::bind_method(D_METHOD("get_resource_uid", "path"), &_ResourceLoader::get_resource_uid); + ClassDB::bind_method(D_METHOD("load", "path", "type_hint", "cache_mode"), &ResourceLoader::load, DEFVAL(""), DEFVAL(CACHE_MODE_REUSE)); + ClassDB::bind_method(D_METHOD("get_recognized_extensions_for_type", "type"), &ResourceLoader::get_recognized_extensions_for_type); + ClassDB::bind_method(D_METHOD("set_abort_on_missing_resources", "abort"), &ResourceLoader::set_abort_on_missing_resources); + ClassDB::bind_method(D_METHOD("get_dependencies", "path"), &ResourceLoader::get_dependencies); + ClassDB::bind_method(D_METHOD("has_cached", "path"), &ResourceLoader::has_cached); + ClassDB::bind_method(D_METHOD("exists", "path", "type_hint"), &ResourceLoader::exists, DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_resource_uid", "path"), &ResourceLoader::get_resource_uid); BIND_ENUM_CONSTANT(THREAD_LOAD_INVALID_RESOURCE); BIND_ENUM_CONSTANT(THREAD_LOAD_IN_PROGRESS); @@ -134,17 +136,17 @@ void _ResourceLoader::_bind_methods() { BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE); } -////// _ResourceSaver ////// +////// ResourceSaver ////// -Error _ResourceSaver::save(const String &p_path, const RES &p_resource, SaverFlags p_flags) { +Error ResourceSaver::save(const String &p_path, const RES &p_resource, SaverFlags p_flags) { ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, "Can't save empty resource to path '" + String(p_path) + "'."); - return ResourceSaver::save(p_path, p_resource, p_flags); + return ::ResourceSaver::save(p_path, p_resource, p_flags); } -Vector<String> _ResourceSaver::get_recognized_extensions(const RES &p_resource) { +Vector<String> ResourceSaver::get_recognized_extensions(const RES &p_resource) { ERR_FAIL_COND_V_MSG(p_resource.is_null(), Vector<String>(), "It's not a reference to a valid Resource object."); List<String> exts; - ResourceSaver::get_recognized_extensions(p_resource, &exts); + ::ResourceSaver::get_recognized_extensions(p_resource, &exts); Vector<String> ret; for (const String &E : exts) { ret.push_back(E); @@ -152,11 +154,11 @@ Vector<String> _ResourceSaver::get_recognized_extensions(const RES &p_resource) return ret; } -_ResourceSaver *_ResourceSaver::singleton = nullptr; +ResourceSaver *ResourceSaver::singleton = nullptr; -void _ResourceSaver::_bind_methods() { - ClassDB::bind_method(D_METHOD("save", "path", "resource", "flags"), &_ResourceSaver::save, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_recognized_extensions", "type"), &_ResourceSaver::get_recognized_extensions); +void ResourceSaver::_bind_methods() { + ClassDB::bind_method(D_METHOD("save", "path", "resource", "flags"), &ResourceSaver::save, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_recognized_extensions", "type"), &ResourceSaver::get_recognized_extensions); BIND_ENUM_CONSTANT(FLAG_RELATIVE_PATHS); BIND_ENUM_CONSTANT(FLAG_BUNDLE_RESOURCES); @@ -167,65 +169,65 @@ void _ResourceSaver::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_REPLACE_SUBRESOURCE_PATHS); } -////// _OS ////// +////// OS ////// -PackedStringArray _OS::get_connected_midi_inputs() { - return OS::get_singleton()->get_connected_midi_inputs(); +PackedStringArray OS::get_connected_midi_inputs() { + return ::OS::get_singleton()->get_connected_midi_inputs(); } -void _OS::open_midi_inputs() { - OS::get_singleton()->open_midi_inputs(); +void OS::open_midi_inputs() { + ::OS::get_singleton()->open_midi_inputs(); } -void _OS::close_midi_inputs() { - OS::get_singleton()->close_midi_inputs(); +void OS::close_midi_inputs() { + ::OS::get_singleton()->close_midi_inputs(); } -void _OS::set_use_file_access_save_and_swap(bool p_enable) { +void OS::set_use_file_access_save_and_swap(bool p_enable) { FileAccess::set_backup_save(p_enable); } -void _OS::set_low_processor_usage_mode(bool p_enabled) { - OS::get_singleton()->set_low_processor_usage_mode(p_enabled); +void OS::set_low_processor_usage_mode(bool p_enabled) { + ::OS::get_singleton()->set_low_processor_usage_mode(p_enabled); } -bool _OS::is_in_low_processor_usage_mode() const { - return OS::get_singleton()->is_in_low_processor_usage_mode(); +bool OS::is_in_low_processor_usage_mode() const { + return ::OS::get_singleton()->is_in_low_processor_usage_mode(); } -void _OS::set_low_processor_usage_mode_sleep_usec(int p_usec) { - OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(p_usec); +void OS::set_low_processor_usage_mode_sleep_usec(int p_usec) { + ::OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(p_usec); } -int _OS::get_low_processor_usage_mode_sleep_usec() const { - return OS::get_singleton()->get_low_processor_usage_mode_sleep_usec(); +int OS::get_low_processor_usage_mode_sleep_usec() const { + return ::OS::get_singleton()->get_low_processor_usage_mode_sleep_usec(); } -void _OS::alert(const String &p_alert, const String &p_title) { - OS::get_singleton()->alert(p_alert, p_title); +void OS::alert(const String &p_alert, const String &p_title) { + ::OS::get_singleton()->alert(p_alert, p_title); } -String _OS::get_executable_path() const { - return OS::get_singleton()->get_executable_path(); +String OS::get_executable_path() const { + return ::OS::get_singleton()->get_executable_path(); } -Error _OS::shell_open(String p_uri) { +Error OS::shell_open(String p_uri) { if (p_uri.begins_with("res://")) { WARN_PRINT("Attempting to open an URL with the \"res://\" protocol. Use `ProjectSettings.globalize_path()` to convert a Godot-specific path to a system path before opening it with `OS.shell_open()`."); } else if (p_uri.begins_with("user://")) { WARN_PRINT("Attempting to open an URL with the \"user://\" protocol. Use `ProjectSettings.globalize_path()` to convert a Godot-specific path to a system path before opening it with `OS.shell_open()`."); } - return OS::get_singleton()->shell_open(p_uri); + return ::OS::get_singleton()->shell_open(p_uri); } -int _OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r_output, bool p_read_stderr) { +int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r_output, bool p_read_stderr) { List<String> args; for (int i = 0; i < p_arguments.size(); i++) { args.push_back(p_arguments[i]); } String pipe; int exitcode = 0; - Error err = OS::get_singleton()->execute(p_path, args, &pipe, &exitcode, p_read_stderr); + Error err = ::OS::get_singleton()->execute(p_path, args, &pipe, &exitcode, p_read_stderr); r_output.push_back(pipe); if (err != OK) { return -1; @@ -233,45 +235,45 @@ int _OS::execute(const String &p_path, const Vector<String> &p_arguments, Array return exitcode; } -int _OS::create_process(const String &p_path, const Vector<String> &p_arguments) { +int OS::create_process(const String &p_path, const Vector<String> &p_arguments) { List<String> args; for (int i = 0; i < p_arguments.size(); i++) { args.push_back(p_arguments[i]); } - OS::ProcessID pid = 0; - Error err = OS::get_singleton()->create_process(p_path, args, &pid); + ::OS::ProcessID pid = 0; + Error err = ::OS::get_singleton()->create_process(p_path, args, &pid); if (err != OK) { return -1; } return pid; } -Error _OS::kill(int p_pid) { - return OS::get_singleton()->kill(p_pid); +Error OS::kill(int p_pid) { + return ::OS::get_singleton()->kill(p_pid); } -int _OS::get_process_id() const { - return OS::get_singleton()->get_process_id(); +int OS::get_process_id() const { + return ::OS::get_singleton()->get_process_id(); } -bool _OS::has_environment(const String &p_var) const { - return OS::get_singleton()->has_environment(p_var); +bool OS::has_environment(const String &p_var) const { + return ::OS::get_singleton()->has_environment(p_var); } -String _OS::get_environment(const String &p_var) const { - return OS::get_singleton()->get_environment(p_var); +String OS::get_environment(const String &p_var) const { + return ::OS::get_singleton()->get_environment(p_var); } -bool _OS::set_environment(const String &p_var, const String &p_value) const { - return OS::get_singleton()->set_environment(p_var, p_value); +bool OS::set_environment(const String &p_var, const String &p_value) const { + return ::OS::get_singleton()->set_environment(p_var, p_value); } -String _OS::get_name() const { - return OS::get_singleton()->get_name(); +String OS::get_name() const { + return ::OS::get_singleton()->get_name(); } -Vector<String> _OS::get_cmdline_args() { - List<String> cmdline = OS::get_singleton()->get_cmdline_args(); +Vector<String> OS::get_cmdline_args() { + List<String> cmdline = ::OS::get_singleton()->get_cmdline_args(); Vector<String> cmdlinev; for (const String &E : cmdline) { cmdlinev.push_back(E); @@ -280,81 +282,81 @@ Vector<String> _OS::get_cmdline_args() { return cmdlinev; } -String _OS::get_locale() const { - return OS::get_singleton()->get_locale(); +String OS::get_locale() const { + return ::OS::get_singleton()->get_locale(); } -String _OS::get_model_name() const { - return OS::get_singleton()->get_model_name(); +String OS::get_model_name() const { + return ::OS::get_singleton()->get_model_name(); } -Error _OS::set_thread_name(const String &p_name) { - return Thread::set_name(p_name); +Error OS::set_thread_name(const String &p_name) { + return ::Thread::set_name(p_name); } -Thread::ID _OS::get_thread_caller_id() const { - return Thread::get_caller_id(); +::Thread::ID OS::get_thread_caller_id() const { + return ::Thread::get_caller_id(); }; -bool _OS::has_feature(const String &p_feature) const { - return OS::get_singleton()->has_feature(p_feature); +bool OS::has_feature(const String &p_feature) const { + return ::OS::get_singleton()->has_feature(p_feature); } -uint64_t _OS::get_static_memory_usage() const { - return OS::get_singleton()->get_static_memory_usage(); +uint64_t OS::get_static_memory_usage() const { + return ::OS::get_singleton()->get_static_memory_usage(); } -uint64_t _OS::get_static_memory_peak_usage() const { - return OS::get_singleton()->get_static_memory_peak_usage(); +uint64_t OS::get_static_memory_peak_usage() const { + return ::OS::get_singleton()->get_static_memory_peak_usage(); } /** This method uses a signed argument for better error reporting as it's used from the scripting API. */ -void _OS::delay_usec(int p_usec) const { +void OS::delay_usec(int p_usec) const { ERR_FAIL_COND_MSG( p_usec < 0, vformat("Can't sleep for %d microseconds. The delay provided must be greater than or equal to 0 microseconds.", p_usec)); - OS::get_singleton()->delay_usec(p_usec); + ::OS::get_singleton()->delay_usec(p_usec); } /** This method uses a signed argument for better error reporting as it's used from the scripting API. */ -void _OS::delay_msec(int p_msec) const { +void OS::delay_msec(int p_msec) const { ERR_FAIL_COND_MSG( p_msec < 0, vformat("Can't sleep for %d milliseconds. The delay provided must be greater than or equal to 0 milliseconds.", p_msec)); - OS::get_singleton()->delay_usec(int64_t(p_msec) * 1000); + ::OS::get_singleton()->delay_usec(int64_t(p_msec) * 1000); } -bool _OS::can_use_threads() const { - return OS::get_singleton()->can_use_threads(); +bool OS::can_use_threads() const { + return ::OS::get_singleton()->can_use_threads(); } -bool _OS::is_userfs_persistent() const { - return OS::get_singleton()->is_userfs_persistent(); +bool OS::is_userfs_persistent() const { + return ::OS::get_singleton()->is_userfs_persistent(); } -int _OS::get_processor_count() const { - return OS::get_singleton()->get_processor_count(); +int OS::get_processor_count() const { + return ::OS::get_singleton()->get_processor_count(); } -bool _OS::is_stdout_verbose() const { - return OS::get_singleton()->is_stdout_verbose(); +bool OS::is_stdout_verbose() const { + return ::OS::get_singleton()->is_stdout_verbose(); } -void _OS::dump_memory_to_file(const String &p_file) { - OS::get_singleton()->dump_memory_to_file(p_file.utf8().get_data()); +void OS::dump_memory_to_file(const String &p_file) { + ::OS::get_singleton()->dump_memory_to_file(p_file.utf8().get_data()); } -struct _OSCoreBindImg { +struct OSCoreBindImg { String path; Size2 size; int fmt = 0; ObjectID id; int vram = 0; - bool operator<(const _OSCoreBindImg &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; } + bool operator<(const OSCoreBindImg &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; } }; -void _OS::print_all_textures_by_size() { - List<_OSCoreBindImg> imgs; +void OS::print_all_textures_by_size() { + List<OSCoreBindImg> imgs; uint64_t total = 0; { List<Ref<Resource>> rsrc; @@ -368,7 +370,7 @@ void _OS::print_all_textures_by_size() { Size2 size = res->call("get_size"); int fmt = res->call("get_format"); - _OSCoreBindImg img; + OSCoreBindImg img; img.size = size; img.fmt = fmt; img.path = res->get_path(); @@ -388,7 +390,7 @@ void _OS::print_all_textures_by_size() { "Path - VRAM usage (Dimensions)"); } - for (const _OSCoreBindImg &img : imgs) { + for (const OSCoreBindImg &img : imgs) { print_line(vformat("%s - %s %s", img.path, String::humanize_size(img.vram), @@ -398,7 +400,7 @@ void _OS::print_all_textures_by_size() { print_line(vformat("Total VRAM usage: %s.", String::humanize_size(total))); } -void _OS::print_resources_by_type(const Vector<String> &p_types) { +void OS::print_resources_by_type(const Vector<String> &p_types) { ERR_FAIL_COND_MSG(p_types.size() == 0, "At least one type should be provided to print resources by type."); @@ -440,42 +442,38 @@ void _OS::print_resources_by_type(const Vector<String> &p_types) { } } -void _OS::print_all_resources(const String &p_to_file) { - OS::get_singleton()->print_all_resources(p_to_file); -} - -void _OS::print_resources_in_use(bool p_short) { - OS::get_singleton()->print_resources_in_use(p_short); +void OS::print_all_resources(const String &p_to_file) { + ::OS::get_singleton()->print_all_resources(p_to_file); } -void _OS::dump_resources_to_file(const String &p_file) { - OS::get_singleton()->dump_resources_to_file(p_file.utf8().get_data()); +void OS::print_resources_in_use(bool p_short) { + ::OS::get_singleton()->print_resources_in_use(p_short); } -String _OS::get_user_data_dir() const { - return OS::get_singleton()->get_user_data_dir(); +void OS::dump_resources_to_file(const String &p_file) { + ::OS::get_singleton()->dump_resources_to_file(p_file.utf8().get_data()); } -String _OS::get_external_data_dir() const { - return OS::get_singleton()->get_external_data_dir(); +String OS::get_user_data_dir() const { + return ::OS::get_singleton()->get_user_data_dir(); } -String _OS::get_config_dir() const { +String OS::get_config_dir() const { // Exposed as `get_config_dir()` instead of `get_config_path()` for consistency with other exposed OS methods. - return OS::get_singleton()->get_config_path(); + return ::OS::get_singleton()->get_config_path(); } -String _OS::get_data_dir() const { +String OS::get_data_dir() const { // Exposed as `get_data_dir()` instead of `get_data_path()` for consistency with other exposed OS methods. - return OS::get_singleton()->get_data_path(); + return ::OS::get_singleton()->get_data_path(); } -String _OS::get_cache_dir() const { +String OS::get_cache_dir() const { // Exposed as `get_cache_dir()` instead of `get_cache_path()` for consistency with other exposed OS methods. - return OS::get_singleton()->get_cache_path(); + return ::OS::get_singleton()->get_cache_path(); } -bool _OS::is_debug_build() const { +bool OS::is_debug_build() const { #ifdef DEBUG_ENABLED return true; #else @@ -483,114 +481,113 @@ bool _OS::is_debug_build() const { #endif } -String _OS::get_system_dir(SystemDir p_dir) const { - return OS::get_singleton()->get_system_dir(OS::SystemDir(p_dir)); +String OS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { + return ::OS::get_singleton()->get_system_dir(::OS::SystemDir(p_dir), p_shared_storage); } -String _OS::get_keycode_string(uint32_t p_code) const { - return keycode_get_string(p_code); +String OS::get_keycode_string(uint32_t p_code) const { + return ::keycode_get_string(p_code); } -bool _OS::is_keycode_unicode(uint32_t p_unicode) const { - return keycode_has_unicode(p_unicode); +bool OS::is_keycode_unicode(uint32_t p_unicode) const { + return ::keycode_has_unicode(p_unicode); } -int _OS::find_keycode_from_string(const String &p_code) const { +int OS::find_keycode_from_string(const String &p_code) const { return find_keycode(p_code); } -bool _OS::request_permission(const String &p_name) { - return OS::get_singleton()->request_permission(p_name); +bool OS::request_permission(const String &p_name) { + return ::OS::get_singleton()->request_permission(p_name); } -bool _OS::request_permissions() { - return OS::get_singleton()->request_permissions(); +bool OS::request_permissions() { + return ::OS::get_singleton()->request_permissions(); } -Vector<String> _OS::get_granted_permissions() const { - return OS::get_singleton()->get_granted_permissions(); +Vector<String> OS::get_granted_permissions() const { + return ::OS::get_singleton()->get_granted_permissions(); } -String _OS::get_unique_id() const { - return OS::get_singleton()->get_unique_id(); +String OS::get_unique_id() const { + return ::OS::get_singleton()->get_unique_id(); } -_OS *_OS::singleton = nullptr; +OS *OS::singleton = nullptr; -void _OS::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_connected_midi_inputs"), &_OS::get_connected_midi_inputs); - ClassDB::bind_method(D_METHOD("open_midi_inputs"), &_OS::open_midi_inputs); - ClassDB::bind_method(D_METHOD("close_midi_inputs"), &_OS::close_midi_inputs); +void OS::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_connected_midi_inputs"), &OS::get_connected_midi_inputs); + ClassDB::bind_method(D_METHOD("open_midi_inputs"), &OS::open_midi_inputs); + ClassDB::bind_method(D_METHOD("close_midi_inputs"), &OS::close_midi_inputs); - ClassDB::bind_method(D_METHOD("alert", "text", "title"), &_OS::alert, DEFVAL("Alert!")); + ClassDB::bind_method(D_METHOD("alert", "text", "title"), &OS::alert, DEFVAL("Alert!")); - ClassDB::bind_method(D_METHOD("set_low_processor_usage_mode", "enable"), &_OS::set_low_processor_usage_mode); - ClassDB::bind_method(D_METHOD("is_in_low_processor_usage_mode"), &_OS::is_in_low_processor_usage_mode); + ClassDB::bind_method(D_METHOD("set_low_processor_usage_mode", "enable"), &OS::set_low_processor_usage_mode); + ClassDB::bind_method(D_METHOD("is_in_low_processor_usage_mode"), &OS::is_in_low_processor_usage_mode); - ClassDB::bind_method(D_METHOD("set_low_processor_usage_mode_sleep_usec", "usec"), &_OS::set_low_processor_usage_mode_sleep_usec); - ClassDB::bind_method(D_METHOD("get_low_processor_usage_mode_sleep_usec"), &_OS::get_low_processor_usage_mode_sleep_usec); + ClassDB::bind_method(D_METHOD("set_low_processor_usage_mode_sleep_usec", "usec"), &OS::set_low_processor_usage_mode_sleep_usec); + ClassDB::bind_method(D_METHOD("get_low_processor_usage_mode_sleep_usec"), &OS::get_low_processor_usage_mode_sleep_usec); - ClassDB::bind_method(D_METHOD("get_processor_count"), &_OS::get_processor_count); + ClassDB::bind_method(D_METHOD("get_processor_count"), &OS::get_processor_count); - ClassDB::bind_method(D_METHOD("get_executable_path"), &_OS::get_executable_path); - ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr"), &_OS::execute, DEFVAL(Array()), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("create_process", "path", "arguments"), &_OS::create_process); - ClassDB::bind_method(D_METHOD("kill", "pid"), &_OS::kill); - ClassDB::bind_method(D_METHOD("shell_open", "uri"), &_OS::shell_open); - ClassDB::bind_method(D_METHOD("get_process_id"), &_OS::get_process_id); + ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path); + ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr"), &OS::execute, DEFVAL(Array()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("create_process", "path", "arguments"), &OS::create_process); + ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill); + ClassDB::bind_method(D_METHOD("shell_open", "uri"), &OS::shell_open); + ClassDB::bind_method(D_METHOD("get_process_id"), &OS::get_process_id); - ClassDB::bind_method(D_METHOD("get_environment", "variable"), &_OS::get_environment); - ClassDB::bind_method(D_METHOD("set_environment", "variable", "value"), &_OS::set_environment); - ClassDB::bind_method(D_METHOD("has_environment", "variable"), &_OS::has_environment); + ClassDB::bind_method(D_METHOD("get_environment", "variable"), &OS::get_environment); + ClassDB::bind_method(D_METHOD("set_environment", "variable", "value"), &OS::set_environment); + ClassDB::bind_method(D_METHOD("has_environment", "variable"), &OS::has_environment); - ClassDB::bind_method(D_METHOD("get_name"), &_OS::get_name); - ClassDB::bind_method(D_METHOD("get_cmdline_args"), &_OS::get_cmdline_args); + ClassDB::bind_method(D_METHOD("get_name"), &OS::get_name); + ClassDB::bind_method(D_METHOD("get_cmdline_args"), &OS::get_cmdline_args); - ClassDB::bind_method(D_METHOD("delay_usec", "usec"), &_OS::delay_usec); - ClassDB::bind_method(D_METHOD("delay_msec", "msec"), &_OS::delay_msec); - ClassDB::bind_method(D_METHOD("get_locale"), &_OS::get_locale); - ClassDB::bind_method(D_METHOD("get_model_name"), &_OS::get_model_name); + ClassDB::bind_method(D_METHOD("delay_usec", "usec"), &OS::delay_usec); + ClassDB::bind_method(D_METHOD("delay_msec", "msec"), &OS::delay_msec); + ClassDB::bind_method(D_METHOD("get_locale"), &OS::get_locale); + ClassDB::bind_method(D_METHOD("get_model_name"), &OS::get_model_name); - ClassDB::bind_method(D_METHOD("is_userfs_persistent"), &_OS::is_userfs_persistent); - ClassDB::bind_method(D_METHOD("is_stdout_verbose"), &_OS::is_stdout_verbose); + ClassDB::bind_method(D_METHOD("is_userfs_persistent"), &OS::is_userfs_persistent); + ClassDB::bind_method(D_METHOD("is_stdout_verbose"), &OS::is_stdout_verbose); - ClassDB::bind_method(D_METHOD("can_use_threads"), &_OS::can_use_threads); + ClassDB::bind_method(D_METHOD("can_use_threads"), &OS::can_use_threads); - ClassDB::bind_method(D_METHOD("is_debug_build"), &_OS::is_debug_build); + ClassDB::bind_method(D_METHOD("is_debug_build"), &OS::is_debug_build); - ClassDB::bind_method(D_METHOD("dump_memory_to_file", "file"), &_OS::dump_memory_to_file); - ClassDB::bind_method(D_METHOD("dump_resources_to_file", "file"), &_OS::dump_resources_to_file); - ClassDB::bind_method(D_METHOD("print_resources_in_use", "short"), &_OS::print_resources_in_use, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("print_all_resources", "tofile"), &_OS::print_all_resources, DEFVAL("")); + ClassDB::bind_method(D_METHOD("dump_memory_to_file", "file"), &OS::dump_memory_to_file); + ClassDB::bind_method(D_METHOD("dump_resources_to_file", "file"), &OS::dump_resources_to_file); + ClassDB::bind_method(D_METHOD("print_resources_in_use", "short"), &OS::print_resources_in_use, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("print_all_resources", "tofile"), &OS::print_all_resources, DEFVAL("")); - ClassDB::bind_method(D_METHOD("get_static_memory_usage"), &_OS::get_static_memory_usage); - ClassDB::bind_method(D_METHOD("get_static_memory_peak_usage"), &_OS::get_static_memory_peak_usage); + ClassDB::bind_method(D_METHOD("get_static_memory_usage"), &OS::get_static_memory_usage); + ClassDB::bind_method(D_METHOD("get_static_memory_peak_usage"), &OS::get_static_memory_peak_usage); - ClassDB::bind_method(D_METHOD("get_user_data_dir"), &_OS::get_user_data_dir); - ClassDB::bind_method(D_METHOD("get_external_data_dir"), &_OS::get_external_data_dir); - ClassDB::bind_method(D_METHOD("get_system_dir", "dir"), &_OS::get_system_dir); - ClassDB::bind_method(D_METHOD("get_config_dir"), &_OS::get_config_dir); - ClassDB::bind_method(D_METHOD("get_data_dir"), &_OS::get_data_dir); - ClassDB::bind_method(D_METHOD("get_cache_dir"), &_OS::get_cache_dir); - ClassDB::bind_method(D_METHOD("get_unique_id"), &_OS::get_unique_id); + ClassDB::bind_method(D_METHOD("get_user_data_dir"), &OS::get_user_data_dir); + ClassDB::bind_method(D_METHOD("get_system_dir", "dir", "shared_storage"), &OS::get_system_dir, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_config_dir"), &OS::get_config_dir); + ClassDB::bind_method(D_METHOD("get_data_dir"), &OS::get_data_dir); + ClassDB::bind_method(D_METHOD("get_cache_dir"), &OS::get_cache_dir); + ClassDB::bind_method(D_METHOD("get_unique_id"), &OS::get_unique_id); - ClassDB::bind_method(D_METHOD("print_all_textures_by_size"), &_OS::print_all_textures_by_size); - ClassDB::bind_method(D_METHOD("print_resources_by_type", "types"), &_OS::print_resources_by_type); + ClassDB::bind_method(D_METHOD("print_all_textures_by_size"), &OS::print_all_textures_by_size); + ClassDB::bind_method(D_METHOD("print_resources_by_type", "types"), &OS::print_resources_by_type); - ClassDB::bind_method(D_METHOD("get_keycode_string", "code"), &_OS::get_keycode_string); - ClassDB::bind_method(D_METHOD("is_keycode_unicode", "code"), &_OS::is_keycode_unicode); - ClassDB::bind_method(D_METHOD("find_keycode_from_string", "string"), &_OS::find_keycode_from_string); + ClassDB::bind_method(D_METHOD("get_keycode_string", "code"), &OS::get_keycode_string); + ClassDB::bind_method(D_METHOD("is_keycode_unicode", "code"), &OS::is_keycode_unicode); + ClassDB::bind_method(D_METHOD("find_keycode_from_string", "string"), &OS::find_keycode_from_string); - ClassDB::bind_method(D_METHOD("set_use_file_access_save_and_swap", "enabled"), &_OS::set_use_file_access_save_and_swap); + ClassDB::bind_method(D_METHOD("set_use_file_access_save_and_swap", "enabled"), &OS::set_use_file_access_save_and_swap); - ClassDB::bind_method(D_METHOD("set_thread_name", "name"), &_OS::set_thread_name); - ClassDB::bind_method(D_METHOD("get_thread_caller_id"), &_OS::get_thread_caller_id); + ClassDB::bind_method(D_METHOD("set_thread_name", "name"), &OS::set_thread_name); + ClassDB::bind_method(D_METHOD("get_thread_caller_id"), &OS::get_thread_caller_id); - ClassDB::bind_method(D_METHOD("has_feature", "tag_name"), &_OS::has_feature); + ClassDB::bind_method(D_METHOD("has_feature", "tag_name"), &OS::has_feature); - ClassDB::bind_method(D_METHOD("request_permission", "name"), &_OS::request_permission); - ClassDB::bind_method(D_METHOD("request_permissions"), &_OS::request_permissions); - ClassDB::bind_method(D_METHOD("get_granted_permissions"), &_OS::get_granted_permissions); + ClassDB::bind_method(D_METHOD("request_permission", "name"), &OS::request_permission); + ClassDB::bind_method(D_METHOD("request_permissions"), &OS::request_permissions); + ClassDB::bind_method(D_METHOD("get_granted_permissions"), &OS::get_granted_permissions); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "low_processor_usage_mode"), "set_low_processor_usage_mode", "is_in_low_processor_usage_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "low_processor_usage_mode_sleep_usec"), "set_low_processor_usage_mode_sleep_usec", "get_low_processor_usage_mode_sleep_usec"); @@ -635,43 +632,43 @@ void _OS::_bind_methods() { BIND_ENUM_CONSTANT(SYSTEM_DIR_RINGTONES); } -////// _Geometry2D ////// +////// Geometry2D ////// -_Geometry2D *_Geometry2D::singleton = nullptr; +Geometry2D *Geometry2D::singleton = nullptr; -_Geometry2D *_Geometry2D::get_singleton() { +Geometry2D *Geometry2D::get_singleton() { return singleton; } -bool _Geometry2D::is_point_in_circle(const Vector2 &p_point, const Vector2 &p_circle_pos, real_t p_circle_radius) { - return Geometry2D::is_point_in_circle(p_point, p_circle_pos, p_circle_radius); +bool Geometry2D::is_point_in_circle(const Vector2 &p_point, const Vector2 &p_circle_pos, real_t p_circle_radius) { + return ::Geometry2D::is_point_in_circle(p_point, p_circle_pos, p_circle_radius); } -real_t _Geometry2D::segment_intersects_circle(const Vector2 &p_from, const Vector2 &p_to, const Vector2 &p_circle_pos, real_t p_circle_radius) { - return Geometry2D::segment_intersects_circle(p_from, p_to, p_circle_pos, p_circle_radius); +real_t Geometry2D::segment_intersects_circle(const Vector2 &p_from, const Vector2 &p_to, const Vector2 &p_circle_pos, real_t p_circle_radius) { + return ::Geometry2D::segment_intersects_circle(p_from, p_to, p_circle_pos, p_circle_radius); } -Variant _Geometry2D::segment_intersects_segment(const Vector2 &p_from_a, const Vector2 &p_to_a, const Vector2 &p_from_b, const Vector2 &p_to_b) { +Variant Geometry2D::segment_intersects_segment(const Vector2 &p_from_a, const Vector2 &p_to_a, const Vector2 &p_from_b, const Vector2 &p_to_b) { Vector2 result; - if (Geometry2D::segment_intersects_segment(p_from_a, p_to_a, p_from_b, p_to_b, &result)) { + if (::Geometry2D::segment_intersects_segment(p_from_a, p_to_a, p_from_b, p_to_b, &result)) { return result; } else { return Variant(); } } -Variant _Geometry2D::line_intersects_line(const Vector2 &p_from_a, const Vector2 &p_dir_a, const Vector2 &p_from_b, const Vector2 &p_dir_b) { +Variant Geometry2D::line_intersects_line(const Vector2 &p_from_a, const Vector2 &p_dir_a, const Vector2 &p_from_b, const Vector2 &p_dir_b) { Vector2 result; - if (Geometry2D::line_intersects_line(p_from_a, p_dir_a, p_from_b, p_dir_b, result)) { + if (::Geometry2D::line_intersects_line(p_from_a, p_dir_a, p_from_b, p_dir_b, result)) { return result; } else { return Variant(); } } -Vector<Vector2> _Geometry2D::get_closest_points_between_segments(const Vector2 &p1, const Vector2 &q1, const Vector2 &p2, const Vector2 &q2) { +Vector<Vector2> Geometry2D::get_closest_points_between_segments(const Vector2 &p1, const Vector2 &q1, const Vector2 &p2, const Vector2 &q2) { Vector2 r1, r2; - Geometry2D::get_closest_points_between_segments(p1, q1, p2, q2, r1, r2); + ::Geometry2D::get_closest_points_between_segments(p1, q1, p2, q2, r1, r2); Vector<Vector2> r; r.resize(2); r.set(0, r1); @@ -679,42 +676,42 @@ Vector<Vector2> _Geometry2D::get_closest_points_between_segments(const Vector2 & return r; } -Vector2 _Geometry2D::get_closest_point_to_segment(const Vector2 &p_point, const Vector2 &p_a, const Vector2 &p_b) { +Vector2 Geometry2D::get_closest_point_to_segment(const Vector2 &p_point, const Vector2 &p_a, const Vector2 &p_b) { Vector2 s[2] = { p_a, p_b }; - return Geometry2D::get_closest_point_to_segment(p_point, s); + return ::Geometry2D::get_closest_point_to_segment(p_point, s); } -Vector2 _Geometry2D::get_closest_point_to_segment_uncapped(const Vector2 &p_point, const Vector2 &p_a, const Vector2 &p_b) { +Vector2 Geometry2D::get_closest_point_to_segment_uncapped(const Vector2 &p_point, const Vector2 &p_a, const Vector2 &p_b) { Vector2 s[2] = { p_a, p_b }; - return Geometry2D::get_closest_point_to_segment_uncapped(p_point, s); + return ::Geometry2D::get_closest_point_to_segment_uncapped(p_point, s); } -bool _Geometry2D::point_is_inside_triangle(const Vector2 &s, const Vector2 &a, const Vector2 &b, const Vector2 &c) const { - return Geometry2D::is_point_in_triangle(s, a, b, c); +bool Geometry2D::point_is_inside_triangle(const Vector2 &s, const Vector2 &a, const Vector2 &b, const Vector2 &c) const { + return ::Geometry2D::is_point_in_triangle(s, a, b, c); } -bool _Geometry2D::is_polygon_clockwise(const Vector<Vector2> &p_polygon) { - return Geometry2D::is_polygon_clockwise(p_polygon); +bool Geometry2D::is_polygon_clockwise(const Vector<Vector2> &p_polygon) { + return ::Geometry2D::is_polygon_clockwise(p_polygon); } -bool _Geometry2D::is_point_in_polygon(const Point2 &p_point, const Vector<Vector2> &p_polygon) { - return Geometry2D::is_point_in_polygon(p_point, p_polygon); +bool Geometry2D::is_point_in_polygon(const Point2 &p_point, const Vector<Vector2> &p_polygon) { + return ::Geometry2D::is_point_in_polygon(p_point, p_polygon); } -Vector<int> _Geometry2D::triangulate_polygon(const Vector<Vector2> &p_polygon) { - return Geometry2D::triangulate_polygon(p_polygon); +Vector<int> Geometry2D::triangulate_polygon(const Vector<Vector2> &p_polygon) { + return ::Geometry2D::triangulate_polygon(p_polygon); } -Vector<int> _Geometry2D::triangulate_delaunay(const Vector<Vector2> &p_points) { - return Geometry2D::triangulate_delaunay(p_points); +Vector<int> Geometry2D::triangulate_delaunay(const Vector<Vector2> &p_points) { + return ::Geometry2D::triangulate_delaunay(p_points); } -Vector<Point2> _Geometry2D::convex_hull(const Vector<Point2> &p_points) { - return Geometry2D::convex_hull(p_points); +Vector<Point2> Geometry2D::convex_hull(const Vector<Point2> &p_points) { + return ::Geometry2D::convex_hull(p_points); } -Array _Geometry2D::merge_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) { - Vector<Vector<Point2>> polys = Geometry2D::merge_polygons(p_polygon_a, p_polygon_b); +Array Geometry2D::merge_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) { + Vector<Vector<Point2>> polys = ::Geometry2D::merge_polygons(p_polygon_a, p_polygon_b); Array ret; @@ -724,8 +721,8 @@ Array _Geometry2D::merge_polygons(const Vector<Vector2> &p_polygon_a, const Vect return ret; } -Array _Geometry2D::clip_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) { - Vector<Vector<Point2>> polys = Geometry2D::clip_polygons(p_polygon_a, p_polygon_b); +Array Geometry2D::clip_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) { + Vector<Vector<Point2>> polys = ::Geometry2D::clip_polygons(p_polygon_a, p_polygon_b); Array ret; @@ -735,8 +732,8 @@ Array _Geometry2D::clip_polygons(const Vector<Vector2> &p_polygon_a, const Vecto return ret; } -Array _Geometry2D::intersect_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) { - Vector<Vector<Point2>> polys = Geometry2D::intersect_polygons(p_polygon_a, p_polygon_b); +Array Geometry2D::intersect_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) { + Vector<Vector<Point2>> polys = ::Geometry2D::intersect_polygons(p_polygon_a, p_polygon_b); Array ret; @@ -746,8 +743,8 @@ Array _Geometry2D::intersect_polygons(const Vector<Vector2> &p_polygon_a, const return ret; } -Array _Geometry2D::exclude_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) { - Vector<Vector<Point2>> polys = Geometry2D::exclude_polygons(p_polygon_a, p_polygon_b); +Array Geometry2D::exclude_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) { + Vector<Vector<Point2>> polys = ::Geometry2D::exclude_polygons(p_polygon_a, p_polygon_b); Array ret; @@ -757,8 +754,8 @@ Array _Geometry2D::exclude_polygons(const Vector<Vector2> &p_polygon_a, const Ve return ret; } -Array _Geometry2D::clip_polyline_with_polygon(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon) { - Vector<Vector<Point2>> polys = Geometry2D::clip_polyline_with_polygon(p_polyline, p_polygon); +Array Geometry2D::clip_polyline_with_polygon(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon) { + Vector<Vector<Point2>> polys = ::Geometry2D::clip_polyline_with_polygon(p_polyline, p_polygon); Array ret; @@ -768,8 +765,8 @@ Array _Geometry2D::clip_polyline_with_polygon(const Vector<Vector2> &p_polyline, return ret; } -Array _Geometry2D::intersect_polyline_with_polygon(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon) { - Vector<Vector<Point2>> polys = Geometry2D::intersect_polyline_with_polygon(p_polyline, p_polygon); +Array Geometry2D::intersect_polyline_with_polygon(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon) { + Vector<Vector<Point2>> polys = ::Geometry2D::intersect_polyline_with_polygon(p_polyline, p_polygon); Array ret; @@ -779,8 +776,8 @@ Array _Geometry2D::intersect_polyline_with_polygon(const Vector<Vector2> &p_poly return ret; } -Array _Geometry2D::offset_polygon(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type) { - Vector<Vector<Point2>> polys = Geometry2D::offset_polygon(p_polygon, p_delta, Geometry2D::PolyJoinType(p_join_type)); +Array Geometry2D::offset_polygon(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type) { + Vector<Vector<Point2>> polys = ::Geometry2D::offset_polygon(p_polygon, p_delta, ::Geometry2D::PolyJoinType(p_join_type)); Array ret; @@ -790,8 +787,8 @@ Array _Geometry2D::offset_polygon(const Vector<Vector2> &p_polygon, real_t p_del return ret; } -Array _Geometry2D::offset_polyline(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) { - Vector<Vector<Point2>> polys = Geometry2D::offset_polyline(p_polygon, p_delta, Geometry2D::PolyJoinType(p_join_type), Geometry2D::PolyEndType(p_end_type)); +Array Geometry2D::offset_polyline(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) { + Vector<Vector<Point2>> polys = ::Geometry2D::offset_polyline(p_polygon, p_delta, ::Geometry2D::PolyJoinType(p_join_type), ::Geometry2D::PolyEndType(p_end_type)); Array ret; @@ -801,7 +798,7 @@ Array _Geometry2D::offset_polyline(const Vector<Vector2> &p_polygon, real_t p_de return ret; } -Dictionary _Geometry2D::make_atlas(const Vector<Size2> &p_rects) { +Dictionary Geometry2D::make_atlas(const Vector<Size2> &p_rects) { Dictionary ret; Vector<Size2i> rects; @@ -812,7 +809,7 @@ Dictionary _Geometry2D::make_atlas(const Vector<Size2> &p_rects) { Vector<Point2i> result; Size2i size; - Geometry2D::make_atlas(rects, result, size); + ::Geometry2D::make_atlas(rects, result, size); Size2 r_size = size; Vector<Point2> r_result; @@ -826,37 +823,37 @@ Dictionary _Geometry2D::make_atlas(const Vector<Size2> &p_rects) { return ret; } -void _Geometry2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("is_point_in_circle", "point", "circle_position", "circle_radius"), &_Geometry2D::is_point_in_circle); - ClassDB::bind_method(D_METHOD("segment_intersects_segment", "from_a", "to_a", "from_b", "to_b"), &_Geometry2D::segment_intersects_segment); - ClassDB::bind_method(D_METHOD("line_intersects_line", "from_a", "dir_a", "from_b", "dir_b"), &_Geometry2D::line_intersects_line); +void Geometry2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("is_point_in_circle", "point", "circle_position", "circle_radius"), &Geometry2D::is_point_in_circle); + ClassDB::bind_method(D_METHOD("segment_intersects_segment", "from_a", "to_a", "from_b", "to_b"), &Geometry2D::segment_intersects_segment); + ClassDB::bind_method(D_METHOD("line_intersects_line", "from_a", "dir_a", "from_b", "dir_b"), &Geometry2D::line_intersects_line); - ClassDB::bind_method(D_METHOD("get_closest_points_between_segments", "p1", "q1", "p2", "q2"), &_Geometry2D::get_closest_points_between_segments); + ClassDB::bind_method(D_METHOD("get_closest_points_between_segments", "p1", "q1", "p2", "q2"), &Geometry2D::get_closest_points_between_segments); - ClassDB::bind_method(D_METHOD("get_closest_point_to_segment", "point", "s1", "s2"), &_Geometry2D::get_closest_point_to_segment); + ClassDB::bind_method(D_METHOD("get_closest_point_to_segment", "point", "s1", "s2"), &Geometry2D::get_closest_point_to_segment); - ClassDB::bind_method(D_METHOD("get_closest_point_to_segment_uncapped", "point", "s1", "s2"), &_Geometry2D::get_closest_point_to_segment_uncapped); + ClassDB::bind_method(D_METHOD("get_closest_point_to_segment_uncapped", "point", "s1", "s2"), &Geometry2D::get_closest_point_to_segment_uncapped); - ClassDB::bind_method(D_METHOD("point_is_inside_triangle", "point", "a", "b", "c"), &_Geometry2D::point_is_inside_triangle); + ClassDB::bind_method(D_METHOD("point_is_inside_triangle", "point", "a", "b", "c"), &Geometry2D::point_is_inside_triangle); - ClassDB::bind_method(D_METHOD("is_polygon_clockwise", "polygon"), &_Geometry2D::is_polygon_clockwise); - ClassDB::bind_method(D_METHOD("is_point_in_polygon", "point", "polygon"), &_Geometry2D::is_point_in_polygon); - ClassDB::bind_method(D_METHOD("triangulate_polygon", "polygon"), &_Geometry2D::triangulate_polygon); - ClassDB::bind_method(D_METHOD("triangulate_delaunay", "points"), &_Geometry2D::triangulate_delaunay); - ClassDB::bind_method(D_METHOD("convex_hull", "points"), &_Geometry2D::convex_hull); + ClassDB::bind_method(D_METHOD("is_polygon_clockwise", "polygon"), &Geometry2D::is_polygon_clockwise); + ClassDB::bind_method(D_METHOD("is_point_in_polygon", "point", "polygon"), &Geometry2D::is_point_in_polygon); + ClassDB::bind_method(D_METHOD("triangulate_polygon", "polygon"), &Geometry2D::triangulate_polygon); + ClassDB::bind_method(D_METHOD("triangulate_delaunay", "points"), &Geometry2D::triangulate_delaunay); + ClassDB::bind_method(D_METHOD("convex_hull", "points"), &Geometry2D::convex_hull); - ClassDB::bind_method(D_METHOD("merge_polygons", "polygon_a", "polygon_b"), &_Geometry2D::merge_polygons); - ClassDB::bind_method(D_METHOD("clip_polygons", "polygon_a", "polygon_b"), &_Geometry2D::clip_polygons); - ClassDB::bind_method(D_METHOD("intersect_polygons", "polygon_a", "polygon_b"), &_Geometry2D::intersect_polygons); - ClassDB::bind_method(D_METHOD("exclude_polygons", "polygon_a", "polygon_b"), &_Geometry2D::exclude_polygons); + ClassDB::bind_method(D_METHOD("merge_polygons", "polygon_a", "polygon_b"), &Geometry2D::merge_polygons); + ClassDB::bind_method(D_METHOD("clip_polygons", "polygon_a", "polygon_b"), &Geometry2D::clip_polygons); + ClassDB::bind_method(D_METHOD("intersect_polygons", "polygon_a", "polygon_b"), &Geometry2D::intersect_polygons); + ClassDB::bind_method(D_METHOD("exclude_polygons", "polygon_a", "polygon_b"), &Geometry2D::exclude_polygons); - ClassDB::bind_method(D_METHOD("clip_polyline_with_polygon", "polyline", "polygon"), &_Geometry2D::clip_polyline_with_polygon); - ClassDB::bind_method(D_METHOD("intersect_polyline_with_polygon", "polyline", "polygon"), &_Geometry2D::intersect_polyline_with_polygon); + ClassDB::bind_method(D_METHOD("clip_polyline_with_polygon", "polyline", "polygon"), &Geometry2D::clip_polyline_with_polygon); + ClassDB::bind_method(D_METHOD("intersect_polyline_with_polygon", "polyline", "polygon"), &Geometry2D::intersect_polyline_with_polygon); - ClassDB::bind_method(D_METHOD("offset_polygon", "polygon", "delta", "join_type"), &_Geometry2D::offset_polygon, DEFVAL(JOIN_SQUARE)); - ClassDB::bind_method(D_METHOD("offset_polyline", "polyline", "delta", "join_type", "end_type"), &_Geometry2D::offset_polyline, DEFVAL(JOIN_SQUARE), DEFVAL(END_SQUARE)); + ClassDB::bind_method(D_METHOD("offset_polygon", "polygon", "delta", "join_type"), &Geometry2D::offset_polygon, DEFVAL(JOIN_SQUARE)); + ClassDB::bind_method(D_METHOD("offset_polyline", "polyline", "delta", "join_type", "end_type"), &Geometry2D::offset_polyline, DEFVAL(JOIN_SQUARE), DEFVAL(END_SQUARE)); - ClassDB::bind_method(D_METHOD("make_atlas", "sizes"), &_Geometry2D::make_atlas); + ClassDB::bind_method(D_METHOD("make_atlas", "sizes"), &Geometry2D::make_atlas); BIND_ENUM_CONSTANT(OPERATION_UNION); BIND_ENUM_CONSTANT(OPERATION_DIFFERENCE); @@ -874,29 +871,29 @@ void _Geometry2D::_bind_methods() { BIND_ENUM_CONSTANT(END_ROUND); } -////// _Geometry3D ////// +////// Geometry3D ////// -_Geometry3D *_Geometry3D::singleton = nullptr; +Geometry3D *Geometry3D::singleton = nullptr; -_Geometry3D *_Geometry3D::get_singleton() { +Geometry3D *Geometry3D::get_singleton() { return singleton; } -Vector<Plane> _Geometry3D::build_box_planes(const Vector3 &p_extents) { - return Geometry3D::build_box_planes(p_extents); +Vector<Plane> Geometry3D::build_box_planes(const Vector3 &p_extents) { + return ::Geometry3D::build_box_planes(p_extents); } -Vector<Plane> _Geometry3D::build_cylinder_planes(float p_radius, float p_height, int p_sides, Vector3::Axis p_axis) { - return Geometry3D::build_cylinder_planes(p_radius, p_height, p_sides, p_axis); +Vector<Plane> Geometry3D::build_cylinder_planes(float p_radius, float p_height, int p_sides, Vector3::Axis p_axis) { + return ::Geometry3D::build_cylinder_planes(p_radius, p_height, p_sides, p_axis); } -Vector<Plane> _Geometry3D::build_capsule_planes(float p_radius, float p_height, int p_sides, int p_lats, Vector3::Axis p_axis) { - return Geometry3D::build_capsule_planes(p_radius, p_height, p_sides, p_lats, p_axis); +Vector<Plane> Geometry3D::build_capsule_planes(float p_radius, float p_height, int p_sides, int p_lats, Vector3::Axis p_axis) { + return ::Geometry3D::build_capsule_planes(p_radius, p_height, p_sides, p_lats, p_axis); } -Vector<Vector3> _Geometry3D::get_closest_points_between_segments(const Vector3 &p1, const Vector3 &p2, const Vector3 &q1, const Vector3 &q2) { +Vector<Vector3> Geometry3D::get_closest_points_between_segments(const Vector3 &p1, const Vector3 &p2, const Vector3 &q1, const Vector3 &q2) { Vector3 r1, r2; - Geometry3D::get_closest_points_between_segments(p1, p2, q1, q2, r1, r2); + ::Geometry3D::get_closest_points_between_segments(p1, p2, q1, q2, r1, r2); Vector<Vector3> r; r.resize(2); r.set(0, r1); @@ -904,38 +901,38 @@ Vector<Vector3> _Geometry3D::get_closest_points_between_segments(const Vector3 & return r; } -Vector3 _Geometry3D::get_closest_point_to_segment(const Vector3 &p_point, const Vector3 &p_a, const Vector3 &p_b) { +Vector3 Geometry3D::get_closest_point_to_segment(const Vector3 &p_point, const Vector3 &p_a, const Vector3 &p_b) { Vector3 s[2] = { p_a, p_b }; - return Geometry3D::get_closest_point_to_segment(p_point, s); + return ::Geometry3D::get_closest_point_to_segment(p_point, s); } -Vector3 _Geometry3D::get_closest_point_to_segment_uncapped(const Vector3 &p_point, const Vector3 &p_a, const Vector3 &p_b) { +Vector3 Geometry3D::get_closest_point_to_segment_uncapped(const Vector3 &p_point, const Vector3 &p_a, const Vector3 &p_b) { Vector3 s[2] = { p_a, p_b }; - return Geometry3D::get_closest_point_to_segment_uncapped(p_point, s); + return ::Geometry3D::get_closest_point_to_segment_uncapped(p_point, s); } -Variant _Geometry3D::ray_intersects_triangle(const Vector3 &p_from, const Vector3 &p_dir, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2) { +Variant Geometry3D::ray_intersects_triangle(const Vector3 &p_from, const Vector3 &p_dir, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2) { Vector3 res; - if (Geometry3D::ray_intersects_triangle(p_from, p_dir, p_v0, p_v1, p_v2, &res)) { + if (::Geometry3D::ray_intersects_triangle(p_from, p_dir, p_v0, p_v1, p_v2, &res)) { return res; } else { return Variant(); } } -Variant _Geometry3D::segment_intersects_triangle(const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2) { +Variant Geometry3D::segment_intersects_triangle(const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2) { Vector3 res; - if (Geometry3D::segment_intersects_triangle(p_from, p_to, p_v0, p_v1, p_v2, &res)) { + if (::Geometry3D::segment_intersects_triangle(p_from, p_to, p_v0, p_v1, p_v2, &res)) { return res; } else { return Variant(); } } -Vector<Vector3> _Geometry3D::segment_intersects_sphere(const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_sphere_pos, real_t p_sphere_radius) { +Vector<Vector3> Geometry3D::segment_intersects_sphere(const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_sphere_pos, real_t p_sphere_radius) { Vector<Vector3> r; Vector3 res, norm; - if (!Geometry3D::segment_intersects_sphere(p_from, p_to, p_sphere_pos, p_sphere_radius, &res, &norm)) { + if (!::Geometry3D::segment_intersects_sphere(p_from, p_to, p_sphere_pos, p_sphere_radius, &res, &norm)) { return r; } @@ -945,10 +942,10 @@ Vector<Vector3> _Geometry3D::segment_intersects_sphere(const Vector3 &p_from, co return r; } -Vector<Vector3> _Geometry3D::segment_intersects_cylinder(const Vector3 &p_from, const Vector3 &p_to, float p_height, float p_radius) { +Vector<Vector3> Geometry3D::segment_intersects_cylinder(const Vector3 &p_from, const Vector3 &p_to, float p_height, float p_radius) { Vector<Vector3> r; Vector3 res, norm; - if (!Geometry3D::segment_intersects_cylinder(p_from, p_to, p_height, p_radius, &res, &norm)) { + if (!::Geometry3D::segment_intersects_cylinder(p_from, p_to, p_height, p_radius, &res, &norm)) { return r; } @@ -958,10 +955,10 @@ Vector<Vector3> _Geometry3D::segment_intersects_cylinder(const Vector3 &p_from, return r; } -Vector<Vector3> _Geometry3D::segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const Vector<Plane> &p_planes) { +Vector<Vector3> Geometry3D::segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const Vector<Plane> &p_planes) { Vector<Vector3> r; Vector3 res, norm; - if (!Geometry3D::segment_intersects_convex(p_from, p_to, p_planes.ptr(), p_planes.size(), &res, &norm)) { + if (!::Geometry3D::segment_intersects_convex(p_from, p_to, p_planes.ptr(), p_planes.size(), &res, &norm)) { return r; } @@ -971,33 +968,33 @@ Vector<Vector3> _Geometry3D::segment_intersects_convex(const Vector3 &p_from, co return r; } -Vector<Vector3> _Geometry3D::clip_polygon(const Vector<Vector3> &p_points, const Plane &p_plane) { - return Geometry3D::clip_polygon(p_points, p_plane); +Vector<Vector3> Geometry3D::clip_polygon(const Vector<Vector3> &p_points, const Plane &p_plane) { + return ::Geometry3D::clip_polygon(p_points, p_plane); } -void _Geometry3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("build_box_planes", "extents"), &_Geometry3D::build_box_planes); - ClassDB::bind_method(D_METHOD("build_cylinder_planes", "radius", "height", "sides", "axis"), &_Geometry3D::build_cylinder_planes, DEFVAL(Vector3::AXIS_Z)); - ClassDB::bind_method(D_METHOD("build_capsule_planes", "radius", "height", "sides", "lats", "axis"), &_Geometry3D::build_capsule_planes, DEFVAL(Vector3::AXIS_Z)); +void Geometry3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("build_box_planes", "extents"), &Geometry3D::build_box_planes); + ClassDB::bind_method(D_METHOD("build_cylinder_planes", "radius", "height", "sides", "axis"), &Geometry3D::build_cylinder_planes, DEFVAL(Vector3::AXIS_Z)); + ClassDB::bind_method(D_METHOD("build_capsule_planes", "radius", "height", "sides", "lats", "axis"), &Geometry3D::build_capsule_planes, DEFVAL(Vector3::AXIS_Z)); - ClassDB::bind_method(D_METHOD("get_closest_points_between_segments", "p1", "p2", "q1", "q2"), &_Geometry3D::get_closest_points_between_segments); + ClassDB::bind_method(D_METHOD("get_closest_points_between_segments", "p1", "p2", "q1", "q2"), &Geometry3D::get_closest_points_between_segments); - ClassDB::bind_method(D_METHOD("get_closest_point_to_segment", "point", "s1", "s2"), &_Geometry3D::get_closest_point_to_segment); + ClassDB::bind_method(D_METHOD("get_closest_point_to_segment", "point", "s1", "s2"), &Geometry3D::get_closest_point_to_segment); - ClassDB::bind_method(D_METHOD("get_closest_point_to_segment_uncapped", "point", "s1", "s2"), &_Geometry3D::get_closest_point_to_segment_uncapped); + ClassDB::bind_method(D_METHOD("get_closest_point_to_segment_uncapped", "point", "s1", "s2"), &Geometry3D::get_closest_point_to_segment_uncapped); - ClassDB::bind_method(D_METHOD("ray_intersects_triangle", "from", "dir", "a", "b", "c"), &_Geometry3D::ray_intersects_triangle); - ClassDB::bind_method(D_METHOD("segment_intersects_triangle", "from", "to", "a", "b", "c"), &_Geometry3D::segment_intersects_triangle); - ClassDB::bind_method(D_METHOD("segment_intersects_sphere", "from", "to", "sphere_position", "sphere_radius"), &_Geometry3D::segment_intersects_sphere); - ClassDB::bind_method(D_METHOD("segment_intersects_cylinder", "from", "to", "height", "radius"), &_Geometry3D::segment_intersects_cylinder); - ClassDB::bind_method(D_METHOD("segment_intersects_convex", "from", "to", "planes"), &_Geometry3D::segment_intersects_convex); + ClassDB::bind_method(D_METHOD("ray_intersects_triangle", "from", "dir", "a", "b", "c"), &Geometry3D::ray_intersects_triangle); + ClassDB::bind_method(D_METHOD("segment_intersects_triangle", "from", "to", "a", "b", "c"), &Geometry3D::segment_intersects_triangle); + ClassDB::bind_method(D_METHOD("segment_intersects_sphere", "from", "to", "sphere_position", "sphere_radius"), &Geometry3D::segment_intersects_sphere); + ClassDB::bind_method(D_METHOD("segment_intersects_cylinder", "from", "to", "height", "radius"), &Geometry3D::segment_intersects_cylinder); + ClassDB::bind_method(D_METHOD("segment_intersects_convex", "from", "to", "planes"), &Geometry3D::segment_intersects_convex); - ClassDB::bind_method(D_METHOD("clip_polygon", "points", "plane"), &_Geometry3D::clip_polygon); + ClassDB::bind_method(D_METHOD("clip_polygon", "points", "plane"), &Geometry3D::clip_polygon); } -////// _File ////// +////// File ////// -Error _File::open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key) { +Error File::open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key) { Error err = open(p_path, p_mode_flags); if (err) { return err; @@ -1014,7 +1011,7 @@ Error _File::open_encrypted(const String &p_path, ModeFlags p_mode_flags, const return OK; } -Error _File::open_encrypted_pass(const String &p_path, ModeFlags p_mode_flags, const String &p_pass) { +Error File::open_encrypted_pass(const String &p_path, ModeFlags p_mode_flags, const String &p_pass) { Error err = open(p_path, p_mode_flags); if (err) { return err; @@ -1032,7 +1029,7 @@ Error _File::open_encrypted_pass(const String &p_path, ModeFlags p_mode_flags, c return OK; } -Error _File::open_compressed(const String &p_path, ModeFlags p_mode_flags, CompressionMode p_compress_mode) { +Error File::open_compressed(const String &p_path, ModeFlags p_mode_flags, CompressionMode p_compress_mode) { FileAccessCompressed *fac = memnew(FileAccessCompressed); fac->configure("GCPF", (Compression::Mode)p_compress_mode); @@ -1048,7 +1045,7 @@ Error _File::open_compressed(const String &p_path, ModeFlags p_mode_flags, Compr return OK; } -Error _File::open(const String &p_path, ModeFlags p_mode_flags) { +Error File::open(const String &p_path, ModeFlags p_mode_flags) { close(); Error err; f = FileAccess::open(p_path, p_mode_flags, &err); @@ -1058,94 +1055,94 @@ Error _File::open(const String &p_path, ModeFlags p_mode_flags) { return err; } -void _File::flush() { +void File::flush() { ERR_FAIL_COND_MSG(!f, "File must be opened before flushing."); f->flush(); } -void _File::close() { +void File::close() { if (f) { memdelete(f); } f = nullptr; } -bool _File::is_open() const { +bool File::is_open() const { return f != nullptr; } -String _File::get_path() const { +String File::get_path() const { ERR_FAIL_COND_V_MSG(!f, "", "File must be opened before use."); return f->get_path(); } -String _File::get_path_absolute() const { +String File::get_path_absolute() const { ERR_FAIL_COND_V_MSG(!f, "", "File must be opened before use."); return f->get_path_absolute(); } -void _File::seek(int64_t p_position) { +void File::seek(int64_t p_position) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); ERR_FAIL_COND_MSG(p_position < 0, "Seek position must be a positive integer."); f->seek(p_position); } -void _File::seek_end(int64_t p_position) { +void File::seek_end(int64_t p_position) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->seek_end(p_position); } -uint64_t _File::get_position() const { +uint64_t File::get_position() const { ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); return f->get_position(); } -uint64_t _File::get_length() const { +uint64_t File::get_length() const { ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); return f->get_length(); } -bool _File::eof_reached() const { +bool File::eof_reached() const { ERR_FAIL_COND_V_MSG(!f, false, "File must be opened before use."); return f->eof_reached(); } -uint8_t _File::get_8() const { +uint8_t File::get_8() const { ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); return f->get_8(); } -uint16_t _File::get_16() const { +uint16_t File::get_16() const { ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); return f->get_16(); } -uint32_t _File::get_32() const { +uint32_t File::get_32() const { ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); return f->get_32(); } -uint64_t _File::get_64() const { +uint64_t File::get_64() const { ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); return f->get_64(); } -float _File::get_float() const { +float File::get_float() const { ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); return f->get_float(); } -double _File::get_double() const { +double File::get_double() const { ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); return f->get_double(); } -real_t _File::get_real() const { +real_t File::get_real() const { ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); return f->get_real(); } -Vector<uint8_t> _File::get_buffer(int64_t p_length) const { +Vector<uint8_t> File::get_buffer(int64_t p_length) const { Vector<uint8_t> data; ERR_FAIL_COND_V_MSG(!f, data, "File must be opened before use."); @@ -1167,7 +1164,7 @@ Vector<uint8_t> _File::get_buffer(int64_t p_length) const { return data; } -String _File::get_as_text() const { +String File::get_as_text() const { ERR_FAIL_COND_V_MSG(!f, String(), "File must be opened before use."); String text; @@ -1186,20 +1183,20 @@ String _File::get_as_text() const { return text; } -String _File::get_md5(const String &p_path) const { +String File::get_md5(const String &p_path) const { return FileAccess::get_md5(p_path); } -String _File::get_sha256(const String &p_path) const { +String File::get_sha256(const String &p_path) const { return FileAccess::get_sha256(p_path); } -String _File::get_line() const { +String File::get_line() const { ERR_FAIL_COND_V_MSG(!f, String(), "File must be opened before use."); return f->get_line(); } -Vector<String> _File::get_csv_line(const String &p_delim) const { +Vector<String> File::get_csv_line(const String &p_delim) const { ERR_FAIL_COND_V_MSG(!f, Vector<String>(), "File must be opened before use."); return f->get_csv_line(p_delim); } @@ -1209,95 +1206,95 @@ Vector<String> _File::get_csv_line(const String &p_delim) const { * These flags get reset to false (little endian) on each open */ -void _File::set_big_endian(bool p_big_endian) { +void File::set_big_endian(bool p_big_endian) { big_endian = p_big_endian; if (f) { f->set_big_endian(p_big_endian); } } -bool _File::is_big_endian() { +bool File::is_big_endian() { return big_endian; } -Error _File::get_error() const { +Error File::get_error() const { if (!f) { return ERR_UNCONFIGURED; } return f->get_error(); } -void _File::store_8(uint8_t p_dest) { +void File::store_8(uint8_t p_dest) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->store_8(p_dest); } -void _File::store_16(uint16_t p_dest) { +void File::store_16(uint16_t p_dest) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->store_16(p_dest); } -void _File::store_32(uint32_t p_dest) { +void File::store_32(uint32_t p_dest) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->store_32(p_dest); } -void _File::store_64(uint64_t p_dest) { +void File::store_64(uint64_t p_dest) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->store_64(p_dest); } -void _File::store_float(float p_dest) { +void File::store_float(float p_dest) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->store_float(p_dest); } -void _File::store_double(double p_dest) { +void File::store_double(double p_dest) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->store_double(p_dest); } -void _File::store_real(real_t p_real) { +void File::store_real(real_t p_real) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->store_real(p_real); } -void _File::store_string(const String &p_string) { +void File::store_string(const String &p_string) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->store_string(p_string); } -void _File::store_pascal_string(const String &p_string) { +void File::store_pascal_string(const String &p_string) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->store_pascal_string(p_string); } -String _File::get_pascal_string() { +String File::get_pascal_string() { ERR_FAIL_COND_V_MSG(!f, "", "File must be opened before use."); return f->get_pascal_string(); } -void _File::store_line(const String &p_string) { +void File::store_line(const String &p_string) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->store_line(p_string); } -void _File::store_csv_line(const Vector<String> &p_values, const String &p_delim) { +void File::store_csv_line(const Vector<String> &p_values, const String &p_delim) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); f->store_csv_line(p_values, p_delim); } -void _File::store_buffer(const Vector<uint8_t> &p_buffer) { +void File::store_buffer(const Vector<uint8_t> &p_buffer) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); uint64_t len = p_buffer.size(); @@ -1310,11 +1307,11 @@ void _File::store_buffer(const Vector<uint8_t> &p_buffer) { f->store_buffer(&r[0], len); } -bool _File::file_exists(const String &p_name) const { +bool File::file_exists(const String &p_name) const { return FileAccess::exists(p_name); } -void _File::store_var(const Variant &p_var, bool p_full_objects) { +void File::store_var(const Variant &p_var, bool p_full_objects) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); int len; Error err = encode_variant(p_var, nullptr, len, p_full_objects); @@ -1331,7 +1328,7 @@ void _File::store_var(const Variant &p_var, bool p_full_objects) { store_buffer(buff); } -Variant _File::get_var(bool p_allow_objects) const { +Variant File::get_var(bool p_allow_objects) const { ERR_FAIL_COND_V_MSG(!f, Variant(), "File must be opened before use."); uint32_t len = get_32(); Vector<uint8_t> buff = get_buffer(len); @@ -1346,62 +1343,62 @@ Variant _File::get_var(bool p_allow_objects) const { return v; } -uint64_t _File::get_modified_time(const String &p_file) const { +uint64_t File::get_modified_time(const String &p_file) const { return FileAccess::get_modified_time(p_file); } -void _File::_bind_methods() { - ClassDB::bind_method(D_METHOD("open_encrypted", "path", "mode_flags", "key"), &_File::open_encrypted); - ClassDB::bind_method(D_METHOD("open_encrypted_with_pass", "path", "mode_flags", "pass"), &_File::open_encrypted_pass); - ClassDB::bind_method(D_METHOD("open_compressed", "path", "mode_flags", "compression_mode"), &_File::open_compressed, DEFVAL(0)); - - ClassDB::bind_method(D_METHOD("open", "path", "flags"), &_File::open); - ClassDB::bind_method(D_METHOD("flush"), &_File::flush); - ClassDB::bind_method(D_METHOD("close"), &_File::close); - ClassDB::bind_method(D_METHOD("get_path"), &_File::get_path); - ClassDB::bind_method(D_METHOD("get_path_absolute"), &_File::get_path_absolute); - ClassDB::bind_method(D_METHOD("is_open"), &_File::is_open); - ClassDB::bind_method(D_METHOD("seek", "position"), &_File::seek); - ClassDB::bind_method(D_METHOD("seek_end", "position"), &_File::seek_end, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_position"), &_File::get_position); - ClassDB::bind_method(D_METHOD("get_length"), &_File::get_length); - ClassDB::bind_method(D_METHOD("eof_reached"), &_File::eof_reached); - ClassDB::bind_method(D_METHOD("get_8"), &_File::get_8); - ClassDB::bind_method(D_METHOD("get_16"), &_File::get_16); - ClassDB::bind_method(D_METHOD("get_32"), &_File::get_32); - ClassDB::bind_method(D_METHOD("get_64"), &_File::get_64); - ClassDB::bind_method(D_METHOD("get_float"), &_File::get_float); - ClassDB::bind_method(D_METHOD("get_double"), &_File::get_double); - ClassDB::bind_method(D_METHOD("get_real"), &_File::get_real); - ClassDB::bind_method(D_METHOD("get_buffer", "length"), &_File::get_buffer); - ClassDB::bind_method(D_METHOD("get_line"), &_File::get_line); - ClassDB::bind_method(D_METHOD("get_csv_line", "delim"), &_File::get_csv_line, DEFVAL(",")); - ClassDB::bind_method(D_METHOD("get_as_text"), &_File::get_as_text); - ClassDB::bind_method(D_METHOD("get_md5", "path"), &_File::get_md5); - ClassDB::bind_method(D_METHOD("get_sha256", "path"), &_File::get_sha256); - ClassDB::bind_method(D_METHOD("is_big_endian"), &_File::is_big_endian); - ClassDB::bind_method(D_METHOD("set_big_endian", "big_endian"), &_File::set_big_endian); - ClassDB::bind_method(D_METHOD("get_error"), &_File::get_error); - ClassDB::bind_method(D_METHOD("get_var", "allow_objects"), &_File::get_var, DEFVAL(false)); - - ClassDB::bind_method(D_METHOD("store_8", "value"), &_File::store_8); - ClassDB::bind_method(D_METHOD("store_16", "value"), &_File::store_16); - ClassDB::bind_method(D_METHOD("store_32", "value"), &_File::store_32); - ClassDB::bind_method(D_METHOD("store_64", "value"), &_File::store_64); - ClassDB::bind_method(D_METHOD("store_float", "value"), &_File::store_float); - ClassDB::bind_method(D_METHOD("store_double", "value"), &_File::store_double); - ClassDB::bind_method(D_METHOD("store_real", "value"), &_File::store_real); - ClassDB::bind_method(D_METHOD("store_buffer", "buffer"), &_File::store_buffer); - ClassDB::bind_method(D_METHOD("store_line", "line"), &_File::store_line); - ClassDB::bind_method(D_METHOD("store_csv_line", "values", "delim"), &_File::store_csv_line, DEFVAL(",")); - ClassDB::bind_method(D_METHOD("store_string", "string"), &_File::store_string); - ClassDB::bind_method(D_METHOD("store_var", "value", "full_objects"), &_File::store_var, DEFVAL(false)); - - ClassDB::bind_method(D_METHOD("store_pascal_string", "string"), &_File::store_pascal_string); - ClassDB::bind_method(D_METHOD("get_pascal_string"), &_File::get_pascal_string); - - ClassDB::bind_method(D_METHOD("file_exists", "path"), &_File::file_exists); - ClassDB::bind_method(D_METHOD("get_modified_time", "file"), &_File::get_modified_time); +void File::_bind_methods() { + ClassDB::bind_method(D_METHOD("open_encrypted", "path", "mode_flags", "key"), &File::open_encrypted); + ClassDB::bind_method(D_METHOD("open_encrypted_with_pass", "path", "mode_flags", "pass"), &File::open_encrypted_pass); + ClassDB::bind_method(D_METHOD("open_compressed", "path", "mode_flags", "compression_mode"), &File::open_compressed, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("open", "path", "flags"), &File::open); + ClassDB::bind_method(D_METHOD("flush"), &File::flush); + ClassDB::bind_method(D_METHOD("close"), &File::close); + ClassDB::bind_method(D_METHOD("get_path"), &File::get_path); + ClassDB::bind_method(D_METHOD("get_path_absolute"), &File::get_path_absolute); + ClassDB::bind_method(D_METHOD("is_open"), &File::is_open); + ClassDB::bind_method(D_METHOD("seek", "position"), &File::seek); + ClassDB::bind_method(D_METHOD("seek_end", "position"), &File::seek_end, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_position"), &File::get_position); + ClassDB::bind_method(D_METHOD("get_length"), &File::get_length); + ClassDB::bind_method(D_METHOD("eof_reached"), &File::eof_reached); + ClassDB::bind_method(D_METHOD("get_8"), &File::get_8); + ClassDB::bind_method(D_METHOD("get_16"), &File::get_16); + ClassDB::bind_method(D_METHOD("get_32"), &File::get_32); + ClassDB::bind_method(D_METHOD("get_64"), &File::get_64); + ClassDB::bind_method(D_METHOD("get_float"), &File::get_float); + ClassDB::bind_method(D_METHOD("get_double"), &File::get_double); + ClassDB::bind_method(D_METHOD("get_real"), &File::get_real); + ClassDB::bind_method(D_METHOD("get_buffer", "length"), &File::get_buffer); + ClassDB::bind_method(D_METHOD("get_line"), &File::get_line); + ClassDB::bind_method(D_METHOD("get_csv_line", "delim"), &File::get_csv_line, DEFVAL(",")); + ClassDB::bind_method(D_METHOD("get_as_text"), &File::get_as_text); + ClassDB::bind_method(D_METHOD("get_md5", "path"), &File::get_md5); + ClassDB::bind_method(D_METHOD("get_sha256", "path"), &File::get_sha256); + ClassDB::bind_method(D_METHOD("is_big_endian"), &File::is_big_endian); + ClassDB::bind_method(D_METHOD("set_big_endian", "big_endian"), &File::set_big_endian); + ClassDB::bind_method(D_METHOD("get_error"), &File::get_error); + ClassDB::bind_method(D_METHOD("get_var", "allow_objects"), &File::get_var, DEFVAL(false)); + + ClassDB::bind_method(D_METHOD("store_8", "value"), &File::store_8); + ClassDB::bind_method(D_METHOD("store_16", "value"), &File::store_16); + ClassDB::bind_method(D_METHOD("store_32", "value"), &File::store_32); + ClassDB::bind_method(D_METHOD("store_64", "value"), &File::store_64); + ClassDB::bind_method(D_METHOD("store_float", "value"), &File::store_float); + ClassDB::bind_method(D_METHOD("store_double", "value"), &File::store_double); + ClassDB::bind_method(D_METHOD("store_real", "value"), &File::store_real); + ClassDB::bind_method(D_METHOD("store_buffer", "buffer"), &File::store_buffer); + ClassDB::bind_method(D_METHOD("store_line", "line"), &File::store_line); + ClassDB::bind_method(D_METHOD("store_csv_line", "values", "delim"), &File::store_csv_line, DEFVAL(",")); + ClassDB::bind_method(D_METHOD("store_string", "string"), &File::store_string); + ClassDB::bind_method(D_METHOD("store_var", "value", "full_objects"), &File::store_var, DEFVAL(false)); + + ClassDB::bind_method(D_METHOD("store_pascal_string", "string"), &File::store_pascal_string); + ClassDB::bind_method(D_METHOD("get_pascal_string"), &File::get_pascal_string); + + ClassDB::bind_method(D_METHOD("file_exists", "path"), &File::file_exists); + ClassDB::bind_method(D_METHOD("get_modified_time", "file"), &File::get_modified_time); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "big_endian"), "set_big_endian", "is_big_endian"); @@ -1416,15 +1413,15 @@ void _File::_bind_methods() { BIND_ENUM_CONSTANT(COMPRESSION_GZIP); } -_File::~_File() { +File::~File() { if (f) { memdelete(f); } } -////// _Directory ////// +////// Directory ////// -Error _Directory::open(const String &p_path) { +Error Directory::open(const String &p_path) { Error err; DirAccess *alt = DirAccess::open(p_path, &err); @@ -1440,11 +1437,11 @@ Error _Directory::open(const String &p_path) { return OK; } -bool _Directory::is_open() const { +bool Directory::is_open() const { return d && dir_open; } -Error _Directory::list_dir_begin(bool p_show_navigational, bool p_show_hidden) { +Error Directory::list_dir_begin(bool p_show_navigational, bool p_show_hidden) { ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use."); _list_skip_navigational = !p_show_navigational; @@ -1453,7 +1450,7 @@ Error _Directory::list_dir_begin(bool p_show_navigational, bool p_show_hidden) { return d->list_dir_begin(); } -String _Directory::get_next() { +String Directory::get_next() { ERR_FAIL_COND_V_MSG(!is_open(), "", "Directory must be opened before use."); String next = d->get_next(); @@ -1463,32 +1460,32 @@ String _Directory::get_next() { return next; } -bool _Directory::current_is_dir() const { +bool Directory::current_is_dir() const { ERR_FAIL_COND_V_MSG(!is_open(), false, "Directory must be opened before use."); return d->current_is_dir(); } -void _Directory::list_dir_end() { +void Directory::list_dir_end() { ERR_FAIL_COND_MSG(!is_open(), "Directory must be opened before use."); d->list_dir_end(); } -int _Directory::get_drive_count() { +int Directory::get_drive_count() { ERR_FAIL_COND_V_MSG(!is_open(), 0, "Directory must be opened before use."); return d->get_drive_count(); } -String _Directory::get_drive(int p_drive) { +String Directory::get_drive(int p_drive) { ERR_FAIL_COND_V_MSG(!is_open(), "", "Directory must be opened before use."); return d->get_drive(p_drive); } -int _Directory::get_current_drive() { +int Directory::get_current_drive() { ERR_FAIL_COND_V_MSG(!is_open(), 0, "Directory must be opened before use."); return d->get_current_drive(); } -Error _Directory::change_dir(String p_dir) { +Error Directory::change_dir(String p_dir) { ERR_FAIL_COND_V_MSG(!d, ERR_UNCONFIGURED, "Directory is not configured properly."); Error err = d->change_dir(p_dir); @@ -1500,12 +1497,12 @@ Error _Directory::change_dir(String p_dir) { return OK; } -String _Directory::get_current_dir() { +String Directory::get_current_dir() { ERR_FAIL_COND_V_MSG(!is_open(), "", "Directory must be opened before use."); return d->get_current_dir(); } -Error _Directory::make_dir(String p_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()) { DirAccess *d = DirAccess::create_for_path(p_dir); @@ -1516,7 +1513,7 @@ Error _Directory::make_dir(String p_dir) { return d->make_dir(p_dir); } -Error _Directory::make_dir_recursive(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()) { DirAccess *d = DirAccess::create_for_path(p_dir); @@ -1527,7 +1524,7 @@ Error _Directory::make_dir_recursive(String p_dir) { return d->make_dir_recursive(p_dir); } -bool _Directory::file_exists(String p_file) { +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()) { return FileAccess::exists(p_file); @@ -1536,7 +1533,7 @@ bool _Directory::file_exists(String p_file) { return d->file_exists(p_file); } -bool _Directory::dir_exists(String p_dir) { +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()) { DirAccess *d = DirAccess::create_for_path(p_dir); @@ -1548,31 +1545,33 @@ bool _Directory::dir_exists(String p_dir) { return d->dir_exists(p_dir); } -uint64_t _Directory::get_space_left() { +uint64_t Directory::get_space_left() { ERR_FAIL_COND_V_MSG(!d, 0, "Directory must be opened before use."); return d->get_space_left() / 1024 * 1024; // Truncate to closest MiB. } -Error _Directory::copy(String p_from, String p_to) { +Error Directory::copy(String p_from, String p_to) { ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use."); return d->copy(p_from, p_to); } -Error _Directory::rename(String p_from, String p_to) { +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()) { DirAccess *d = DirAccess::create_for_path(p_from); - ERR_FAIL_COND_V_MSG(!d->file_exists(p_from), ERR_DOES_NOT_EXIST, "File does not exist."); + 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); memdelete(d); return err; } - ERR_FAIL_COND_V_MSG(!d->file_exists(p_from), ERR_DOES_NOT_EXIST, "File does not exist."); + 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."); return d->rename(p_from, p_to); } -Error _Directory::remove(String p_name) { +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()) { DirAccess *d = DirAccess::create_for_path(p_name); @@ -1584,47 +1583,47 @@ Error _Directory::remove(String p_name) { return d->remove(p_name); } -void _Directory::_bind_methods() { - ClassDB::bind_method(D_METHOD("open", "path"), &_Directory::open); - ClassDB::bind_method(D_METHOD("list_dir_begin", "show_navigational", "show_hidden"), &_Directory::list_dir_begin, DEFVAL(false), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("get_next"), &_Directory::get_next); - ClassDB::bind_method(D_METHOD("current_is_dir"), &_Directory::current_is_dir); - ClassDB::bind_method(D_METHOD("list_dir_end"), &_Directory::list_dir_end); - ClassDB::bind_method(D_METHOD("get_drive_count"), &_Directory::get_drive_count); - ClassDB::bind_method(D_METHOD("get_drive", "idx"), &_Directory::get_drive); - ClassDB::bind_method(D_METHOD("get_current_drive"), &_Directory::get_current_drive); - ClassDB::bind_method(D_METHOD("change_dir", "todir"), &_Directory::change_dir); - ClassDB::bind_method(D_METHOD("get_current_dir"), &_Directory::get_current_dir); - ClassDB::bind_method(D_METHOD("make_dir", "path"), &_Directory::make_dir); - ClassDB::bind_method(D_METHOD("make_dir_recursive", "path"), &_Directory::make_dir_recursive); - ClassDB::bind_method(D_METHOD("file_exists", "path"), &_Directory::file_exists); - ClassDB::bind_method(D_METHOD("dir_exists", "path"), &_Directory::dir_exists); - //ClassDB::bind_method(D_METHOD("get_modified_time","file"),&_Directory::get_modified_time); - ClassDB::bind_method(D_METHOD("get_space_left"), &_Directory::get_space_left); - ClassDB::bind_method(D_METHOD("copy", "from", "to"), &_Directory::copy); - ClassDB::bind_method(D_METHOD("rename", "from", "to"), &_Directory::rename); - ClassDB::bind_method(D_METHOD("remove", "path"), &_Directory::remove); -} - -_Directory::_Directory() { +void Directory::_bind_methods() { + ClassDB::bind_method(D_METHOD("open", "path"), &Directory::open); + ClassDB::bind_method(D_METHOD("list_dir_begin", "show_navigational", "show_hidden"), &Directory::list_dir_begin, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_next"), &Directory::get_next); + ClassDB::bind_method(D_METHOD("current_is_dir"), &Directory::current_is_dir); + ClassDB::bind_method(D_METHOD("list_dir_end"), &Directory::list_dir_end); + ClassDB::bind_method(D_METHOD("get_drive_count"), &Directory::get_drive_count); + ClassDB::bind_method(D_METHOD("get_drive", "idx"), &Directory::get_drive); + ClassDB::bind_method(D_METHOD("get_current_drive"), &Directory::get_current_drive); + ClassDB::bind_method(D_METHOD("change_dir", "todir"), &Directory::change_dir); + ClassDB::bind_method(D_METHOD("get_current_dir"), &Directory::get_current_dir); + ClassDB::bind_method(D_METHOD("make_dir", "path"), &Directory::make_dir); + ClassDB::bind_method(D_METHOD("make_dir_recursive", "path"), &Directory::make_dir_recursive); + ClassDB::bind_method(D_METHOD("file_exists", "path"), &Directory::file_exists); + ClassDB::bind_method(D_METHOD("dir_exists", "path"), &Directory::dir_exists); + //ClassDB::bind_method(D_METHOD("get_modified_time","file"),&Directory::get_modified_time); + ClassDB::bind_method(D_METHOD("get_space_left"), &Directory::get_space_left); + ClassDB::bind_method(D_METHOD("copy", "from", "to"), &Directory::copy); + ClassDB::bind_method(D_METHOD("rename", "from", "to"), &Directory::rename); + ClassDB::bind_method(D_METHOD("remove", "path"), &Directory::remove); +} + +Directory::Directory() { d = DirAccess::create(DirAccess::ACCESS_RESOURCES); } -_Directory::~_Directory() { +Directory::~Directory() { if (d) { memdelete(d); } } -////// _Marshalls ////// +////// Marshalls ////// -_Marshalls *_Marshalls::singleton = nullptr; +Marshalls *Marshalls::singleton = nullptr; -_Marshalls *_Marshalls::get_singleton() { +Marshalls *Marshalls::get_singleton() { return singleton; } -String _Marshalls::variant_to_base64(const Variant &p_var, bool p_full_objects) { +String Marshalls::variant_to_base64(const Variant &p_var, bool p_full_objects) { int len; Error err = encode_variant(p_var, nullptr, len, p_full_objects); ERR_FAIL_COND_V_MSG(err != OK, "", "Error when trying to encode Variant."); @@ -1642,7 +1641,7 @@ String _Marshalls::variant_to_base64(const Variant &p_var, bool p_full_objects) return ret; } -Variant _Marshalls::base64_to_variant(const String &p_str, bool p_allow_objects) { +Variant Marshalls::base64_to_variant(const String &p_str, bool p_allow_objects) { int strlen = p_str.length(); CharString cstr = p_str.ascii(); @@ -1660,13 +1659,13 @@ Variant _Marshalls::base64_to_variant(const String &p_str, bool p_allow_objects) return v; } -String _Marshalls::raw_to_base64(const Vector<uint8_t> &p_arr) { +String Marshalls::raw_to_base64(const Vector<uint8_t> &p_arr) { String ret = CryptoCore::b64_encode_str(p_arr.ptr(), p_arr.size()); ERR_FAIL_COND_V(ret == "", ret); return ret; } -Vector<uint8_t> _Marshalls::base64_to_raw(const String &p_str) { +Vector<uint8_t> Marshalls::base64_to_raw(const String &p_str) { int strlen = p_str.length(); CharString cstr = p_str.ascii(); @@ -1683,14 +1682,14 @@ Vector<uint8_t> _Marshalls::base64_to_raw(const String &p_str) { return buf; } -String _Marshalls::utf8_to_base64(const String &p_str) { +String Marshalls::utf8_to_base64(const String &p_str) { CharString cstr = p_str.utf8(); String ret = CryptoCore::b64_encode_str((unsigned char *)cstr.get_data(), cstr.length()); ERR_FAIL_COND_V(ret == "", ret); return ret; } -String _Marshalls::base64_to_utf8(const String &p_str) { +String Marshalls::base64_to_utf8(const String &p_str) { int strlen = p_str.length(); CharString cstr = p_str.ascii(); @@ -1707,62 +1706,62 @@ String _Marshalls::base64_to_utf8(const String &p_str) { return ret; } -void _Marshalls::_bind_methods() { - ClassDB::bind_method(D_METHOD("variant_to_base64", "variant", "full_objects"), &_Marshalls::variant_to_base64, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("base64_to_variant", "base64_str", "allow_objects"), &_Marshalls::base64_to_variant, DEFVAL(false)); +void Marshalls::_bind_methods() { + ClassDB::bind_method(D_METHOD("variant_to_base64", "variant", "full_objects"), &Marshalls::variant_to_base64, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("base64_to_variant", "base64_str", "allow_objects"), &Marshalls::base64_to_variant, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("raw_to_base64", "array"), &_Marshalls::raw_to_base64); - ClassDB::bind_method(D_METHOD("base64_to_raw", "base64_str"), &_Marshalls::base64_to_raw); + ClassDB::bind_method(D_METHOD("raw_to_base64", "array"), &Marshalls::raw_to_base64); + ClassDB::bind_method(D_METHOD("base64_to_raw", "base64_str"), &Marshalls::base64_to_raw); - ClassDB::bind_method(D_METHOD("utf8_to_base64", "utf8_str"), &_Marshalls::utf8_to_base64); - ClassDB::bind_method(D_METHOD("base64_to_utf8", "base64_str"), &_Marshalls::base64_to_utf8); + ClassDB::bind_method(D_METHOD("utf8_to_base64", "utf8_str"), &Marshalls::utf8_to_base64); + ClassDB::bind_method(D_METHOD("base64_to_utf8", "base64_str"), &Marshalls::base64_to_utf8); } -////// _Semaphore ////// +////// Semaphore ////// -void _Semaphore::wait() { +void Semaphore::wait() { semaphore.wait(); } -Error _Semaphore::try_wait() { +Error Semaphore::try_wait() { return semaphore.try_wait() ? OK : ERR_BUSY; } -void _Semaphore::post() { +void Semaphore::post() { semaphore.post(); } -void _Semaphore::_bind_methods() { - ClassDB::bind_method(D_METHOD("wait"), &_Semaphore::wait); - ClassDB::bind_method(D_METHOD("try_wait"), &_Semaphore::try_wait); - ClassDB::bind_method(D_METHOD("post"), &_Semaphore::post); +void Semaphore::_bind_methods() { + ClassDB::bind_method(D_METHOD("wait"), &Semaphore::wait); + ClassDB::bind_method(D_METHOD("try_wait"), &Semaphore::try_wait); + ClassDB::bind_method(D_METHOD("post"), &Semaphore::post); } -////// _Mutex ////// +////// Mutex ////// -void _Mutex::lock() { +void Mutex::lock() { mutex.lock(); } -Error _Mutex::try_lock() { +Error Mutex::try_lock() { return mutex.try_lock(); } -void _Mutex::unlock() { +void Mutex::unlock() { mutex.unlock(); } -void _Mutex::_bind_methods() { - ClassDB::bind_method(D_METHOD("lock"), &_Mutex::lock); - ClassDB::bind_method(D_METHOD("try_lock"), &_Mutex::try_lock); - ClassDB::bind_method(D_METHOD("unlock"), &_Mutex::unlock); +void Mutex::_bind_methods() { + ClassDB::bind_method(D_METHOD("lock"), &Mutex::lock); + ClassDB::bind_method(D_METHOD("try_lock"), &Mutex::try_lock); + ClassDB::bind_method(D_METHOD("unlock"), &Mutex::unlock); } -////// _Thread ////// +////// Thread ////// -void _Thread::_start_func(void *ud) { - Ref<_Thread> *tud = (Ref<_Thread> *)ud; - Ref<_Thread> t = *tud; +void Thread::_start_func(void *ud) { + Ref<Thread> *tud = (Ref<Thread> *)ud; + Ref<Thread> t = *tud; memdelete(tud); Callable::CallError ce; const Variant *arg[1] = { &t->userdata }; @@ -1797,7 +1796,7 @@ void _Thread::_start_func(void *ud) { } } - Thread::set_name(t->target_method); + ::Thread::set_name(t->target_method); t->ret = t->target_instance->call(t->target_method, arg, argc, ce); if (ce.error != Callable::CallError::CALL_OK) { @@ -1823,7 +1822,7 @@ void _Thread::_start_func(void *ud) { } } -Error _Thread::start(Object *p_instance, const StringName &p_method, const Variant &p_userdata, Priority p_priority) { +Error Thread::start(Object *p_instance, const StringName &p_method, const Variant &p_userdata, Priority p_priority) { ERR_FAIL_COND_V_MSG(active.is_set(), ERR_ALREADY_IN_USE, "Thread already started."); ERR_FAIL_COND_V(!p_instance, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_method == StringName(), ERR_INVALID_PARAMETER); @@ -1835,24 +1834,24 @@ Error _Thread::start(Object *p_instance, const StringName &p_method, const Varia userdata = p_userdata; active.set(); - Ref<_Thread> *ud = memnew(Ref<_Thread>(this)); + Ref<Thread> *ud = memnew(Ref<Thread>(this)); - Thread::Settings s; - s.priority = (Thread::Priority)p_priority; + ::Thread::Settings s; + s.priority = (::Thread::Priority)p_priority; thread.start(_start_func, ud, s); return OK; } -String _Thread::get_id() const { +String Thread::get_id() const { return itos(thread.get_id()); } -bool _Thread::is_active() const { +bool Thread::is_active() const { return active.is_set(); } -Variant _Thread::wait_to_finish() { +Variant Thread::wait_to_finish() { ERR_FAIL_COND_V_MSG(!active.is_set(), Variant(), "Thread must be active to wait for its completion."); thread.wait_to_finish(); Variant r = ret; @@ -1864,22 +1863,24 @@ Variant _Thread::wait_to_finish() { return r; } -void _Thread::_bind_methods() { - ClassDB::bind_method(D_METHOD("start", "instance", "method", "userdata", "priority"), &_Thread::start, DEFVAL(Variant()), DEFVAL(PRIORITY_NORMAL)); - ClassDB::bind_method(D_METHOD("get_id"), &_Thread::get_id); - ClassDB::bind_method(D_METHOD("is_active"), &_Thread::is_active); - ClassDB::bind_method(D_METHOD("wait_to_finish"), &_Thread::wait_to_finish); +void Thread::_bind_methods() { + ClassDB::bind_method(D_METHOD("start", "instance", "method", "userdata", "priority"), &Thread::start, DEFVAL(Variant()), DEFVAL(PRIORITY_NORMAL)); + ClassDB::bind_method(D_METHOD("get_id"), &Thread::get_id); + ClassDB::bind_method(D_METHOD("is_active"), &Thread::is_active); + ClassDB::bind_method(D_METHOD("wait_to_finish"), &Thread::wait_to_finish); BIND_ENUM_CONSTANT(PRIORITY_LOW); BIND_ENUM_CONSTANT(PRIORITY_NORMAL); BIND_ENUM_CONSTANT(PRIORITY_HIGH); } -////// _ClassDB ////// +namespace special { -PackedStringArray _ClassDB::get_class_list() const { +////// ClassDB ////// + +PackedStringArray ClassDB::get_class_list() const { List<StringName> classes; - ClassDB::get_class_list(&classes); + ::ClassDB::get_class_list(&classes); PackedStringArray ret; ret.resize(classes.size()); @@ -1891,9 +1892,9 @@ PackedStringArray _ClassDB::get_class_list() const { return ret; } -PackedStringArray _ClassDB::get_inheriters_from_class(const StringName &p_class) const { +PackedStringArray ClassDB::get_inheriters_from_class(const StringName &p_class) const { List<StringName> classes; - ClassDB::get_inheriters_from_class(p_class, &classes); + ::ClassDB::get_inheriters_from_class(p_class, &classes); PackedStringArray ret; ret.resize(classes.size()); @@ -1905,24 +1906,24 @@ PackedStringArray _ClassDB::get_inheriters_from_class(const StringName &p_class) return ret; } -StringName _ClassDB::get_parent_class(const StringName &p_class) const { - return ClassDB::get_parent_class(p_class); +StringName ClassDB::get_parent_class(const StringName &p_class) const { + return ::ClassDB::get_parent_class(p_class); } -bool _ClassDB::class_exists(const StringName &p_class) const { - return ClassDB::class_exists(p_class); +bool ClassDB::class_exists(const StringName &p_class) const { + return ::ClassDB::class_exists(p_class); } -bool _ClassDB::is_parent_class(const StringName &p_class, const StringName &p_inherits) const { - return ClassDB::is_parent_class(p_class, p_inherits); +bool ClassDB::is_parent_class(const StringName &p_class, const StringName &p_inherits) const { + return ::ClassDB::is_parent_class(p_class, p_inherits); } -bool _ClassDB::can_instantiate(const StringName &p_class) const { - return ClassDB::can_instantiate(p_class); +bool ClassDB::can_instantiate(const StringName &p_class) const { + return ::ClassDB::can_instantiate(p_class); } -Variant _ClassDB::instantiate(const StringName &p_class) const { - Object *obj = ClassDB::instantiate(p_class); +Variant ClassDB::instantiate(const StringName &p_class) const { + Object *obj = ::ClassDB::instantiate(p_class); if (!obj) { return Variant(); } @@ -1935,22 +1936,22 @@ Variant _ClassDB::instantiate(const StringName &p_class) const { } } -bool _ClassDB::has_signal(StringName p_class, StringName p_signal) const { - return ClassDB::has_signal(p_class, p_signal); +bool ClassDB::has_signal(StringName p_class, StringName p_signal) const { + return ::ClassDB::has_signal(p_class, p_signal); } -Dictionary _ClassDB::get_signal(StringName p_class, StringName p_signal) const { +Dictionary ClassDB::get_signal(StringName p_class, StringName p_signal) const { MethodInfo signal; - if (ClassDB::get_signal(p_class, p_signal, &signal)) { + if (::ClassDB::get_signal(p_class, p_signal, &signal)) { return signal.operator Dictionary(); } else { return Dictionary(); } } -Array _ClassDB::get_signal_list(StringName p_class, bool p_no_inheritance) const { +Array ClassDB::get_signal_list(StringName p_class, bool p_no_inheritance) const { List<MethodInfo> signals; - ClassDB::get_signal_list(p_class, &signals, p_no_inheritance); + ::ClassDB::get_signal_list(p_class, &signals, p_no_inheritance); Array ret; for (const MethodInfo &E : signals) { @@ -1960,9 +1961,9 @@ Array _ClassDB::get_signal_list(StringName p_class, bool p_no_inheritance) const return ret; } -Array _ClassDB::get_property_list(StringName p_class, bool p_no_inheritance) const { +Array ClassDB::get_property_list(StringName p_class, bool p_no_inheritance) const { List<PropertyInfo> plist; - ClassDB::get_property_list(p_class, &plist, p_no_inheritance); + ::ClassDB::get_property_list(p_class, &plist, p_no_inheritance); Array ret; for (const PropertyInfo &E : plist) { ret.push_back(E.operator Dictionary()); @@ -1971,16 +1972,16 @@ Array _ClassDB::get_property_list(StringName p_class, bool p_no_inheritance) con return ret; } -Variant _ClassDB::get_property(Object *p_object, const StringName &p_property) const { +Variant ClassDB::get_property(Object *p_object, const StringName &p_property) const { Variant ret; - ClassDB::get_property(p_object, p_property, ret); + ::ClassDB::get_property(p_object, p_property, ret); return ret; } -Error _ClassDB::set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const { +Error ClassDB::set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const { Variant ret; bool valid; - if (!ClassDB::set_property(p_object, p_property, p_value, &valid)) { + if (!::ClassDB::set_property(p_object, p_property, p_value, &valid)) { return ERR_UNAVAILABLE; } else if (!valid) { return ERR_INVALID_DATA; @@ -1988,13 +1989,13 @@ Error _ClassDB::set_property(Object *p_object, const StringName &p_property, con return OK; } -bool _ClassDB::has_method(StringName p_class, StringName p_method, bool p_no_inheritance) const { - return ClassDB::has_method(p_class, p_method, p_no_inheritance); +bool ClassDB::has_method(StringName p_class, StringName p_method, bool p_no_inheritance) const { + return ::ClassDB::has_method(p_class, p_method, p_no_inheritance); } -Array _ClassDB::get_method_list(StringName p_class, bool p_no_inheritance) const { +Array ClassDB::get_method_list(StringName p_class, bool p_no_inheritance) const { List<MethodInfo> methods; - ClassDB::get_method_list(p_class, &methods, p_no_inheritance); + ::ClassDB::get_method_list(p_class, &methods, p_no_inheritance); Array ret; for (const MethodInfo &E : methods) { @@ -2010,9 +2011,9 @@ Array _ClassDB::get_method_list(StringName p_class, bool p_no_inheritance) const return ret; } -PackedStringArray _ClassDB::get_integer_constant_list(const StringName &p_class, bool p_no_inheritance) const { +PackedStringArray ClassDB::get_integer_constant_list(const StringName &p_class, bool p_no_inheritance) const { List<String> constants; - ClassDB::get_integer_constant_list(p_class, &constants, p_no_inheritance); + ::ClassDB::get_integer_constant_list(p_class, &constants, p_no_inheritance); PackedStringArray ret; ret.resize(constants.size()); @@ -2024,204 +2025,237 @@ PackedStringArray _ClassDB::get_integer_constant_list(const StringName &p_class, return ret; } -bool _ClassDB::has_integer_constant(const StringName &p_class, const StringName &p_name) const { +bool ClassDB::has_integer_constant(const StringName &p_class, const StringName &p_name) const { bool success; - ClassDB::get_integer_constant(p_class, p_name, &success); + ::ClassDB::get_integer_constant(p_class, p_name, &success); return success; } -int _ClassDB::get_integer_constant(const StringName &p_class, const StringName &p_name) const { +int ClassDB::get_integer_constant(const StringName &p_class, const StringName &p_name) const { bool found; - int c = ClassDB::get_integer_constant(p_class, p_name, &found); + int c = ::ClassDB::get_integer_constant(p_class, p_name, &found); ERR_FAIL_COND_V(!found, 0); return c; } -StringName _ClassDB::get_category(const StringName &p_node) const { - return ClassDB::get_category(p_node); +StringName ClassDB::get_category(const StringName &p_node) const { + return ::ClassDB::get_category(p_node); } -bool _ClassDB::is_class_enabled(StringName p_class) const { - return ClassDB::is_class_enabled(p_class); +bool ClassDB::is_class_enabled(StringName p_class) const { + return ::ClassDB::is_class_enabled(p_class); } -void _ClassDB::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_class_list"), &_ClassDB::get_class_list); - ClassDB::bind_method(D_METHOD("get_inheriters_from_class", "class"), &_ClassDB::get_inheriters_from_class); - ClassDB::bind_method(D_METHOD("get_parent_class", "class"), &_ClassDB::get_parent_class); - ClassDB::bind_method(D_METHOD("class_exists", "class"), &_ClassDB::class_exists); - ClassDB::bind_method(D_METHOD("is_parent_class", "class", "inherits"), &_ClassDB::is_parent_class); - ClassDB::bind_method(D_METHOD("can_instantiate", "class"), &_ClassDB::can_instantiate); - ClassDB::bind_method(D_METHOD("instantiate", "class"), &_ClassDB::instantiate); +void ClassDB::_bind_methods() { + ::ClassDB::bind_method(D_METHOD("get_class_list"), &ClassDB::get_class_list); + ::ClassDB::bind_method(D_METHOD("get_inheriters_from_class", "class"), &ClassDB::get_inheriters_from_class); + ::ClassDB::bind_method(D_METHOD("get_parent_class", "class"), &ClassDB::get_parent_class); + ::ClassDB::bind_method(D_METHOD("class_exists", "class"), &ClassDB::class_exists); + ::ClassDB::bind_method(D_METHOD("is_parent_class", "class", "inherits"), &ClassDB::is_parent_class); + ::ClassDB::bind_method(D_METHOD("can_instantiate", "class"), &ClassDB::can_instantiate); + ::ClassDB::bind_method(D_METHOD("instantiate", "class"), &ClassDB::instantiate); + + ::ClassDB::bind_method(D_METHOD("class_has_signal", "class", "signal"), &ClassDB::has_signal); + ::ClassDB::bind_method(D_METHOD("class_get_signal", "class", "signal"), &ClassDB::get_signal); + ::ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &ClassDB::get_signal_list, DEFVAL(false)); + + ::ClassDB::bind_method(D_METHOD("class_get_property_list", "class", "no_inheritance"), &ClassDB::get_property_list, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_property", "object", "property"), &ClassDB::get_property); + ::ClassDB::bind_method(D_METHOD("class_set_property", "object", "property", "value"), &ClassDB::set_property); - ClassDB::bind_method(D_METHOD("class_has_signal", "class", "signal"), &_ClassDB::has_signal); - ClassDB::bind_method(D_METHOD("class_get_signal", "class", "signal"), &_ClassDB::get_signal); - ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &_ClassDB::get_signal_list, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_has_method", "class", "method", "no_inheritance"), &ClassDB::has_method, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("class_get_property_list", "class", "no_inheritance"), &_ClassDB::get_property_list, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("class_get_property", "object", "property"), &_ClassDB::get_property); - ClassDB::bind_method(D_METHOD("class_set_property", "object", "property", "value"), &_ClassDB::set_property); + ::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::get_method_list, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("class_has_method", "class", "method", "no_inheritance"), &_ClassDB::has_method, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::get_integer_constant_list, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &_ClassDB::get_method_list, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_has_integer_constant", "class", "name"), &ClassDB::has_integer_constant); + ::ClassDB::bind_method(D_METHOD("class_get_integer_constant", "class", "name"), &ClassDB::get_integer_constant); - ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &_ClassDB::get_integer_constant_list, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_category", "class"), &ClassDB::get_category); + ::ClassDB::bind_method(D_METHOD("is_class_enabled", "class"), &ClassDB::is_class_enabled); +} + +} // namespace special - ClassDB::bind_method(D_METHOD("class_has_integer_constant", "class", "name"), &_ClassDB::has_integer_constant); - ClassDB::bind_method(D_METHOD("class_get_integer_constant", "class", "name"), &_ClassDB::get_integer_constant); +////// Engine ////// - ClassDB::bind_method(D_METHOD("class_get_category", "class"), &_ClassDB::get_category); - ClassDB::bind_method(D_METHOD("is_class_enabled", "class"), &_ClassDB::is_class_enabled); +void Engine::set_physics_ticks_per_second(int p_ips) { + ::Engine::get_singleton()->set_physics_ticks_per_second(p_ips); } -////// _Engine ////// +int Engine::get_physics_ticks_per_second() const { + return ::Engine::get_singleton()->get_physics_ticks_per_second(); +} -void _Engine::set_physics_ticks_per_second(int p_ips) { - Engine::get_singleton()->set_physics_ticks_per_second(p_ips); +void Engine::set_physics_jitter_fix(double p_threshold) { + ::Engine::get_singleton()->set_physics_jitter_fix(p_threshold); } -int _Engine::get_physics_ticks_per_second() const { - return Engine::get_singleton()->get_physics_ticks_per_second(); +double Engine::get_physics_jitter_fix() const { + return ::Engine::get_singleton()->get_physics_jitter_fix(); } -void _Engine::set_physics_jitter_fix(double p_threshold) { - Engine::get_singleton()->set_physics_jitter_fix(p_threshold); +double Engine::get_physics_interpolation_fraction() const { + return ::Engine::get_singleton()->get_physics_interpolation_fraction(); } -double _Engine::get_physics_jitter_fix() const { - return Engine::get_singleton()->get_physics_jitter_fix(); +void Engine::set_target_fps(int p_fps) { + ::Engine::get_singleton()->set_target_fps(p_fps); } -double _Engine::get_physics_interpolation_fraction() const { - return Engine::get_singleton()->get_physics_interpolation_fraction(); +int Engine::get_target_fps() const { + return ::Engine::get_singleton()->get_target_fps(); } -void _Engine::set_target_fps(int p_fps) { - Engine::get_singleton()->set_target_fps(p_fps); +double Engine::get_frames_per_second() const { + return ::Engine::get_singleton()->get_frames_per_second(); } -int _Engine::get_target_fps() const { - return Engine::get_singleton()->get_target_fps(); +uint64_t Engine::get_physics_frames() const { + return ::Engine::get_singleton()->get_physics_frames(); } -double _Engine::get_frames_per_second() const { - return Engine::get_singleton()->get_frames_per_second(); +uint64_t Engine::get_process_frames() const { + return ::Engine::get_singleton()->get_process_frames(); } -uint64_t _Engine::get_physics_frames() const { - return Engine::get_singleton()->get_physics_frames(); +void Engine::set_time_scale(double p_scale) { + ::Engine::get_singleton()->set_time_scale(p_scale); } -uint64_t _Engine::get_process_frames() const { - return Engine::get_singleton()->get_process_frames(); +double Engine::get_time_scale() { + return ::Engine::get_singleton()->get_time_scale(); } -void _Engine::set_time_scale(double p_scale) { - Engine::get_singleton()->set_time_scale(p_scale); +int Engine::get_frames_drawn() { + return ::Engine::get_singleton()->get_frames_drawn(); } -double _Engine::get_time_scale() { - return Engine::get_singleton()->get_time_scale(); +MainLoop *Engine::get_main_loop() const { + // Needs to remain in OS, since it's actually OS that interacts with it, but it's better exposed here + return ::OS::get_singleton()->get_main_loop(); } -int _Engine::get_frames_drawn() { - return Engine::get_singleton()->get_frames_drawn(); +Dictionary Engine::get_version_info() const { + return ::Engine::get_singleton()->get_version_info(); } -MainLoop *_Engine::get_main_loop() const { - //needs to remain in OS, since it's actually OS that interacts with it, but it's better exposed here - return OS::get_singleton()->get_main_loop(); +Dictionary Engine::get_author_info() const { + return ::Engine::get_singleton()->get_author_info(); } -Dictionary _Engine::get_version_info() const { - return Engine::get_singleton()->get_version_info(); +Array Engine::get_copyright_info() const { + return ::Engine::get_singleton()->get_copyright_info(); } -Dictionary _Engine::get_author_info() const { - return Engine::get_singleton()->get_author_info(); +Dictionary Engine::get_donor_info() const { + return ::Engine::get_singleton()->get_donor_info(); } -Array _Engine::get_copyright_info() const { - return Engine::get_singleton()->get_copyright_info(); +Dictionary Engine::get_license_info() const { + return ::Engine::get_singleton()->get_license_info(); } -Dictionary _Engine::get_donor_info() const { - return Engine::get_singleton()->get_donor_info(); +String Engine::get_license_text() const { + return ::Engine::get_singleton()->get_license_text(); } -Dictionary _Engine::get_license_info() const { - return Engine::get_singleton()->get_license_info(); +bool Engine::is_in_physics_frame() const { + return ::Engine::get_singleton()->is_in_physics_frame(); } -String _Engine::get_license_text() const { - return Engine::get_singleton()->get_license_text(); +bool Engine::has_singleton(const StringName &p_name) const { + return ::Engine::get_singleton()->has_singleton(p_name); } -bool _Engine::is_in_physics_frame() const { - return Engine::get_singleton()->is_in_physics_frame(); +Object *Engine::get_singleton_object(const StringName &p_name) const { + return ::Engine::get_singleton()->get_singleton_object(p_name); } -bool _Engine::has_singleton(const String &p_name) const { - return Engine::get_singleton()->has_singleton(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); } -Object *_Engine::get_singleton_object(const String &p_name) const { - return Engine::get_singleton()->get_singleton_object(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); +void Engine::set_editor_hint(bool p_enabled) { + ::Engine::get_singleton()->set_editor_hint(p_enabled); } -bool _Engine::is_editor_hint() const { - return Engine::get_singleton()->is_editor_hint(); +bool Engine::is_editor_hint() const { + return ::Engine::get_singleton()->is_editor_hint(); } -void _Engine::set_print_error_messages(bool p_enabled) { - Engine::get_singleton()->set_print_error_messages(p_enabled); +void Engine::set_print_error_messages(bool p_enabled) { + ::Engine::get_singleton()->set_print_error_messages(p_enabled); } -bool _Engine::is_printing_error_messages() const { - return Engine::get_singleton()->is_printing_error_messages(); +bool Engine::is_printing_error_messages() const { + return ::Engine::get_singleton()->is_printing_error_messages(); } -void _Engine::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_physics_ticks_per_second", "physics_ticks_per_second"), &_Engine::set_physics_ticks_per_second); - ClassDB::bind_method(D_METHOD("get_physics_ticks_per_second"), &_Engine::get_physics_ticks_per_second); - ClassDB::bind_method(D_METHOD("set_physics_jitter_fix", "physics_jitter_fix"), &_Engine::set_physics_jitter_fix); - ClassDB::bind_method(D_METHOD("get_physics_jitter_fix"), &_Engine::get_physics_jitter_fix); - ClassDB::bind_method(D_METHOD("get_physics_interpolation_fraction"), &_Engine::get_physics_interpolation_fraction); - ClassDB::bind_method(D_METHOD("set_target_fps", "target_fps"), &_Engine::set_target_fps); - ClassDB::bind_method(D_METHOD("get_target_fps"), &_Engine::get_target_fps); +void Engine::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_physics_ticks_per_second", "physics_ticks_per_second"), &Engine::set_physics_ticks_per_second); + ClassDB::bind_method(D_METHOD("get_physics_ticks_per_second"), &Engine::get_physics_ticks_per_second); + ClassDB::bind_method(D_METHOD("set_physics_jitter_fix", "physics_jitter_fix"), &Engine::set_physics_jitter_fix); + ClassDB::bind_method(D_METHOD("get_physics_jitter_fix"), &Engine::get_physics_jitter_fix); + ClassDB::bind_method(D_METHOD("get_physics_interpolation_fraction"), &Engine::get_physics_interpolation_fraction); + ClassDB::bind_method(D_METHOD("set_target_fps", "target_fps"), &Engine::set_target_fps); + ClassDB::bind_method(D_METHOD("get_target_fps"), &Engine::get_target_fps); - ClassDB::bind_method(D_METHOD("set_time_scale", "time_scale"), &_Engine::set_time_scale); - ClassDB::bind_method(D_METHOD("get_time_scale"), &_Engine::get_time_scale); + ClassDB::bind_method(D_METHOD("set_time_scale", "time_scale"), &Engine::set_time_scale); + ClassDB::bind_method(D_METHOD("get_time_scale"), &Engine::get_time_scale); - ClassDB::bind_method(D_METHOD("get_frames_drawn"), &_Engine::get_frames_drawn); - ClassDB::bind_method(D_METHOD("get_frames_per_second"), &_Engine::get_frames_per_second); - ClassDB::bind_method(D_METHOD("get_physics_frames"), &_Engine::get_physics_frames); - ClassDB::bind_method(D_METHOD("get_process_frames"), &_Engine::get_process_frames); + ClassDB::bind_method(D_METHOD("get_frames_drawn"), &Engine::get_frames_drawn); + ClassDB::bind_method(D_METHOD("get_frames_per_second"), &Engine::get_frames_per_second); + ClassDB::bind_method(D_METHOD("get_physics_frames"), &Engine::get_physics_frames); + ClassDB::bind_method(D_METHOD("get_process_frames"), &Engine::get_process_frames); - ClassDB::bind_method(D_METHOD("get_main_loop"), &_Engine::get_main_loop); + ClassDB::bind_method(D_METHOD("get_main_loop"), &Engine::get_main_loop); - ClassDB::bind_method(D_METHOD("get_version_info"), &_Engine::get_version_info); - ClassDB::bind_method(D_METHOD("get_author_info"), &_Engine::get_author_info); - ClassDB::bind_method(D_METHOD("get_copyright_info"), &_Engine::get_copyright_info); - ClassDB::bind_method(D_METHOD("get_donor_info"), &_Engine::get_donor_info); - ClassDB::bind_method(D_METHOD("get_license_info"), &_Engine::get_license_info); - ClassDB::bind_method(D_METHOD("get_license_text"), &_Engine::get_license_text); + ClassDB::bind_method(D_METHOD("get_version_info"), &Engine::get_version_info); + ClassDB::bind_method(D_METHOD("get_author_info"), &Engine::get_author_info); + ClassDB::bind_method(D_METHOD("get_copyright_info"), &Engine::get_copyright_info); + ClassDB::bind_method(D_METHOD("get_donor_info"), &Engine::get_donor_info); + ClassDB::bind_method(D_METHOD("get_license_info"), &Engine::get_license_info); + ClassDB::bind_method(D_METHOD("get_license_text"), &Engine::get_license_text); - ClassDB::bind_method(D_METHOD("is_in_physics_frame"), &_Engine::is_in_physics_frame); + ClassDB::bind_method(D_METHOD("is_in_physics_frame"), &Engine::is_in_physics_frame); - 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("has_singleton", "name"), &Engine::has_singleton); + ClassDB::bind_method(D_METHOD("get_singleton", "name"), &Engine::get_singleton_object); - 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); + 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_print_error_messages", "enabled"), &_Engine::set_print_error_messages); - ClassDB::bind_method(D_METHOD("is_printing_error_messages"), &_Engine::is_printing_error_messages); + 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); + + ClassDB::bind_method(D_METHOD("set_print_error_messages", "enabled"), &Engine::set_print_error_messages); + ClassDB::bind_method(D_METHOD("is_printing_error_messages"), &Engine::is_printing_error_messages); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_hint"), "set_editor_hint", "is_editor_hint"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "print_error_messages"), "set_print_error_messages", "is_printing_error_messages"); @@ -2231,92 +2265,74 @@ void _Engine::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "physics_jitter_fix"), "set_physics_jitter_fix", "get_physics_jitter_fix"); } -_Engine *_Engine::singleton = nullptr; - -////// _EngineDebugger ////// - -void _EngineDebugger::_bind_methods() { - ClassDB::bind_method(D_METHOD("is_active"), &_EngineDebugger::is_active); - - ClassDB::bind_method(D_METHOD("register_profiler", "name", "toggle", "add", "tick"), &_EngineDebugger::register_profiler); - ClassDB::bind_method(D_METHOD("unregister_profiler", "name"), &_EngineDebugger::unregister_profiler); - ClassDB::bind_method(D_METHOD("is_profiling", "name"), &_EngineDebugger::is_profiling); - ClassDB::bind_method(D_METHOD("has_profiler", "name"), &_EngineDebugger::has_profiler); +Engine *Engine::singleton = nullptr; - ClassDB::bind_method(D_METHOD("profiler_add_frame_data", "name", "data"), &_EngineDebugger::profiler_add_frame_data); - ClassDB::bind_method(D_METHOD("profiler_enable", "name", "enable", "arguments"), &_EngineDebugger::profiler_enable, DEFVAL(Array())); - - ClassDB::bind_method(D_METHOD("register_message_capture", "name", "callable"), &_EngineDebugger::register_message_capture); - ClassDB::bind_method(D_METHOD("unregister_message_capture", "name"), &_EngineDebugger::unregister_message_capture); - ClassDB::bind_method(D_METHOD("has_capture", "name"), &_EngineDebugger::has_capture); - - ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &_EngineDebugger::send_message); -} +////// EngineDebugger ////// -bool _EngineDebugger::is_active() { - return EngineDebugger::is_active(); +bool EngineDebugger::is_active() { + return ::EngineDebugger::is_active(); } -void _EngineDebugger::register_profiler(const StringName &p_name, const Callable &p_toggle, const Callable &p_add, const Callable &p_tick) { +void EngineDebugger::register_profiler(const StringName &p_name, const Callable &p_toggle, const Callable &p_add, const Callable &p_tick) { ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler already registered: " + p_name); profilers.insert(p_name, ProfilerCallable(p_toggle, p_add, p_tick)); ProfilerCallable &p = profilers[p_name]; - EngineDebugger::Profiler profiler( + ::EngineDebugger::Profiler profiler( &p, - &_EngineDebugger::call_toggle, - &_EngineDebugger::call_add, - &_EngineDebugger::call_tick); - EngineDebugger::register_profiler(p_name, profiler); + &EngineDebugger::call_toggle, + &EngineDebugger::call_add, + &EngineDebugger::call_tick); + ::EngineDebugger::register_profiler(p_name, profiler); } -void _EngineDebugger::unregister_profiler(const StringName &p_name) { +void EngineDebugger::unregister_profiler(const StringName &p_name) { ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name); - EngineDebugger::unregister_profiler(p_name); + ::EngineDebugger::unregister_profiler(p_name); profilers.erase(p_name); } -bool _EngineDebugger::_EngineDebugger::is_profiling(const StringName &p_name) { - return EngineDebugger::is_profiling(p_name); +bool EngineDebugger::is_profiling(const StringName &p_name) { + return ::EngineDebugger::is_profiling(p_name); } -bool _EngineDebugger::has_profiler(const StringName &p_name) { - return EngineDebugger::has_profiler(p_name); +bool EngineDebugger::has_profiler(const StringName &p_name) { + return ::EngineDebugger::has_profiler(p_name); } -void _EngineDebugger::profiler_add_frame_data(const StringName &p_name, const Array &p_data) { - EngineDebugger::profiler_add_frame_data(p_name, p_data); +void EngineDebugger::profiler_add_frame_data(const StringName &p_name, const Array &p_data) { + ::EngineDebugger::profiler_add_frame_data(p_name, p_data); } -void _EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts) { - if (EngineDebugger::get_singleton()) { - EngineDebugger::get_singleton()->profiler_enable(p_name, p_enabled, p_opts); +void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts) { + if (::EngineDebugger::get_singleton()) { + ::EngineDebugger::get_singleton()->profiler_enable(p_name, p_enabled, p_opts); } } -void _EngineDebugger::register_message_capture(const StringName &p_name, const Callable &p_callable) { +void EngineDebugger::register_message_capture(const StringName &p_name, const Callable &p_callable) { ERR_FAIL_COND_MSG(captures.has(p_name) || has_capture(p_name), "Capture already registered: " + p_name); captures.insert(p_name, p_callable); Callable &c = captures[p_name]; - EngineDebugger::Capture capture(&c, &_EngineDebugger::call_capture); - EngineDebugger::register_message_capture(p_name, capture); + ::EngineDebugger::Capture capture(&c, &EngineDebugger::call_capture); + ::EngineDebugger::register_message_capture(p_name, capture); } -void _EngineDebugger::unregister_message_capture(const StringName &p_name) { +void EngineDebugger::unregister_message_capture(const StringName &p_name) { ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name); - EngineDebugger::unregister_message_capture(p_name); + ::EngineDebugger::unregister_message_capture(p_name); captures.erase(p_name); } -bool _EngineDebugger::has_capture(const StringName &p_name) { - return EngineDebugger::has_capture(p_name); +bool EngineDebugger::has_capture(const StringName &p_name) { + return ::EngineDebugger::has_capture(p_name); } -void _EngineDebugger::send_message(const String &p_msg, const Array &p_data) { - ERR_FAIL_COND_MSG(!EngineDebugger::is_active(), "Can't send message. No active debugger"); - EngineDebugger::get_singleton()->send_message(p_msg, p_data); +void EngineDebugger::send_message(const String &p_msg, const Array &p_data) { + ERR_FAIL_COND_MSG(!::EngineDebugger::is_active(), "Can't send message. No active debugger"); + ::EngineDebugger::get_singleton()->send_message(p_msg, p_data); } -void _EngineDebugger::call_toggle(void *p_user, bool p_enable, const Array &p_opts) { +void EngineDebugger::call_toggle(void *p_user, bool p_enable, const Array &p_opts) { Callable &toggle = ((ProfilerCallable *)p_user)->callable_toggle; if (toggle.is_null()) { return; @@ -2329,7 +2345,7 @@ void _EngineDebugger::call_toggle(void *p_user, bool p_enable, const Array &p_op ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'toggle' to callable: " + Variant::get_callable_error_text(toggle, args, 2, err)); } -void _EngineDebugger::call_add(void *p_user, const Array &p_data) { +void EngineDebugger::call_add(void *p_user, const Array &p_data) { Callable &add = ((ProfilerCallable *)p_user)->callable_add; if (add.is_null()) { return; @@ -2342,7 +2358,7 @@ void _EngineDebugger::call_add(void *p_user, const Array &p_data) { ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'add' to callable: " + Variant::get_callable_error_text(add, args, 1, err)); } -void _EngineDebugger::call_tick(void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { +void EngineDebugger::call_tick(void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { Callable &tick = ((ProfilerCallable *)p_user)->callable_tick; if (tick.is_null()) { return; @@ -2355,7 +2371,7 @@ void _EngineDebugger::call_tick(void *p_user, double p_frame_time, double p_idle ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'tick' to callable: " + Variant::get_callable_error_text(tick, args, 4, err)); } -Error _EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) { +Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) { Callable &capture = *(Callable *)p_user; if (capture.is_null()) { return FAILED; @@ -2371,15 +2387,35 @@ Error _EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Arr return OK; } -_EngineDebugger::~_EngineDebugger() { +EngineDebugger::~EngineDebugger() { for (Map<StringName, Callable>::Element *E = captures.front(); E; E = E->next()) { - EngineDebugger::unregister_message_capture(E->key()); + ::EngineDebugger::unregister_message_capture(E->key()); } captures.clear(); for (Map<StringName, ProfilerCallable>::Element *E = profilers.front(); E; E = E->next()) { - EngineDebugger::unregister_profiler(E->key()); + ::EngineDebugger::unregister_profiler(E->key()); } profilers.clear(); } -_EngineDebugger *_EngineDebugger::singleton = nullptr; +EngineDebugger *EngineDebugger::singleton = nullptr; + +void EngineDebugger::_bind_methods() { + ClassDB::bind_method(D_METHOD("is_active"), &EngineDebugger::is_active); + + ClassDB::bind_method(D_METHOD("register_profiler", "name", "toggle", "add", "tick"), &EngineDebugger::register_profiler); + ClassDB::bind_method(D_METHOD("unregister_profiler", "name"), &EngineDebugger::unregister_profiler); + ClassDB::bind_method(D_METHOD("is_profiling", "name"), &EngineDebugger::is_profiling); + ClassDB::bind_method(D_METHOD("has_profiler", "name"), &EngineDebugger::has_profiler); + + ClassDB::bind_method(D_METHOD("profiler_add_frame_data", "name", "data"), &EngineDebugger::profiler_add_frame_data); + ClassDB::bind_method(D_METHOD("profiler_enable", "name", "enable", "arguments"), &EngineDebugger::profiler_enable, DEFVAL(Array())); + + ClassDB::bind_method(D_METHOD("register_message_capture", "name", "callable"), &EngineDebugger::register_message_capture); + ClassDB::bind_method(D_METHOD("unregister_message_capture", "name"), &EngineDebugger::unregister_message_capture); + ClassDB::bind_method(D_METHOD("has_capture", "name"), &EngineDebugger::has_capture); + + ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &EngineDebugger::send_message); +} + +} // namespace core_bind diff --git a/core/core_bind.h b/core/core_bind.h index 9060aff340..a6fac63edd 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -42,12 +42,16 @@ #include "core/os/thread.h" #include "core/templates/safe_refcount.h" -class _ResourceLoader : public Object { - GDCLASS(_ResourceLoader, Object); +class MainLoop; + +namespace core_bind { + +class ResourceLoader : public Object { + GDCLASS(ResourceLoader, Object); protected: static void _bind_methods(); - static _ResourceLoader *singleton; + static ResourceLoader *singleton; public: enum ThreadLoadStatus { @@ -63,7 +67,7 @@ public: CACHE_MODE_REPLACE, // Resource and subresource use path cache, but replace existing loaded resources when available with information from disk. }; - static _ResourceLoader *get_singleton() { return singleton; } + static ResourceLoader *get_singleton() { return singleton; } Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false); ThreadLoadStatus load_threaded_get_status(const String &p_path, Array r_progress = Array()); @@ -77,18 +81,15 @@ public: bool exists(const String &p_path, const String &p_type_hint = ""); ResourceUID::ID get_resource_uid(const String &p_path); - _ResourceLoader() { singleton = this; } + ResourceLoader() { singleton = this; } }; -VARIANT_ENUM_CAST(_ResourceLoader::ThreadLoadStatus); -VARIANT_ENUM_CAST(_ResourceLoader::CacheMode); - -class _ResourceSaver : public Object { - GDCLASS(_ResourceSaver, Object); +class ResourceSaver : public Object { + GDCLASS(ResourceSaver, Object); protected: static void _bind_methods(); - static _ResourceSaver *singleton; + static ResourceSaver *singleton; public: enum SaverFlags { @@ -101,24 +102,20 @@ public: FLAG_REPLACE_SUBRESOURCE_PATHS = 64, }; - static _ResourceSaver *get_singleton() { return singleton; } + static ResourceSaver *get_singleton() { return singleton; } Error save(const String &p_path, const RES &p_resource, SaverFlags p_flags); Vector<String> get_recognized_extensions(const RES &p_resource); - _ResourceSaver() { singleton = this; } + ResourceSaver() { singleton = this; } }; -VARIANT_ENUM_CAST(_ResourceSaver::SaverFlags); - -class MainLoop; - -class _OS : public Object { - GDCLASS(_OS, Object); +class OS : public Object { + GDCLASS(OS, Object); protected: static void _bind_methods(); - static _OS *singleton; + static OS *singleton; public: enum VideoDriver { @@ -229,10 +226,9 @@ public: SYSTEM_DIR_RINGTONES, }; - String get_system_dir(SystemDir p_dir) const; + String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const; String get_user_data_dir() const; - String get_external_data_dir() const; String get_config_dir() const; String get_data_dir() const; String get_cache_dir() const; @@ -246,26 +242,21 @@ public: bool request_permissions(); Vector<String> get_granted_permissions() const; - static _OS *get_singleton() { return singleton; } + static OS *get_singleton() { return singleton; } - _OS() { singleton = this; } + OS() { singleton = this; } }; -VARIANT_ENUM_CAST(_OS::VideoDriver); -VARIANT_ENUM_CAST(_OS::Weekday); -VARIANT_ENUM_CAST(_OS::Month); -VARIANT_ENUM_CAST(_OS::SystemDir); +class Geometry2D : public Object { + GDCLASS(Geometry2D, Object); -class _Geometry2D : public Object { - GDCLASS(_Geometry2D, Object); - - static _Geometry2D *singleton; + static Geometry2D *singleton; protected: static void _bind_methods(); public: - static _Geometry2D *get_singleton(); + static Geometry2D *get_singleton(); Variant segment_intersects_segment(const Vector2 &p_from_a, const Vector2 &p_to_a, const Vector2 &p_from_b, const Vector2 &p_to_b); Variant line_intersects_line(const Vector2 &p_from_a, const Vector2 &p_dir_a, const Vector2 &p_from_b, const Vector2 &p_dir_b); Vector<Vector2> get_closest_points_between_segments(const Vector2 &p1, const Vector2 &q1, const Vector2 &p2, const Vector2 &q2); @@ -316,23 +307,19 @@ public: Dictionary make_atlas(const Vector<Size2> &p_rects); - _Geometry2D() { singleton = this; } + Geometry2D() { singleton = this; } }; -VARIANT_ENUM_CAST(_Geometry2D::PolyBooleanOperation); -VARIANT_ENUM_CAST(_Geometry2D::PolyJoinType); -VARIANT_ENUM_CAST(_Geometry2D::PolyEndType); - -class _Geometry3D : public Object { - GDCLASS(_Geometry3D, Object); +class Geometry3D : public Object { + GDCLASS(Geometry3D, Object); - static _Geometry3D *singleton; + static Geometry3D *singleton; protected: static void _bind_methods(); public: - static _Geometry3D *get_singleton(); + static Geometry3D *get_singleton(); Vector<Plane> build_box_planes(const Vector3 &p_extents); Vector<Plane> build_cylinder_planes(float p_radius, float p_height, int p_sides, Vector3::Axis p_axis = Vector3::AXIS_Z); Vector<Plane> build_capsule_planes(float p_radius, float p_height, int p_sides, int p_lats, Vector3::Axis p_axis = Vector3::AXIS_Z); @@ -348,11 +335,11 @@ public: Vector<Vector3> clip_polygon(const Vector<Vector3> &p_points, const Plane &p_plane); - _Geometry3D() { singleton = this; } + Geometry3D() { singleton = this; } }; -class _File : public RefCounted { - GDCLASS(_File, RefCounted); +class File : public RefCounted { + GDCLASS(File, RefCounted); FileAccess *f = nullptr; bool big_endian = false; @@ -446,15 +433,12 @@ public: uint64_t get_modified_time(const String &p_file) const; - _File() {} - virtual ~_File(); + File() {} + virtual ~File(); }; -VARIANT_ENUM_CAST(_File::ModeFlags); -VARIANT_ENUM_CAST(_File::CompressionMode); - -class _Directory : public RefCounted { - GDCLASS(_Directory, RefCounted); +class Directory : public RefCounted { + GDCLASS(Directory, RefCounted); DirAccess *d; bool dir_open = false; @@ -491,24 +475,24 @@ public: Error rename(String p_from, String p_to); Error remove(String p_name); - _Directory(); - virtual ~_Directory(); + Directory(); + virtual ~Directory(); private: bool _list_skip_navigational = false; bool _list_skip_hidden = false; }; -class _Marshalls : public Object { - GDCLASS(_Marshalls, Object); +class Marshalls : public Object { + GDCLASS(Marshalls, Object); - static _Marshalls *singleton; + static Marshalls *singleton; protected: static void _bind_methods(); public: - static _Marshalls *get_singleton(); + static Marshalls *get_singleton(); String variant_to_base64(const Variant &p_var, bool p_full_objects = false); Variant base64_to_variant(const String &p_str, bool p_allow_objects = false); @@ -519,13 +503,13 @@ public: String utf8_to_base64(const String &p_str); String base64_to_utf8(const String &p_str); - _Marshalls() { singleton = this; } - ~_Marshalls() { singleton = nullptr; } + Marshalls() { singleton = this; } + ~Marshalls() { singleton = nullptr; } }; -class _Mutex : public RefCounted { - GDCLASS(_Mutex, RefCounted); - Mutex mutex; +class Mutex : public RefCounted { + GDCLASS(Mutex, RefCounted); + ::Mutex mutex; static void _bind_methods(); @@ -535,9 +519,9 @@ public: void unlock(); }; -class _Semaphore : public RefCounted { - GDCLASS(_Semaphore, RefCounted); - Semaphore semaphore; +class Semaphore : public RefCounted { + GDCLASS(Semaphore, RefCounted); + ::Semaphore semaphore; static void _bind_methods(); @@ -547,8 +531,8 @@ public: void post(); }; -class _Thread : public RefCounted { - GDCLASS(_Thread, RefCounted); +class Thread : public RefCounted { + GDCLASS(Thread, RefCounted); protected: Variant ret; @@ -556,7 +540,7 @@ protected: SafeFlag active; Object *target_instance = nullptr; StringName target_method; - Thread thread; + ::Thread thread; static void _bind_methods(); static void _start_func(void *ud); @@ -574,10 +558,10 @@ public: Variant wait_to_finish(); }; -VARIANT_ENUM_CAST(_Thread::Priority); +namespace special { -class _ClassDB : public Object { - GDCLASS(_ClassDB, Object); +class ClassDB : public Object { + GDCLASS(ClassDB, Object); protected: static void _bind_methods(); @@ -610,19 +594,21 @@ public: bool is_class_enabled(StringName p_class) const; - _ClassDB() {} - ~_ClassDB() {} + ClassDB() {} + ~ClassDB() {} }; -class _Engine : public Object { - GDCLASS(_Engine, Object); +} // namespace special + +class Engine : public Object { + GDCLASS(Engine, Object); protected: static void _bind_methods(); - static _Engine *singleton; + static Engine *singleton; public: - static _Engine *get_singleton() { return singleton; } + static Engine *get_singleton() { return singleton; } void set_physics_ticks_per_second(int p_ips); int get_physics_ticks_per_second() const; @@ -653,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; @@ -662,14 +651,14 @@ public: void set_print_error_messages(bool p_enabled); bool is_printing_error_messages() const; - _Engine() { singleton = this; } + Engine() { singleton = this; } }; -class _EngineDebugger : public Object { - GDCLASS(_EngineDebugger, Object); +class EngineDebugger : public Object { + GDCLASS(EngineDebugger, Object); class ProfilerCallable { - friend class _EngineDebugger; + friend class EngineDebugger; Callable callable_toggle; Callable callable_add; @@ -690,10 +679,10 @@ class _EngineDebugger : public Object { protected: static void _bind_methods(); - static _EngineDebugger *singleton; + static EngineDebugger *singleton; public: - static _EngineDebugger *get_singleton() { return singleton; } + static EngineDebugger *get_singleton() { return singleton; } bool is_active(); @@ -715,8 +704,29 @@ public: static void call_tick(void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time); static Error call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured); - _EngineDebugger() { singleton = this; } - ~_EngineDebugger(); + EngineDebugger() { singleton = this; } + ~EngineDebugger(); }; +} // namespace core_bind + +VARIANT_ENUM_CAST(core_bind::ResourceLoader::ThreadLoadStatus); +VARIANT_ENUM_CAST(core_bind::ResourceLoader::CacheMode); + +VARIANT_ENUM_CAST(core_bind::ResourceSaver::SaverFlags); + +VARIANT_ENUM_CAST(core_bind::OS::VideoDriver); +VARIANT_ENUM_CAST(core_bind::OS::Weekday); +VARIANT_ENUM_CAST(core_bind::OS::Month); +VARIANT_ENUM_CAST(core_bind::OS::SystemDir); + +VARIANT_ENUM_CAST(core_bind::Geometry2D::PolyBooleanOperation); +VARIANT_ENUM_CAST(core_bind::Geometry2D::PolyJoinType); +VARIANT_ENUM_CAST(core_bind::Geometry2D::PolyEndType); + +VARIANT_ENUM_CAST(core_bind::File::ModeFlags); +VARIANT_ENUM_CAST(core_bind::File::CompressionMode); + +VARIANT_ENUM_CAST(core_bind::Thread::Priority); + #endif // CORE_BIND_H diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 5791f79053..4f3f1fd16e 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -558,6 +558,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); @@ -605,6 +606,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_VIRTUAL); BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_FROM_SCRIPT); BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_STATIC); + BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_OBJECT_CORE); BIND_CORE_ENUM_CONSTANT(METHOD_FLAGS_DEFAULT); BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_NIL", Variant::NIL); 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 49570cd1c1..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); } @@ -653,7 +660,7 @@ Dictionary NativeExtensionAPIDump::generate_extension_api() { ClassDB::get_method_list(class_name, &method_list, true); for (const MethodInfo &F : method_list) { StringName method_name = F.name; - if (F.flags & METHOD_FLAG_VIRTUAL) { + if ((F.flags & METHOD_FLAG_VIRTUAL) && !(F.flags & METHOD_FLAG_OBJECT_CORE)) { //virtual method const MethodInfo &mi = F; Dictionary d2; @@ -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 e0ebd19376..de107b4156 100644 --- a/core/extension/gdnative_interface.cpp +++ b/core/extension/gdnative_interface.cpp @@ -663,7 +663,23 @@ static const char32_t *gdnative_string_operator_index_const(const GDNativeString /* OBJECT API */ -static void gdnative_object_method_bind_ptrcall(GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr p_ret) { +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) { + MethodBind *mb = (MethodBind *)p_method_bind; + Object *o = (Object *)p_instance; + const Variant **args = (const Variant **)p_args; + Callable::CallError error; + + Variant ret = mb->call(o, args, p_arg_count, error); + memnew_placement(r_return, Variant(ret)); + + if (r_error) { + r_error->error = (GDNativeCallErrorType)(error.error); + r_error->argument = error.argument; + r_error->expected = error.expected; + } +} + +static void gdnative_object_method_bind_ptrcall(const GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr p_ret) { MethodBind *mb = (MethodBind *)p_method_bind; Object *o = (Object *)p_instance; mb->ptrcall(o, (const void **)p_args, p_ret); @@ -829,6 +845,7 @@ void gdnative_setup_interface(GDNativeInterface *p_interface) { /* OBJECT */ + gdni.object_method_bind_call = gdnative_object_method_bind_call; gdni.object_method_bind_ptrcall = gdnative_object_method_bind_ptrcall; gdni.object_destroy = gdnative_object_destroy; gdni.global_get_singleton = gdnative_global_get_singleton; diff --git a/core/extension/gdnative_interface.h b/core/extension/gdnative_interface.h index 3432d8fabe..3a5b04429c 100644 --- a/core/extension/gdnative_interface.h +++ b/core/extension/gdnative_interface.h @@ -389,7 +389,8 @@ typedef struct { /* OBJECT */ - void (*object_method_bind_ptrcall)(GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret); + 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); + void (*object_method_bind_ptrcall)(const GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret); void (*object_destroy)(GDNativeObjectPtr p_o); GDNativeObjectPtr (*global_get_singleton)(const char *p_name); void *(*object_get_instance_binding)(GDNativeObjectPtr p_o, void *p_token, const GDNativeInstanceBindingCallbacks *p_callbacks); @@ -409,7 +410,7 @@ typedef struct { void (*classdb_register_extension_class)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_parent_class_name, const GDNativeExtensionClassCreationInfo *p_extension_funcs); void (*classdb_register_extension_class_method)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativeExtensionClassMethodInfo *p_method_info); - void (*classdb_register_extension_class_integer_constant)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_enum_name, const char *p_class_name, const char *p_constant_name, uint32_t p_constant_value); + void (*classdb_register_extension_class_integer_constant)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, GDNativeInt p_constant_value); void (*classdb_register_extension_class_property)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativePropertyInfo *p_info, const char *p_setter, const char *p_getter); void (*classdb_register_extension_class_signal)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_signal_name, const GDNativePropertyInfo *p_argument_info, GDNativeInt p_argument_count); void (*classdb_unregister_extension_class)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name); /* Unregistering a parent class before a class that inherits it will result in failure. Inheritors must be unregistered first. */ diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp index 91d304627a..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; @@ -69,7 +71,7 @@ public: virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { Variant ret; GDExtensionClassInstancePtr extension_instance = p_object->_get_extension_instance(); - GDNativeCallError ce; + GDNativeCallError ce{ GDNATIVE_CALL_OK, 0, 0 }; call_func(method_userdata, extension_instance, (const GDNativeVariantPtr *)p_args, p_arg_count, (GDNativeVariantPtr)&ret, &ce); r_error.error = Callable::CallError::Error(ce.error); r_error.argument = ce.argument; @@ -154,6 +156,7 @@ void NativeExtension::_register_extension_class(const GDNativeExtensionClassLibr extension->native_extension.create_instance = p_extension_funcs->create_instance_func; extension->native_extension.set_object_instance = p_extension_funcs->object_instance_func; extension->native_extension.free_instance = p_extension_funcs->free_instance_func; + extension->native_extension.get_virtual = p_extension_funcs->get_virtual_func; ClassDB::register_extension_class(&extension->native_extension); } @@ -171,7 +174,7 @@ void NativeExtension::_register_extension_class_method(const GDNativeExtensionCl ClassDB::bind_method_custom(class_name, method); } -void NativeExtension::_register_extension_class_integer_constant(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, uint32_t p_constant_value) { +void NativeExtension::_register_extension_class_integer_constant(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, GDNativeInt p_constant_value) { NativeExtension *self = (NativeExtension *)p_library; StringName class_name = p_class_name; diff --git a/core/extension/native_extension.h b/core/extension/native_extension.h index 8bd95b2867..b661381d64 100644 --- a/core/extension/native_extension.h +++ b/core/extension/native_extension.h @@ -48,7 +48,7 @@ class NativeExtension : public Resource { static void _register_extension_class(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_parent_class_name, const GDNativeExtensionClassCreationInfo *p_extension_funcs); static void _register_extension_class_method(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativeExtensionClassMethodInfo *p_method_info); - static void _register_extension_class_integer_constant(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, uint32_t p_constant_value); + static void _register_extension_class_integer_constant(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, GDNativeInt p_constant_value); static void _register_extension_class_property(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativePropertyInfo *p_info, const char *p_setter, const char *p_getter); static void _register_extension_class_signal(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_signal_name, const GDNativePropertyInfo *p_argument_info, GDNativeInt p_argument_count); static void _unregister_extension_class(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name); @@ -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..c7ca65f61a 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()) { 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 c0aac6d91a..d0cb08724e 100644 --- a/scene/gui/shortcut.cpp +++ b/core/input/shortcut.cpp @@ -32,7 +32,7 @@ #include "core/os/keyboard.h" void Shortcut::set_event(const Ref<InputEvent> &p_event) { - ERR_FAIL_COND(Object::cast_to<InputEventShortcut>(*p_event)); + ERR_FAIL_COND_MSG(Object::cast_to<InputEventShortcut>(*p_event), "Cannot set a shortcut event to an instance of InputEventShortcut."); event = p_event; emit_changed(); } 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..55793aa5a4 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -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/multiplayer_api.cpp b/core/io/multiplayer_api.cpp index f792dc6cb4..0ce9a70921 100644 --- a/core/io/multiplayer_api.cpp +++ b/core/io/multiplayer_api.cpp @@ -32,15 +32,11 @@ #include "core/debugger/engine_debugger.h" #include "core/io/marshalls.h" +#include "core/io/multiplayer_replicator.h" #include "scene/main/node.h" -#include "scene/resources/packed_scene.h" #include <stdint.h> -#define NODE_ID_COMPRESSION_SHIFT 3 -#define NAME_ID_COMPRESSION_SHIFT 5 -#define BYTE_ONLY_OR_NO_ARGS_SHIFT 6 - #ifdef DEBUG_ENABLED #include "core/os/os.h" #endif @@ -147,7 +143,7 @@ void MultiplayerAPI::poll() { } void MultiplayerAPI::clear() { - replicated_nodes.clear(); + replicator->clear(); connected_peers.clear(); path_get_cache.clear(); path_send_cache.clear(); @@ -325,10 +321,10 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ _process_raw(p_from, p_packet, p_packet_len); } break; case NETWORK_COMMAND_SPAWN: { - _process_spawn_despawn(p_from, p_packet, p_packet_len, true); + replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true); } break; case NETWORK_COMMAND_DESPAWN: { - _process_spawn_despawn(p_from, p_packet, p_packet_len, false); + replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false); } break; } } @@ -338,7 +334,6 @@ Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uin 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."); @@ -353,25 +348,11 @@ Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uin if (!node) { ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); } + return node; } else { // Use cached path. - int id = p_node_target; - - Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); - ERR_FAIL_COND_V_MSG(!E, nullptr, "Invalid packet received. Requests invalid peer cache."); - - Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(id); - ERR_FAIL_COND_V_MSG(!F, nullptr, "Invalid packet received. Unabled to find requested cached node."); - - PathGetCache::NodeInfo *ni = &F->get(); - // Do proper caching later. - - node = root_node->get_node(ni->path); - if (!node) { - ERR_PRINT("Failed to get cached path from RPC: " + String(ni->path) + "."); - } + return get_cached_node(p_from, p_node_target); } - return node; } 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) { @@ -425,7 +406,7 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, 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); + 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]; @@ -514,64 +495,6 @@ void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, E->get() = true; } -void MultiplayerAPI::_process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { - ERR_FAIL_COND_MSG(p_packet_len < 18, "Invalid spawn packet received"); - int ofs = 1; - ResourceUID::ID id = decode_uint64(&p_packet[ofs]); - ofs += 8; - ERR_FAIL_COND_MSG(!spawnables.has(id), "Invalid spawn ID received " + itos(id)); - - uint32_t node_target = decode_uint32(&p_packet[ofs]); - Node *parent = _process_get_node(p_from, nullptr, node_target, 0); - ofs += 4; - ERR_FAIL_COND_MSG(parent == nullptr, "Invalid packet received. Requested node was not found."); - - uint32_t name_len = decode_uint32(&p_packet[ofs]); - ofs += 4; - ERR_FAIL_COND_MSG(name_len > uint32_t(p_packet_len - ofs), vformat("Invalid spawn packet size: %d, wants: %d", p_packet_len, ofs + name_len)); - ERR_FAIL_COND_MSG(name_len < 1, "Zero spawn name size."); - - const String name = String::utf8((const char *)&p_packet[ofs], name_len); - // We need to make sure no trickery happens here (e.g. despawning a subpath), but we want to allow autogenerated ("@") node names. - ERR_FAIL_COND_MSG(name.validate_node_name() != name.replace("@", ""), vformat("Invalid node name received: '%s'", name)); - ofs += name_len; - PackedByteArray data; - if (p_packet_len > ofs) { - data.resize(p_packet_len - ofs); - memcpy(data.ptrw(), &p_packet[ofs], data.size()); - } - - SpawnMode mode = spawnables[id]; - if (mode == SPAWN_MODE_SERVER && p_from == 1) { - String scene_path = ResourceUID::get_singleton()->get_id_path(id); - if (p_spawn) { - 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); - ERR_FAIL_COND_MSG(!res.is_valid(), "Unable to load scene to spawn at path: " + scene_path); - PackedScene *scene = Object::cast_to<PackedScene>(res.ptr()); - ERR_FAIL_COND(!scene); - Node *node = scene->instantiate(); - ERR_FAIL_COND(!node); - replicated_nodes[node->get_instance_id()] = id; - parent->_add_child_nocheck(node, name); - emit_signal(SNAME("network_spawn"), p_from, id, node, data); - } else { - ERR_FAIL_COND_MSG(!parent->has_node(name), vformat("Path not found: %s/%s", parent->get_path(), name)); - 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("network_despawn"), p_from, id, node, data); - replicated_nodes.erase(node->get_instance_id()); - node->queue_delete(); - } - } else { - if (p_spawn) { - emit_signal(SNAME("network_spawn_request"), p_from, id, parent, name, data); - } else { - emit_signal(SNAME("network_despawn_request"), p_from, id, parent, name, data); - } - } -} - 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. @@ -647,7 +570,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC #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) { +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); @@ -722,7 +645,7 @@ Error MultiplayerAPI::_encode_and_compress_variant(const Variant &p_variant, uin return OK; } -Error MultiplayerAPI::_decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) { +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; @@ -906,10 +829,10 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const ofs += 1; for (int i = 0; i < p_argcount; i++) { int len(0); - Error err = _encode_and_compress_variant(*p_arg[i], nullptr, len); + 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); + encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len); ofs += len; } } @@ -976,14 +899,7 @@ void MultiplayerAPI::_add_peer(int p_id) { connected_peers.insert(p_id); path_get_cache.insert(p_id, PathGetCache()); if (is_network_server()) { - for (const KeyValue<ObjectID, ResourceUID::ID> &E : replicated_nodes) { - // Only server mode adds to replicated_nodes, no need to check it. - Object *obj = ObjectDB::get_instance(E.key); - ERR_CONTINUE(!obj); - Node *node = Object::cast_to<Node>(obj); - ERR_CONTINUE(!node); - _send_spawn_despawn(p_id, E.value, node->get_path(), nullptr, 0, true); - } + replicator->spawn_all(p_id); } emit_signal(SNAME("network_peer_connected"), p_id); } @@ -1000,10 +916,6 @@ void MultiplayerAPI::_del_peer(int p_id) { PathSentCache *psc = path_send_cache.getptr(E); psc->confirmed_peers.erase(p_id); } - if (p_id == 1) { - // Erase server replicated nodes, but do not queue them for deletion. - replicated_nodes.clear(); - } emit_signal(SNAME("network_peer_disconnected"), p_id); } @@ -1109,6 +1021,36 @@ void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_pac 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(); @@ -1147,97 +1089,12 @@ bool MultiplayerAPI::is_object_decoding_allowed() const { return allow_object_decoding; } -Error MultiplayerAPI::spawnable_config(const ResourceUID::ID &p_id, SpawnMode p_mode) { - ERR_FAIL_COND_V(p_mode < SPAWN_MODE_NONE || p_mode > SPAWN_MODE_CUSTOM, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); -#ifdef TOOLS_ENABLED - String path = ResourceUID::get_singleton()->get_id_path(p_id); - RES res = ResourceLoader::load(path); - ERR_FAIL_COND_V(!res->is_class("PackedScene"), ERR_INVALID_PARAMETER); -#endif - if (p_mode == SPAWN_MODE_NONE) { - if (spawnables.has(p_id)) { - spawnables.erase(p_id); - } - } else { - spawnables[p_id] = p_mode; - } - return OK; -} - -Error MultiplayerAPI::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data) { - return _send_spawn_despawn(p_peer_id, p_scene_id, p_path, p_data.ptr(), p_data.size(), false); +MultiplayerReplicator *MultiplayerAPI::get_replicator() const { + return replicator; } -Error MultiplayerAPI::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data) { - return _send_spawn_despawn(p_peer_id, p_scene_id, p_path, p_data.ptr(), p_data.size(), true); -} - -Error MultiplayerAPI::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const uint8_t *p_data, int p_data_len, bool p_spawn) { - ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED); - ERR_FAIL_COND_V_MSG(!spawnables.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); - - NodePath rel_path = (root_node->get_path()).rel_path_to(p_path); - const Vector<StringName> names = rel_path.get_names(); - ERR_FAIL_COND_V(names.size() < 2, ERR_INVALID_PARAMETER); - - NodePath parent = NodePath(names.subarray(0, names.size() - 2), false); - ERR_FAIL_COND_V_MSG(!root_node->has_node(parent), ERR_INVALID_PARAMETER, "Path not found: " + parent); - - // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(parent); - if (!psc) { - // Path is not cached, create. - path_send_cache[parent] = PathSentCache(); - psc = path_send_cache.getptr(parent); - psc->id = last_send_cache_id++; - } - _send_confirm_path(root_node->get_node(parent), parent, psc, p_peer_id); - - const CharString cname = String(names[names.size() - 1]).utf8(); - int nlen = encode_cstring(cname.get_data(), nullptr); - MAKE_ROOM(1 + 8 + 4 + 4 + nlen + p_data_len); - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = p_spawn ? NETWORK_COMMAND_SPAWN : NETWORK_COMMAND_DESPAWN; - int ofs = 1; - ofs += encode_uint64(p_scene_id, &ptr[ofs]); - ofs += encode_uint32(psc->id, &ptr[ofs]); - ofs += encode_uint32(nlen, &ptr[ofs]); - ofs += encode_cstring(cname.get_data(), &ptr[ofs]); - memcpy(&ptr[ofs], p_data, p_data_len); - 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 + p_data_len); -} - -void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, const Node *p_node, bool p_enter) { - if (!has_network_peer()) { - return; - } - ERR_FAIL_COND(!p_node || !p_node->get_parent() || !root_node); - NodePath path = (root_node->get_path()).rel_path_to(p_node->get_parent()->get_path()); - if (path.is_empty()) { - return; - } - ResourceUID::ID id = ResourceLoader::get_resource_uid(p_scene); - if (!spawnables.has(id)) { - return; - } - SpawnMode mode = spawnables[id]; - if (p_enter) { - if (mode == SPAWN_MODE_SERVER && is_network_server()) { - replicated_nodes[p_node->get_instance_id()] = id; - _send_spawn_despawn(0, id, p_node->get_path(), nullptr, 0, true); - } - emit_signal(SNAME("network_spawnable_added"), id, p_node); - } else { - if (mode == SPAWN_MODE_SERVER && is_network_server() && replicated_nodes.has(p_node->get_instance_id())) { - replicated_nodes.erase(p_node->get_instance_id()); - _send_spawn_despawn(0, id, p_node->get_path(), nullptr, 0, false); - } - emit_signal(SNAME("network_spawnable_removed"), id, p_node); - } +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() { @@ -1258,15 +1115,14 @@ void MultiplayerAPI::_bind_methods() { 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("spawnable_config", "scene_id", "spawn_mode"), &MultiplayerAPI::spawnable_config); - ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "path", "data"), &MultiplayerAPI::send_despawn, DEFVAL(PackedByteArray())); - ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "path", "data"), &MultiplayerAPI::send_spawn, DEFVAL(PackedByteArray())); + 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"))); @@ -1274,27 +1130,19 @@ void MultiplayerAPI::_bind_methods() { ADD_SIGNAL(MethodInfo("connected_to_server")); ADD_SIGNAL(MethodInfo("connection_failed")); ADD_SIGNAL(MethodInfo("server_disconnected")); - ADD_SIGNAL(MethodInfo("network_despawn", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); - ADD_SIGNAL(MethodInfo("network_spawn", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); - ADD_SIGNAL(MethodInfo("network_despawn_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); - ADD_SIGNAL(MethodInfo("network_spawn_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); - ADD_SIGNAL(MethodInfo("network_spawnable_added", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - ADD_SIGNAL(MethodInfo("network_spawnable_removed", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); BIND_ENUM_CONSTANT(RPC_MODE_REMOTE); BIND_ENUM_CONSTANT(RPC_MODE_MASTER); BIND_ENUM_CONSTANT(RPC_MODE_PUPPET); - - BIND_ENUM_CONSTANT(SPAWN_MODE_NONE); - BIND_ENUM_CONSTANT(SPAWN_MODE_SERVER); - BIND_ENUM_CONSTANT(SPAWN_MODE_CUSTOM); } MultiplayerAPI::MultiplayerAPI() { + replicator = memnew(MultiplayerReplicator(this)); clear(); } MultiplayerAPI::~MultiplayerAPI() { clear(); + memdelete(replicator); } diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h index 9552a0bf35..5853541efa 100644 --- a/core/io/multiplayer_api.h +++ b/core/io/multiplayer_api.h @@ -35,6 +35,8 @@ #include "core/io/resource_uid.h" #include "core/object/ref_counted.h" +class MultiplayerReplicator; + class MultiplayerAPI : public RefCounted { GDCLASS(MultiplayerAPI, RefCounted); @@ -46,12 +48,6 @@ public: RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets }; - enum SpawnMode { - SPAWN_MODE_NONE, - SPAWN_MODE_SERVER, - SPAWN_MODE_CUSTOM, - }; - struct RPCConfig { StringName name; RPCMode rpc_mode = RPC_MODE_DISABLED; @@ -71,6 +67,32 @@ public: } }; + enum NetworkCommands { + NETWORK_COMMAND_REMOTE_CALL = 0, + NETWORK_COMMAND_SIMPLIFY_PATH, + NETWORK_COMMAND_CONFIRM_PATH, + NETWORK_COMMAND_RAW, + NETWORK_COMMAND_SPAWN, + NETWORK_COMMAND_DESPAWN, + }; + + 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, + }; + + enum { + NODE_ID_COMPRESSION_SHIFT = 3, + NAME_ID_COMPRESSION_SHIFT = 5, + BYTE_ONLY_OR_NO_ARGS_SHIFT = 6, + }; + private: //path sent caches struct PathSentCache { @@ -89,16 +111,15 @@ private: }; Ref<MultiplayerPeer> network_peer; - Map<ResourceUID::ID, SpawnMode> spawnables; int rpc_sender_id = 0; Set<int> connected_peers; HashMap<NodePath, PathSentCache> path_send_cache; Map<int, PathGetCache> path_get_cache; - Map<ObjectID, ResourceUID::ID> replicated_nodes; int last_send_cache_id; Vector<uint8_t> packet_cache; Node *root_node = nullptr; bool allow_object_decoding = false; + MultiplayerReplicator *replicator = nullptr; protected: static void _bind_methods(); @@ -106,7 +127,6 @@ protected: 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); - void _process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn); 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); @@ -114,30 +134,7 @@ protected: 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); - 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); - public: - enum NetworkCommands { - NETWORK_COMMAND_REMOTE_CALL = 0, - NETWORK_COMMAND_SIMPLIFY_PATH, - NETWORK_COMMAND_CONFIRM_PATH, - NETWORK_COMMAND_RAW, - NETWORK_COMMAND_SPAWN, - NETWORK_COMMAND_DESPAWN, - }; - - 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, - }; - void poll(); void clear(); void set_root_node(Node *p_node); @@ -146,8 +143,16 @@ public: 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); + 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); + // 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); void _add_peer(int p_id); void _del_peer(int p_id); @@ -166,17 +171,12 @@ public: void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; - Error spawnable_config(const ResourceUID::ID &p_id, SpawnMode p_mode); - Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data = PackedByteArray()); - Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data = PackedByteArray()); - Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const uint8_t *p_data, int p_data_len, bool p_spawn); - void scene_enter_exit_notify(const String &p_scene, const Node *p_node, bool p_enter); + MultiplayerReplicator *get_replicator() const; MultiplayerAPI(); ~MultiplayerAPI(); }; VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode); -VARIANT_ENUM_CAST(MultiplayerAPI::SpawnMode); #endif // MULTIPLAYER_API_H diff --git a/core/io/multiplayer_replicator.cpp b/core/io/multiplayer_replicator.cpp new file mode 100644 index 0000000000..ba0fe32b58 --- /dev/null +++ b/core/io/multiplayer_replicator.cpp @@ -0,0 +1,497 @@ +/*************************************************************************/ +/* multiplayer_replicator.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/io/multiplayer_replicator.h" + +#include "core/io/marshalls.h" +#include "scene/main/node.h" +#include "scene/resources/packed_scene.h" + +#define MAKE_ROOM(m_amount) \ + if (packet_cache.size() < m_amount) \ + packet_cache.resize(m_amount); + +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); + Error err; + // Prepare state + List<Variant> state_variants; + int state_len = 0; + const SceneConfig &cfg = replications[p_scene_id]; + if (p_spawn) { + if ((err = _get_state(cfg.properties, p_obj, state_variants)) != OK) { + return err; + } + } + + bool is_raw = false; + if (state_variants.size() == 1 && state_variants[0].get_type() == Variant::PACKED_BYTE_ARRAY) { + is_raw = true; + } else if (state_variants.size()) { + err = _encode_state(state_variants, nullptr, state_len); + ERR_FAIL_COND_V(err, err); + } else { + is_raw = true; + } + + int ofs = 0; + + // Prepare simplified path + const Node *root_node = multiplayer->get_root_node(); + ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED); + NodePath rel_path = (root_node->get_path()).rel_path_to(p_path); + const Vector<StringName> names = rel_path.get_names(); + ERR_FAIL_COND_V(names.size() < 2, ERR_INVALID_PARAMETER); + + NodePath parent = NodePath(names.subarray(0, names.size() - 2), false); + ERR_FAIL_COND_V_MSG(!root_node->has_node(parent), ERR_INVALID_PARAMETER, "Path not found: " + parent); + + int path_id = 0; + multiplayer->send_confirm_path(root_node->get_node(parent), parent, p_peer_id, path_id); + + // Encode name and parent ID. + CharString cname = String(names[names.size() - 1]).utf8(); + 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); + ofs = 1; + ofs += encode_uint64(p_scene_id, &ptr[ofs]); + ofs += encode_uint32(path_id, &ptr[ofs]); + ofs += encode_uint32(nlen, &ptr[ofs]); + ofs += encode_cstring(cname.get_data(), &ptr[ofs]); + + // Encode state. + if (!is_raw) { + _encode_state(state_variants, &ptr[ofs], state_len); + } else if (state_len) { + PackedByteArray pba = state_variants[0]; + 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); +} + +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) { + ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET + 9, "Invalid spawn packet received"); + int ofs = SPAWN_CMD_OFFSET; + uint32_t node_target = decode_uint32(&p_packet[ofs]); + Node *parent = multiplayer->get_cached_node(p_from, node_target); + ofs += 4; + ERR_FAIL_COND_MSG(parent == nullptr, "Invalid packet received. Requested node was not found."); + + uint32_t name_len = decode_uint32(&p_packet[ofs]); + ofs += 4; + ERR_FAIL_COND_MSG(name_len > uint32_t(p_packet_len - ofs), vformat("Invalid spawn packet size: %d, wants: %d", p_packet_len, ofs + name_len)); + ERR_FAIL_COND_MSG(name_len < 1, "Zero spawn name size."); + + const String name = String::utf8((const char *)&p_packet[ofs], name_len); + // We need to make sure no trickery happens here (e.g. despawning a subpath), but we want to allow autogenerated ("@") node names. + ERR_FAIL_COND_MSG(name.validate_node_name() != name.replace("@", ""), vformat("Invalid node name received: '%s'", name)); + ofs += name_len; + + const SceneConfig &cfg = replications[p_scene_id]; + 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; + + 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); + ERR_FAIL_COND_MSG(!res.is_valid(), "Unable to load scene to spawn at path: " + scene_path); + PackedScene *scene = Object::cast_to<PackedScene>(res.ptr()); + ERR_FAIL_COND(!scene); + Node *node = scene->instantiate(); + ERR_FAIL_COND(!node); + replicated_nodes[node->get_instance_id()] = p_scene_id; + int size; + _decode_state(cfg.properties, node, &p_packet[ofs], p_packet_len - ofs, size, is_raw); + parent->_add_child_nocheck(node, name); + emit_signal(SNAME("spawned"), p_scene_id, node); + } else { + ERR_FAIL_COND_MSG(!parent->has_node(name), vformat("Path not found: %s/%s", parent->get_path(), name)); + 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); + replicated_nodes.erase(node->get_instance_id()); + node->queue_delete(); + } + } else { + PackedByteArray data; + if (p_packet_len > ofs) { + data.resize(p_packet_len - ofs); + memcpy(data.ptrw(), &p_packet[ofs], data.size()); + } + if (p_spawn) { + emit_signal(SNAME("spawn_requested"), p_from, p_scene_id, parent, name, data); + } else { + emit_signal(SNAME("despawn_requested"), p_from, p_scene_id, parent, name, data); + } + } +} + +void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { + 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_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; + Variant data; + int left = p_packet_len - ofs; + if (is_raw && left) { + PackedByteArray pba; + pba.resize(left); + memcpy(pba.ptrw(), &p_packet[ofs], pba.size()); + data = pba; + } else if (left) { + ERR_FAIL_COND(decode_variant(data, &p_packet[ofs], left) != OK); + } + + Variant args[4]; + args[0] = p_from; + args[1] = id; + args[2] = data; + args[3] = p_spawn; + const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] }; + Callable::CallError ce; + Variant ret; + cfg.on_spawn_despawn_receive.call(argp, 4, ret, ce); + ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom receive function failed"); + } else { + _process_default_spawn_despawn(p_from, id, p_packet, p_packet_len, p_spawn); + } +} + +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) { + bool valid = false; + const Variant v = p_obj->get(prop, &valid); + ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop)); + r_variant.push_back(v); + } + return OK; +} + +Error MultiplayerReplicator::_encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw) { + r_len = 0; + int size = 0; + + // Try raw encoding optimization. + if (r_raw && p_variants.size() == 1) { + *r_raw = false; + const Variant v = p_variants[0]; + if (v.get_type() == Variant::PACKED_BYTE_ARRAY) { + *r_raw = true; + const PackedByteArray pba = v; + if (p_buffer) { + memcpy(p_buffer, pba.ptr(), pba.size()); + } + r_len += pba.size(); + } else { + multiplayer->encode_and_compress_variant(v, p_buffer, size); + r_len += size; + } + return OK; + } + + // Regular encoding. + for (const Variant &v : p_variants) { + multiplayer->encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size); + r_len += size; + } + return OK; +} + +Error MultiplayerReplicator::_decode_state(const List<StringName> &p_properties, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw) { + r_len = 0; + int argc = p_properties.size(); + if (argc == 0 && p_raw) { + ERR_FAIL_COND_V_MSG(p_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes."); + return OK; + } + ERR_FAIL_COND_V(p_raw && argc != 1, ERR_INVALID_DATA); + if (p_raw) { + r_len = p_len; + PackedByteArray pba; + pba.resize(p_len); + memcpy(pba.ptrw(), p_buffer, p_len); + p_obj->set(p_properties[0], pba); + return OK; + } + + Vector<Variant> args; + Vector<const Variant *> argp; + args.resize(argc); + + for (int i = 0; i < argc; i++) { + ERR_FAIL_COND_V_MSG(r_len >= p_len, ERR_INVALID_DATA, "Invalid packet received. Size too small."); + + int vlen; + Error err = multiplayer->decode_and_decompress_variant(args.write[i], &p_buffer[r_len], p_len - r_len, &vlen); + ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable."); + r_len += vlen; + } + ERR_FAIL_COND_V_MSG(p_len - r_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes."); + + int i = 0; + for (const StringName &prop : p_properties) { + p_obj->set(prop, args[i]); + i += 1; + } + return OK; +} + +Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props, const Callable &p_on_send, const Callable &p_on_recv) { + ERR_FAIL_COND_V(p_mode < REPLICATION_MODE_NONE || p_mode > REPLICATION_MODE_CUSTOM, ERR_INVALID_PARAMETER); + 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"); +#ifdef TOOLS_ENABLED + if (!p_on_send.is_valid()) { + // We allow non scene spawning with custom callables. + String path = ResourceUID::get_singleton()->get_id_path(p_id); + RES res = ResourceLoader::load(path); + ERR_FAIL_COND_V(!res->is_class("PackedScene"), ERR_INVALID_PARAMETER); + } +#endif + if (p_mode == REPLICATION_MODE_NONE) { + if (replications.has(p_id)) { + replications.erase(p_id); + } + } else { + SceneConfig cfg; + cfg.mode = p_mode; + for (int i = 0; i < p_props.size(); i++) { + cfg.properties.push_back(StringName(p_props[i])); + } + cfg.on_spawn_despawn_send = p_on_send; + cfg.on_spawn_despawn_receive = p_on_recv; + replications[p_id] = cfg; + } + 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; + if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) { + const PackedByteArray pba = p_data; + is_raw = true; + data_size = p_data.operator PackedByteArray().size(); + } else if (p_data.get_type() == Variant::NIL) { + is_raw = true; + } else { + Error err = encode_variant(p_data, nullptr, data_size); + ERR_FAIL_COND_V(err, err); + } + 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); + encode_uint64(p_scene_id, &ptr[1]); + if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) { + const PackedByteArray pba = p_data; + memcpy(&ptr[SPAWN_CMD_OFFSET], pba.ptr(), pba.size()); + } 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); +} + +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_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."); + NodePath path = p_path; + Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr; + if (path.is_empty() && obj) { + Node *node = Object::cast_to<Node>(obj); + if (node && node->is_inside_tree()) { + path = node->get_path(); + } + } + ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Despawn default implementation requires a despawn path, or the data to be a node inside the SceneTree"); + return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, false); + } +} + +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_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."); + 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."); + if (path.is_empty()) { + Node *node = Object::cast_to<Node>(obj); + if (node && node->is_inside_tree()) { + path = node->get_path(); + } + } + ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Spawn default implementation requires a spawn path, or the data to be a node inside the SceneTree"); + return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, true); + } +} + +Error MultiplayerReplicator::_spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn) { + 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()) { + Variant args[4]; + args[0] = p_peer; + args[1] = p_scene_id; + args[2] = p_obj; + args[3] = true; + const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] }; + Callable::CallError ce; + Variant ret; + cfg.on_spawn_despawn_send.call(argp, 4, ret, ce); + ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom send function failed"); + return OK; + } else { + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Only nodes can be replicated by the default implementation"); + return _send_default_spawn_despawn(p_peer, p_scene_id, node, node->get_path(), p_spawn); + } +} + +Error MultiplayerReplicator::spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) { + return _spawn_despawn(p_scene_id, p_obj, p_peer, true); +} + +Error MultiplayerReplicator::despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) { + 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 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); + 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."); + state.resize(len); + _encode_state(state_vars, state.ptrw(), len); + return state; +} + +Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data) { + 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]; + int size; + return _decode_state(cfg.properties, 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()) { + return; + } + Node *root_node = multiplayer->get_root_node(); + ERR_FAIL_COND(!p_node || !p_node->get_parent() || !root_node); + NodePath path = (root_node->get_path()).rel_path_to(p_node->get_parent()->get_path()); + if (path.is_empty()) { + return; + } + ResourceUID::ID id = ResourceLoader::get_resource_uid(p_scene); + if (!replications.has(id)) { + return; + } + const SceneConfig &cfg = replications[id]; + if (p_enter) { + if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server()) { + replicated_nodes[p_node->get_instance_id()] = id; + 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())) { + replicated_nodes.erase(p_node->get_instance_id()); + despawn(id, p_node, 0); + } + emit_signal(SNAME("replicated_instance_removed"), id, p_node); + } +} + +void MultiplayerReplicator::spawn_all(int p_peer) { + for (const KeyValue<ObjectID, ResourceUID::ID> &E : replicated_nodes) { + // Only server mode adds to replicated_nodes, no need to check it. + Object *obj = ObjectDB::get_instance(E.key); + ERR_CONTINUE(!obj); + Node *node = Object::cast_to<Node>(obj); + ERR_CONTINUE(!node); + spawn(E.value, node, p_peer); + } +} + +void MultiplayerReplicator::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("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); + + 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"))); + ADD_SIGNAL(MethodInfo("despawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("spawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("replicated_instance_added", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("replicated_instance_removed", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + + BIND_ENUM_CONSTANT(REPLICATION_MODE_NONE); + BIND_ENUM_CONSTANT(REPLICATION_MODE_SERVER); + BIND_ENUM_CONSTANT(REPLICATION_MODE_CUSTOM); +} diff --git a/core/io/multiplayer_replicator.h b/core/io/multiplayer_replicator.h new file mode 100644 index 0000000000..e19dd80602 --- /dev/null +++ b/core/io/multiplayer_replicator.h @@ -0,0 +1,99 @@ +/*************************************************************************/ +/* multiplayer_replicator.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_REPLICATOR_H +#define MULTIPLAYER_REPLICATOR_H + +#include "core/io/multiplayer_api.h" +#include "core/variant/typed_array.h" + +class MultiplayerReplicator : public Object { + GDCLASS(MultiplayerReplicator, Object); + +public: + enum { + SPAWN_CMD_OFFSET = 9, + }; + + enum ReplicationMode { + REPLICATION_MODE_NONE, + REPLICATION_MODE_SERVER, + REPLICATION_MODE_CUSTOM, + }; + + struct SceneConfig { + ReplicationMode mode; + List<StringName> properties; + Callable on_spawn_despawn_send; + Callable on_spawn_despawn_receive; + }; + +protected: + static void _bind_methods(); + +private: + MultiplayerAPI *multiplayer = nullptr; + Vector<uint8_t> packet_cache; + Map<ResourceUID::ID, SceneConfig> replications; + Map<ObjectID, ResourceUID::ID> replicated_nodes; + + 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); + 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); + +public: + void clear(); + + 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); + + // 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 scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); + + MultiplayerReplicator(MultiplayerAPI *p_multiplayer) { + multiplayer = p_multiplayer; + } +}; + +VARIANT_ENUM_CAST(MultiplayerReplicator::ReplicationMode); + +#endif // MULTIPLAYER_REPLICATOR_H diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 0262655927..87b4d7195d 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -352,9 +352,8 @@ Node *Resource::get_local_scene() const { } void Resource::setup_local_to_scene() { - if (get_script_instance()) { - get_script_instance()->call("_setup_local_to_scene"); - } + // Can't use GDVIRTUAL in Resource, so this will have to be done with a signal + emit_signal(SNAME("setup_local_to_scene_requested")); } Node *(*Resource::_get_local_scene_func)() = nullptr; @@ -422,12 +421,12 @@ void Resource::_bind_methods() { ClassDB::bind_method(D_METHOD("duplicate", "subresources"), &Resource::duplicate, DEFVAL(false)); ADD_SIGNAL(MethodInfo("changed")); + ADD_SIGNAL(MethodInfo("setup_local_to_scene_requested")); + ADD_GROUP("Resource", "resource_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resource_local_to_scene"), "set_local_to_scene", "is_local_to_scene"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_path", "get_path"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "resource_name"), "set_name", "get_name"); - - BIND_VMETHOD(MethodInfo("_setup_local_to_scene")); } Resource::Resource() : diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index d02d827443..64237f3b15 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -68,25 +68,28 @@ bool ResourceFormatLoader::recognize_path(const String &p_path, const String &p_ } bool ResourceFormatLoader::handles_type(const String &p_type) const { - if (get_script_instance() && get_script_instance()->has_method("_handles_type")) { - // I guess custom loaders for custom resources should use "Resource" - return get_script_instance()->call("_handles_type", p_type); + bool success; + if (GDVIRTUAL_CALL(_handles_type, p_type, success)) { + return success; } return false; } String ResourceFormatLoader::get_resource_type(const String &p_path) const { - if (get_script_instance() && get_script_instance()->has_method("_get_resource_type")) { - return get_script_instance()->call("_get_resource_type", p_path); + String ret; + + if (GDVIRTUAL_CALL(_get_resource_type, p_path, ret)) { + return ret; } return ""; } ResourceUID::ID ResourceFormatLoader::get_resource_uid(const String &p_path) const { - if (get_script_instance() && get_script_instance()->has_method("_get_resource_uid")) { - return get_script_instance()->call("_get_resource_uid", p_path); + int64_t uid; + if (GDVIRTUAL_CALL(_get_resource_uid, p_path, uid)) { + return uid; } return ResourceUID::INVALID_ID; @@ -105,27 +108,26 @@ void ResourceLoader::get_recognized_extensions_for_type(const String &p_type, Li } bool ResourceFormatLoader::exists(const String &p_path) const { + bool success; + if (GDVIRTUAL_CALL(_exists, p_path, success)) { + return success; + } return FileAccess::exists(p_path); //by default just check file } void ResourceFormatLoader::get_recognized_extensions(List<String> *p_extensions) const { - if (get_script_instance() && get_script_instance()->has_method("_get_recognized_extensions")) { - PackedStringArray exts = get_script_instance()->call("_get_recognized_extensions"); - - { - const String *r = exts.ptr(); - for (int i = 0; i < exts.size(); ++i) { - p_extensions->push_back(r[i]); - } + PackedStringArray exts; + if (GDVIRTUAL_CALL(_get_recognized_extensions, exts)) { + const String *r = exts.ptr(); + for (int i = 0; i < exts.size(); ++i) { + p_extensions->push_back(r[i]); } } } RES ResourceFormatLoader::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) { - // Check user-defined loader if there's any. Hard fail if it returns an error. - if (get_script_instance() && get_script_instance()->has_method("_load")) { - Variant res = get_script_instance()->call("_load", p_path, p_original_path, p_use_sub_threads, p_cache_mode); - + Variant res; + if (GDVIRTUAL_CALL(_load, p_path, p_original_path, p_use_sub_threads, p_cache_mode, res)) { if (res.get_type() == Variant::INT) { // Error code, abort. if (r_error) { *r_error = (Error)res.operator int64_t(); @@ -143,48 +145,42 @@ RES ResourceFormatLoader::load(const String &p_path, const String &p_original_pa } void ResourceFormatLoader::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) { - if (get_script_instance() && get_script_instance()->has_method("_get_dependencies")) { - PackedStringArray deps = get_script_instance()->call("_get_dependencies", p_path, p_add_types); - - { - const String *r = deps.ptr(); - for (int i = 0; i < deps.size(); ++i) { - p_dependencies->push_back(r[i]); - } + PackedStringArray deps; + if (GDVIRTUAL_CALL(_get_dependencies, p_path, p_add_types, deps)) { + const String *r = deps.ptr(); + for (int i = 0; i < deps.size(); ++i) { + p_dependencies->push_back(r[i]); } } } Error ResourceFormatLoader::rename_dependencies(const String &p_path, const Map<String, String> &p_map) { - if (get_script_instance() && get_script_instance()->has_method("_rename_dependencies")) { - Dictionary deps_dict; - for (Map<String, String>::Element *E = p_map.front(); E; E = E->next()) { - deps_dict[E->key()] = E->value(); - } + Dictionary deps_dict; + for (Map<String, String>::Element *E = p_map.front(); E; E = E->next()) { + deps_dict[E->key()] = E->value(); + } - int64_t res = get_script_instance()->call("_rename_dependencies", deps_dict); - return (Error)res; + int64_t err; + if (GDVIRTUAL_CALL(_rename_dependencies, p_path, deps_dict, err)) { + return (Error)err; } return OK; } void ResourceFormatLoader::_bind_methods() { - { - MethodInfo info = MethodInfo(Variant::NIL, "_load", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "original_path"), PropertyInfo(Variant::BOOL, "use_sub_threads"), PropertyInfo(Variant::INT, "cache_mode")); - info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(info); - } - - BIND_VMETHOD(MethodInfo(Variant::PACKED_STRING_ARRAY, "_get_recognized_extensions")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_handles_type", PropertyInfo(Variant::STRING_NAME, "typename"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_resource_type", PropertyInfo(Variant::STRING, "path"))); - BIND_VMETHOD(MethodInfo("_get_dependencies", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "add_types"))); - BIND_VMETHOD(MethodInfo(Variant::INT, "_rename_dependencies", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "renames"))); - BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE); BIND_ENUM_CONSTANT(CACHE_MODE_REUSE); BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE); + + GDVIRTUAL_BIND(_get_recognized_extensions); + GDVIRTUAL_BIND(_handles_type, "type"); + GDVIRTUAL_BIND(_get_resource_type, "path"); + GDVIRTUAL_BIND(_get_resource_uid, "path"); + GDVIRTUAL_BIND(_get_dependencies, "path", "add_types"); + GDVIRTUAL_BIND(_rename_dependencies, "path", "renames"); + GDVIRTUAL_BIND(_exists, "path"); + GDVIRTUAL_BIND(_load, "path", "original_path", "use_sub_threads", "cache_mode"); } /////////////////////////////////// diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index e525e80a9d..f1d9815635 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -32,6 +32,8 @@ #define RESOURCE_LOADER_H #include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" #include "core/os/semaphore.h" #include "core/os/thread.h" @@ -48,6 +50,16 @@ public: protected: static void _bind_methods(); + GDVIRTUAL0RC(Vector<String>, _get_recognized_extensions) + GDVIRTUAL1RC(bool, _handles_type, StringName) + GDVIRTUAL1RC(String, _get_resource_type, String) + GDVIRTUAL1RC(ResourceUID::ID, _get_resource_uid, String) + GDVIRTUAL2RC(Vector<String>, _get_dependencies, String, bool) + GDVIRTUAL2RC(int64_t, _rename_dependencies, String, Dictionary) + GDVIRTUAL1RC(bool, _exists, String) + + GDVIRTUAL4RC(Variant, _load, String, String, bool, int) + 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 bool exists(const String &p_path) const; diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp index 564de5ee69..823b5f75b1 100644 --- a/core/io/resource_saver.cpp +++ b/core/io/resource_saver.cpp @@ -42,44 +42,37 @@ ResourceSavedCallback ResourceSaver::save_callback = nullptr; ResourceSaverGetResourceIDForPath ResourceSaver::save_get_id_for_path = nullptr; Error ResourceFormatSaver::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { - if (get_script_instance() && get_script_instance()->has_method("_save")) { - return (Error)get_script_instance()->call("_save", p_path, p_resource, p_flags).operator int64_t(); + int64_t res; + if (GDVIRTUAL_CALL(_save, p_path, p_resource, p_flags, res)) { + return (Error)res; } return ERR_METHOD_NOT_FOUND; } bool ResourceFormatSaver::recognize(const RES &p_resource) const { - if (get_script_instance() && get_script_instance()->has_method("_recognize")) { - return get_script_instance()->call("_recognize", p_resource); + bool success; + if (GDVIRTUAL_CALL(_recognize, p_resource, success)) { + return success; } return false; } void ResourceFormatSaver::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { - if (get_script_instance() && get_script_instance()->has_method("_get_recognized_extensions")) { - PackedStringArray exts = get_script_instance()->call("_get_recognized_extensions", p_resource); - - { - const String *r = exts.ptr(); - for (int i = 0; i < exts.size(); ++i) { - p_extensions->push_back(r[i]); - } + PackedStringArray exts; + if (GDVIRTUAL_CALL(_get_recognized_extensions, p_resource, exts)) { + const String *r = exts.ptr(); + for (int i = 0; i < exts.size(); ++i) { + p_extensions->push_back(r[i]); } } } void ResourceFormatSaver::_bind_methods() { - { - PropertyInfo arg0 = PropertyInfo(Variant::STRING, "path"); - PropertyInfo arg1 = PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"); - PropertyInfo arg2 = PropertyInfo(Variant::INT, "flags"); - BIND_VMETHOD(MethodInfo(Variant::INT, "_save", arg0, arg1, arg2)); - } - - BIND_VMETHOD(MethodInfo(Variant::PACKED_STRING_ARRAY, "_get_recognized_extensions", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_recognize", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"))); + GDVIRTUAL_BIND(_save, "path", "resource", "flags"); + GDVIRTUAL_BIND(_recognize, "resource"); + GDVIRTUAL_BIND(_get_recognized_extensions, "resource"); } Error ResourceSaver::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { diff --git a/core/io/resource_saver.h b/core/io/resource_saver.h index 2fc8d32126..fcde835dab 100644 --- a/core/io/resource_saver.h +++ b/core/io/resource_saver.h @@ -32,6 +32,8 @@ #define RESOURCE_SAVER_H #include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" class ResourceFormatSaver : public RefCounted { GDCLASS(ResourceFormatSaver, RefCounted); @@ -39,6 +41,10 @@ class ResourceFormatSaver : public RefCounted { protected: static void _bind_methods(); + GDVIRTUAL3R(int64_t, _save, String, RES, uint32_t) + GDVIRTUAL1RC(bool, _recognize, RES) + GDVIRTUAL1RC(Vector<String>, _get_recognized_extensions, RES) + public: virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); virtual bool recognize(const RES &p_resource) const; diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index 322eb7ac61..b380860522 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -382,8 +382,9 @@ bool AStar::_solve(Point *begin_point, Point *end_point) { } real_t AStar::_estimate_cost(int p_from_id, int p_to_id) { - if (get_script_instance() && get_script_instance()->has_method(SceneStringNames::get_singleton()->_estimate_cost)) { - return get_script_instance()->call(SceneStringNames::get_singleton()->_estimate_cost, p_from_id, p_to_id); + real_t scost; + if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) { + return scost; } Point *from_point; @@ -398,8 +399,9 @@ real_t AStar::_estimate_cost(int p_from_id, int p_to_id) { } real_t AStar::_compute_cost(int p_from_id, int p_to_id) { - if (get_script_instance() && get_script_instance()->has_method(SceneStringNames::get_singleton()->_compute_cost)) { - return get_script_instance()->call(SceneStringNames::get_singleton()->_compute_cost, p_from_id, p_to_id); + real_t scost; + if (GDVIRTUAL_CALL(_compute_cost, p_from_id, p_to_id, scost)) { + return scost; } Point *from_point; @@ -557,8 +559,8 @@ void AStar::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id"), &AStar::get_point_path); ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id"), &AStar::get_id_path); - BIND_VMETHOD(MethodInfo(Variant::FLOAT, "_estimate_cost", PropertyInfo(Variant::INT, "from_id"), PropertyInfo(Variant::INT, "to_id"))); - BIND_VMETHOD(MethodInfo(Variant::FLOAT, "_compute_cost", PropertyInfo(Variant::INT, "from_id"), PropertyInfo(Variant::INT, "to_id"))); + GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id") + GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id") } AStar::~AStar() { @@ -654,8 +656,9 @@ Vector2 AStar2D::get_closest_position_in_segment(const Vector2 &p_point) const { } real_t AStar2D::_estimate_cost(int p_from_id, int p_to_id) { - if (get_script_instance() && get_script_instance()->has_method(SceneStringNames::get_singleton()->_estimate_cost)) { - return get_script_instance()->call(SceneStringNames::get_singleton()->_estimate_cost, p_from_id, p_to_id); + real_t scost; + if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) { + return scost; } AStar::Point *from_point; @@ -670,8 +673,9 @@ real_t AStar2D::_estimate_cost(int p_from_id, int p_to_id) { } real_t AStar2D::_compute_cost(int p_from_id, int p_to_id) { - if (get_script_instance() && get_script_instance()->has_method(SceneStringNames::get_singleton()->_compute_cost)) { - return get_script_instance()->call(SceneStringNames::get_singleton()->_compute_cost, p_from_id, p_to_id); + real_t scost; + if (GDVIRTUAL_CALL(_compute_cost, p_from_id, p_to_id, scost)) { + return scost; } AStar::Point *from_point; @@ -875,6 +879,6 @@ void AStar2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id"), &AStar2D::get_point_path); ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id"), &AStar2D::get_id_path); - BIND_VMETHOD(MethodInfo(Variant::FLOAT, "_estimate_cost", PropertyInfo(Variant::INT, "from_id"), PropertyInfo(Variant::INT, "to_id"))); - BIND_VMETHOD(MethodInfo(Variant::FLOAT, "_compute_cost", PropertyInfo(Variant::INT, "from_id"), PropertyInfo(Variant::INT, "to_id"))); + GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id") + GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id") } diff --git a/core/math/a_star.h b/core/math/a_star.h index 44758cb046..64fa32a325 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -31,7 +31,9 @@ #ifndef A_STAR_H #define A_STAR_H +#include "core/object/gdvirtual.gen.inc" #include "core/object/ref_counted.h" +#include "core/object/script_language.h" #include "core/templates/oa_hash_map.h" /** @@ -122,6 +124,9 @@ protected: virtual real_t _estimate_cost(int p_from_id, int p_to_id); virtual real_t _compute_cost(int p_from_id, int p_to_id); + GDVIRTUAL2RC(real_t, _estimate_cost, int64_t, int64_t) + GDVIRTUAL2RC(real_t, _compute_cost, int64_t, int64_t) + public: int get_available_point_id() const; @@ -169,6 +174,9 @@ protected: virtual real_t _estimate_cost(int p_from_id, int p_to_id); virtual real_t _compute_cost(int p_from_id, int p_to_id); + GDVIRTUAL2RC(real_t, _estimate_cost, int64_t, int64_t) + GDVIRTUAL2RC(real_t, _compute_cost, int64_t, int64_t) + public: int get_available_point_id() const; 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 682a7ea39e..21cb0efe20 100644 --- a/core/math/convex_hull.cpp +++ b/core/math/convex_hull.cpp @@ -2278,9 +2278,18 @@ Error ConvexHullComputer::convex_hull(const Vector<Vector3> &p_points, Geometry3 e = e->get_next_edge_of_face(); } while (e != e_start); + // reverse indices: Godot wants clockwise, but this is counter-clockwise + if (face.indices.size() > 2) { + // reverse all but the first index. + int *indices = face.indices.ptrw(); + for (int c = 0; c < (face.indices.size() - 1) / 2; c++) { + SWAP(indices[c + 1], indices[face.indices.size() - 1 - c]); + } + } + // compute normal if (face.indices.size() >= 3) { - face.plane = Plane(r_mesh.vertices[face.indices[0]], r_mesh.vertices[face.indices[2]], r_mesh.vertices[face.indices[1]]); + face.plane = Plane(r_mesh.vertices[face.indices[0]], r_mesh.vertices[face.indices[1]], r_mesh.vertices[face.indices[2]]); } else { WARN_PRINT("Too few vertices per face."); } 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/object/class_db.cpp b/core/object/class_db.cpp index c58fe7bc24..e268a8d292 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; @@ -1068,6 +1094,20 @@ void ClassDB::set_property_default_value(const StringName &p_class, const String default_values[p_class][p_name] = p_default; } +void ClassDB::add_linked_property(const StringName &p_class, const String &p_property, const String &p_linked_property) { +#ifdef TOOLS_ENABLED + OBJTYPE_WLOCK; + ClassInfo *type = classes.getptr(p_class); + ERR_FAIL_COND(!type); + + ERR_FAIL_COND(!type->property_map.has(p_property)); + ERR_FAIL_COND(!type->property_map.has(p_linked_property)); + + PropertyInfo &pinfo = type->property_map[p_property]; + pinfo.linked_properties.push_back(p_linked_property); +#endif +} + void ClassDB::get_property_list(const StringName &p_class, List<PropertyInfo> *p_list, bool p_no_inheritance, const Object *p_validator) { OBJTYPE_RLOCK; @@ -1407,7 +1447,7 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const c return p_bind; } -void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual) { +void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual, const Vector<String> &p_arg_names, bool p_object_core) { ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'."); OBJTYPE_WLOCK; @@ -1417,6 +1457,19 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_ if (p_virtual) { mi.flags |= METHOD_FLAG_VIRTUAL; } + if (p_object_core) { + mi.flags |= METHOD_FLAG_OBJECT_CORE; + } + if (p_arg_names.size()) { + if (p_arg_names.size() != mi.arguments.size()) { + WARN_PRINT("Mismatch argument name count for virtual function: " + String(p_class) + "::" + p_method.name); + } else { + for (int i = 0; i < p_arg_names.size(); i++) { + mi.arguments[i].name = p_arg_names[i]; + } + } + } + classes[p_class].virtual_methods.push_back(mi); classes[p_class].virtual_methods_map[p_method.name] = mi; diff --git a/core/object/class_db.h b/core/object/class_db.h index bfc9d6f283..166aa35469 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; @@ -330,7 +331,7 @@ public: if (type->method_map.has(p_name)) { memdelete(bind); - // overloading not supported + // Overloading not supported ERR_FAIL_V_MSG(nullptr, "Method already bound: " + instance_type + "::" + p_name + "."); } type->method_map[p_name] = bind; @@ -354,6 +355,7 @@ public: static void add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_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); static void get_property_list(const StringName &p_class, List<PropertyInfo> *p_list, bool p_no_inheritance = false, const Object *p_validator = nullptr); static bool get_property_info(const StringName &p_class, const StringName &p_property, PropertyInfo *r_info, bool p_no_inheritance = false, const Object *p_validator = nullptr); static bool set_property(Object *p_object, const StringName &p_property, const Variant &p_value, bool *r_valid = nullptr); @@ -371,7 +373,7 @@ public: static bool get_method_info(const StringName &p_class, const StringName &p_method, MethodInfo *r_info, bool p_no_inheritance = false, bool p_exclude_from_properties = false); static MethodBind *get_method(const StringName &p_class, const StringName &p_name); - static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true); + static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true, const Vector<String> &p_arg_names = Vector<String>(), bool p_object_core = false); static void get_virtual_methods(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false); static void bind_integer_constant(const StringName &p_class, const StringName &p_enum, const StringName &p_name, int p_constant); @@ -384,6 +386,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); @@ -409,39 +413,53 @@ public: #ifdef DEBUG_METHODS_ENABLED #define BIND_CONSTANT(m_constant) \ - ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); + ::ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); #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); + ::ClassDB::bind_integer_constant(get_class_static(), __constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant); -#else - -#define BIND_CONSTANT(m_constant) \ - ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); +_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr) { +} -#define BIND_ENUM_CONSTANT(m_constant) \ - ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); +_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr, const Error &p_err) { + arr.push_back(p_err); +} -#endif +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...); +} -#ifdef TOOLS_ENABLED +template <class... P> +_FORCE_INLINE_ Vector<Error> errarray(P... p_args) { + Vector<Error> arr; + errarray_add_str(arr, p_args...); + return arr; +} -#define BIND_VMETHOD(m_method) \ - ClassDB::add_virtual_method(get_class_static(), m_method); +#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_VMETHOD(m_method) +#define BIND_CONSTANT(m_constant) \ + ::ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); + +#define BIND_ENUM_CONSTANT(m_constant) \ + ::ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); + +#define BIND_METHOD_ERR_RETURN_DOC(m_method, ...) #endif #define GDREGISTER_CLASS(m_class) \ if (!GD_IS_DEFINED(ClassDB_Disable_##m_class)) { \ - ClassDB::register_class<m_class>(); \ + ::ClassDB::register_class<m_class>(); \ } -#define GDREGISTER_VIRTUAL_CLASS(m_class) \ - if (!GD_IS_DEFINED(ClassDB_Disable_##m_class)) { \ - ClassDB::register_virtual_class<m_class>(); \ +#define GDREGISTER_VIRTUAL_CLASS(m_class) \ + if (!GD_IS_DEFINED(ClassDB_Disable_##m_class)) { \ + ::ClassDB::register_virtual_class<m_class>(); \ } #include "core/disabled_classes.gen.h" diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index 2c6b8cddc9..86c2891e5d 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -1,8 +1,8 @@ proto = """ #define GDVIRTUAL$VER($RET m_name $ARG) \\ -GDNativeExtensionClassCallVirtual _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, #m_name) : (GDNativeExtensionClassCallVirtual) nullptr;\\ StringName _gdvirtual_##m_name##_sn = #m_name;\\ -bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ +GDNativeExtensionClassCallVirtual _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, #m_name) : (GDNativeExtensionClassCallVirtual) nullptr;\\ +_FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ ScriptInstance *script_instance = ((Object*)(this))->get_script_instance();\\ if (script_instance) {\\ Callable::CallError ce; \\ @@ -23,6 +23,16 @@ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ \\ return false;\\ }\\ +_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);\\ + }\\ + if (_gdvirtual_##m_name) {\\ + return true;\\ + }\\ + return false;\\ +}\\ \\ _FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() { \\ MethodInfo method_info;\\ @@ -32,7 +42,6 @@ _FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() { \\ return method_info;\\ } - """ @@ -77,7 +86,7 @@ def generate_version(argcount, const=False, returns=False): callptrargs += "\t\t" callptrargsptr += ", " argtext += "m_type" + str(i + 1) - callargtext += "const m_type" + str(i + 1) + "& arg" + str(i + 1) + callargtext += "m_type" + str(i + 1) + " arg" + str(i + 1) callsiargs += "Variant(arg" + str(i + 1) + ")" callsiargptrs += "&vargs[" + str(i) + "]" callptrargs += ( @@ -103,7 +112,7 @@ def generate_version(argcount, const=False, returns=False): if returns: if argcount > 0: callargtext += "," - callargtext += " m_ret& r_ret" + callargtext += " m_ret& r_ret" s = s.replace("$CALLSIBEGIN", "Variant ret = ") s = s.replace("$CALLSIRET", "r_ret = ret;") s = s.replace("$CALLPTRRETPASS", "&ret") diff --git a/core/object/method_bind.h b/core/object/method_bind.h index 92b964772a..b0b379873e 100644 --- a/core/object/method_bind.h +++ b/core/object/method_bind.h @@ -43,6 +43,7 @@ enum MethodFlags { METHOD_FLAG_FROM_SCRIPT = 64, METHOD_FLAG_VARARG = 128, METHOD_FLAG_STATIC = 256, + METHOD_FLAG_OBJECT_CORE = 512, METHOD_FLAGS_DEFAULT = METHOD_FLAG_NORMAL, }; diff --git a/core/object/object.cpp b/core/object/object.cpp index 2bb4b981b9..3335942fb3 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1619,22 +1619,25 @@ void Object::_bind_methods() { ADD_SIGNAL(MethodInfo("script_changed")); ADD_SIGNAL(MethodInfo("property_list_changed")); - BIND_VMETHOD(MethodInfo("_notification", PropertyInfo(Variant::INT, "what"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_set", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value"))); +#define BIND_OBJ_CORE_METHOD(m_method) \ + ::ClassDB::add_virtual_method(get_class_static(), m_method, true, Vector<String>(), true); + + BIND_OBJ_CORE_METHOD(MethodInfo("_notification", PropertyInfo(Variant::INT, "what"))); + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_set", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value"))); #ifdef TOOLS_ENABLED MethodInfo miget("_get", PropertyInfo(Variant::STRING_NAME, "property")); miget.return_val.name = "Variant"; miget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(miget); + BIND_OBJ_CORE_METHOD(miget); MethodInfo plget("_get_property_list"); plget.return_val.type = Variant::ARRAY; - BIND_VMETHOD(plget); + BIND_OBJ_CORE_METHOD(plget); #endif - BIND_VMETHOD(MethodInfo("_init")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_to_string")); + BIND_OBJ_CORE_METHOD(MethodInfo("_init")); + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::STRING, "_to_string")); BIND_CONSTANT(NOTIFICATION_POSTINITIALIZE); BIND_CONSTANT(NOTIFICATION_PREDELETE); diff --git a/core/object/object.h b/core/object/object.h index 6523105820..102776a589 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 }; @@ -137,21 +138,26 @@ enum PropertyUsageFlags { PROPERTY_USAGE_NOEDITOR = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_NETWORK, }; -#define ADD_SIGNAL(m_signal) ClassDB::add_signal(get_class_static(), m_signal) -#define ADD_PROPERTY(m_property, m_setter, m_getter) ClassDB::add_property(get_class_static(), m_property, _scs_create(m_setter), _scs_create(m_getter)) -#define ADD_PROPERTYI(m_property, m_setter, m_getter, m_index) ClassDB::add_property(get_class_static(), m_property, _scs_create(m_setter), _scs_create(m_getter), m_index) -#define ADD_PROPERTY_DEFAULT(m_property, m_default) ClassDB::set_property_default_value(get_class_static(), m_property, m_default) -#define ADD_GROUP(m_name, m_prefix) ClassDB::add_property_group(get_class_static(), m_name, m_prefix) -#define ADD_SUBGROUP(m_name, m_prefix) ClassDB::add_property_subgroup(get_class_static(), m_name, m_prefix) +#define ADD_SIGNAL(m_signal) ::ClassDB::add_signal(get_class_static(), m_signal) +#define ADD_PROPERTY(m_property, m_setter, m_getter) ::ClassDB::add_property(get_class_static(), m_property, _scs_create(m_setter), _scs_create(m_getter)) +#define ADD_PROPERTYI(m_property, m_setter, m_getter, m_index) ::ClassDB::add_property(get_class_static(), m_property, _scs_create(m_setter), _scs_create(m_getter), m_index) +#define ADD_PROPERTY_DEFAULT(m_property, m_default) ::ClassDB::set_property_default_value(get_class_static(), m_property, m_default) +#define ADD_GROUP(m_name, m_prefix) ::ClassDB::add_property_group(get_class_static(), m_name, m_prefix) +#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) struct PropertyInfo { Variant::Type type = Variant::NIL; String name; - StringName class_name; //for classes + StringName class_name; // For classes PropertyHint hint = PROPERTY_HINT_NONE; String hint_string; uint32_t usage = PROPERTY_USAGE_DEFAULT; +#ifdef TOOLS_ENABLED + Vector<String> linked_properties; +#endif + _FORCE_INLINE_ PropertyInfo added_usage(uint32_t p_fl) const { PropertyInfo pi = *this; pi.usage |= p_fl; @@ -277,7 +283,14 @@ struct ObjectNativeExtension { }; #define GDVIRTUAL_CALL(m_name, ...) _gdvirtual_##m_name##_call(__VA_ARGS__) -#define GDVIRTUAL_BIND(m_name) ClassDB::add_virtual_method(get_class_static(), _gdvirtual_##m_name##_get_method_info()); +#define GDVIRTUAL_CALL_PTR(m_obj, m_name, ...) m_obj->_gdvirtual_##m_name##_call(__VA_ARGS__) +#ifdef DEBUG_METHODS_ENABLED +#define GDVIRTUAL_BIND(m_name, ...) ::ClassDB::add_virtual_method(get_class_static(), _gdvirtual_##m_name##_get_method_info(), true, sarray(__VA_ARGS__)); +#else +#define GDVIRTUAL_BIND(m_name, ...) +#endif +#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. @@ -299,7 +312,7 @@ private: private: \ void operator=(const m_class &p_rval) {} \ mutable StringName _class_name; \ - friend class ClassDB; \ + friend class ::ClassDB; \ \ public: \ virtual String get_class() const override { \ @@ -372,7 +385,7 @@ public: return; \ } \ m_inherits::initialize_class(); \ - ClassDB::_add_class<m_class>(); \ + ::ClassDB::_add_class<m_class>(); \ if (m_class::_get_bind_methods() != m_inherits::_get_bind_methods()) { \ _bind_methods(); \ } \ @@ -415,13 +428,13 @@ protected: } \ p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); \ if (!_is_gpl_reversed()) { \ - ClassDB::get_property_list(#m_class, p_list, true, this); \ + ::ClassDB::get_property_list(#m_class, p_list, true, this); \ } \ if (m_class::_get_get_property_list() != m_inherits::_get_get_property_list()) { \ _get_property_list(p_list); \ } \ if (_is_gpl_reversed()) { \ - ClassDB::get_property_list(#m_class, p_list, true, this); \ + ::ClassDB::get_property_list(#m_class, p_list, true, this); \ } \ if (p_reversed) { \ m_inherits::_get_property_listv(p_list, p_reversed); \ diff --git a/core/object/ref_counted.h b/core/object/ref_counted.h index e0af2c18bb..f2dd2aa324 100644 --- a/core/object/ref_counted.h +++ b/core/object/ref_counted.h @@ -274,8 +274,6 @@ struct PtrToArg<const Ref<T> &> { } }; -#ifdef DEBUG_METHODS_ENABLED - template <class T> struct GetTypeInfo<Ref<T>> { static const Variant::Type VARIANT_TYPE = Variant::OBJECT; @@ -296,6 +294,4 @@ struct GetTypeInfo<const Ref<T> &> { } }; -#endif // DEBUG_METHODS_ENABLED - #endif // REF_COUNTED_H diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp index adf068eb2f..b7d2bac96d 100644 --- a/core/object/undo_redo.cpp +++ b/core/object/undo_redo.cpp @@ -405,11 +405,11 @@ String UndoRedo::get_current_action_name() const { return actions[current_action].name; } -bool UndoRedo::has_undo() { +bool UndoRedo::has_undo() const { return current_action >= 0; } -bool UndoRedo::has_redo() { +bool UndoRedo::has_redo() const { return (current_action + 1) < actions.size(); } diff --git a/core/object/undo_redo.h b/core/object/undo_redo.h index 8f009830e3..d1ce252d86 100644 --- a/core/object/undo_redo.h +++ b/core/object/undo_redo.h @@ -121,8 +121,8 @@ public: String get_action_name(int p_id); void clear_history(bool p_increase_version = true); - bool has_undo(); - bool has_redo(); + bool has_undo() const; + bool has_redo() const; uint64_t get_version() const; diff --git a/core/os/main_loop.cpp b/core/os/main_loop.cpp index 3c0e56f5a8..0ba69a8d47 100644 --- a/core/os/main_loop.cpp +++ b/core/os/main_loop.cpp @@ -33,11 +33,6 @@ #include "core/object/script_language.h" void MainLoop::_bind_methods() { - BIND_VMETHOD(MethodInfo("_initialize")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_physics_process", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_process", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo("_finalize")); - BIND_CONSTANT(NOTIFICATION_OS_MEMORY_WARNING); BIND_CONSTANT(NOTIFICATION_TRANSLATION_CHANGED); BIND_CONSTANT(NOTIFICATION_WM_ABOUT); @@ -50,7 +45,12 @@ void MainLoop::_bind_methods() { BIND_CONSTANT(NOTIFICATION_TEXT_SERVER_CHANGED); ADD_SIGNAL(MethodInfo("on_request_permissions_result", PropertyInfo(Variant::STRING, "permission"), PropertyInfo(Variant::BOOL, "granted"))); -}; + + GDVIRTUAL_BIND(_initialize); + GDVIRTUAL_BIND(_physics_process, "delta"); + GDVIRTUAL_BIND(_process, "delta"); + GDVIRTUAL_BIND(_finalize); +} void MainLoop::set_initialize_script(const Ref<Script> &p_initialize_script) { initialize_script = p_initialize_script; @@ -61,30 +61,31 @@ void MainLoop::initialize() { set_script(initialize_script); } - if (get_script_instance()) { - get_script_instance()->call("_initialize"); - } + GDVIRTUAL_CALL(_initialize); } bool MainLoop::physics_process(double p_time) { - if (get_script_instance()) { - return get_script_instance()->call("_physics_process", p_time); + bool quit; + if (GDVIRTUAL_CALL(_physics_process, p_time, quit)) { + return quit; } return false; } bool MainLoop::process(double p_time) { - if (get_script_instance()) { - return get_script_instance()->call("_process", p_time); + bool quit; + if (GDVIRTUAL_CALL(_process, p_time, quit)) { + return quit; } return false; } void MainLoop::finalize() { + GDVIRTUAL_CALL(_finalize); + if (get_script_instance()) { - get_script_instance()->call("_finalize"); set_script(Variant()); //clear script } } diff --git a/core/os/main_loop.h b/core/os/main_loop.h index b42e9b18ff..4da01d767e 100644 --- a/core/os/main_loop.h +++ b/core/os/main_loop.h @@ -32,6 +32,7 @@ #define MAIN_LOOP_H #include "core/input/input_event.h" +#include "core/object/gdvirtual.gen.inc" #include "core/object/ref_counted.h" #include "core/object/script_language.h" @@ -44,6 +45,11 @@ class MainLoop : public Object { protected: static void _bind_methods(); + GDVIRTUAL0(_initialize) + GDVIRTUAL1R(bool, _physics_process, double) + GDVIRTUAL1R(bool, _process, double) + GDVIRTUAL0(_finalize) + public: enum { //make sure these are replicated in Node diff --git a/core/os/os.cpp b/core/os/os.cpp index 76a6da51e1..63390919f4 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -277,18 +277,13 @@ String OS::get_user_data_dir() const { return "."; } -// Android OS path to app's external data storage -String OS::get_external_data_dir() const { - return get_user_data_dir(); -}; - // Absolute path to res:// String OS::get_resource_dir() const { return ProjectSettings::get_singleton()->get_resource_path(); } // Access system-specific dirs like Documents, Downloads, etc. -String OS::get_system_dir(SystemDir p_dir) const { +String OS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { return "."; } diff --git a/core/os/os.h b/core/os/os.h index 0466d94acd..55b21266fc 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -250,7 +250,6 @@ public: virtual String get_bundle_resource_dir() const; virtual String get_user_data_dir() const; - virtual String get_external_data_dir() const; virtual String get_resource_dir() const; enum SystemDir { @@ -264,7 +263,7 @@ public: SYSTEM_DIR_RINGTONES, }; - virtual String get_system_dir(SystemDir p_dir) const; + virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const; virtual Error move_to_trash(const String &p_path) { return FAILED; } diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index f801be3db3..6c7d9cbd89 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -41,6 +41,7 @@ #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" @@ -49,6 +50,7 @@ #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" @@ -84,18 +86,18 @@ static Ref<ResourceFormatSaverCrypto> resource_format_saver_crypto; static Ref<ResourceFormatLoaderCrypto> resource_format_loader_crypto; static Ref<NativeExtensionResourceLoader> resource_loader_native_extension; -static _ResourceLoader *_resource_loader = nullptr; -static _ResourceSaver *_resource_saver = nullptr; -static _OS *_os = nullptr; -static _Engine *_engine = nullptr; -static _ClassDB *_classdb = nullptr; -static _Marshalls *_marshalls = nullptr; -static _EngineDebugger *_engine_debugger = nullptr; +static core_bind::ResourceLoader *_resource_loader = nullptr; +static core_bind::ResourceSaver *_resource_saver = nullptr; +static core_bind::OS *_os = nullptr; +static core_bind::Engine *_engine = nullptr; +static core_bind::special::ClassDB *_classdb = nullptr; +static core_bind::Marshalls *_marshalls = nullptr; +static core_bind::EngineDebugger *_engine_debugger = nullptr; static IP *ip = nullptr; -static _Geometry2D *_geometry_2d = nullptr; -static _Geometry3D *_geometry_3d = nullptr; +static core_bind::Geometry2D *_geometry_2d = nullptr; +static core_bind::Geometry3D *_geometry_3d = nullptr; extern Mutex _global_mutex; @@ -144,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); @@ -193,6 +197,7 @@ void register_core_types() { ResourceLoader::add_resource_format_loader(resource_format_loader_crypto); GDREGISTER_VIRTUAL_CLASS(MultiplayerPeer); + GDREGISTER_VIRTUAL_CLASS(MultiplayerReplicator); GDREGISTER_CLASS(MultiplayerAPI); GDREGISTER_CLASS(MainLoop); GDREGISTER_CLASS(Translation); @@ -203,11 +208,11 @@ void register_core_types() { GDREGISTER_CLASS(ResourceFormatLoader); GDREGISTER_CLASS(ResourceFormatSaver); - GDREGISTER_CLASS(_File); - GDREGISTER_CLASS(_Directory); - GDREGISTER_CLASS(_Thread); - GDREGISTER_CLASS(_Mutex); - GDREGISTER_CLASS(_Semaphore); + GDREGISTER_CLASS(core_bind::File); + GDREGISTER_CLASS(core_bind::Directory); + GDREGISTER_CLASS(core_bind::Thread); + GDREGISTER_CLASS(core_bind::Mutex); + GDREGISTER_CLASS(core_bind::Semaphore); GDREGISTER_CLASS(XMLParser); GDREGISTER_CLASS(JSON); @@ -240,16 +245,16 @@ void register_core_types() { ip = IP::create(); - _geometry_2d = memnew(_Geometry2D); - _geometry_3d = memnew(_Geometry3D); + _geometry_2d = memnew(core_bind::Geometry2D); + _geometry_3d = memnew(core_bind::Geometry3D); - _resource_loader = memnew(_ResourceLoader); - _resource_saver = memnew(_ResourceSaver); - _os = memnew(_OS); - _engine = memnew(_Engine); - _classdb = memnew(_ClassDB); - _marshalls = memnew(_Marshalls); - _engine_debugger = memnew(_EngineDebugger); + _resource_loader = memnew(core_bind::ResourceLoader); + _resource_saver = memnew(core_bind::ResourceSaver); + _os = memnew(core_bind::OS); + _engine = memnew(core_bind::Engine); + _classdb = memnew(core_bind::special::ClassDB); + _marshalls = memnew(core_bind::Marshalls); + _engine_debugger = memnew(core_bind::EngineDebugger); } void register_core_settings() { @@ -266,35 +271,35 @@ void register_core_settings() { void register_core_singletons() { GDREGISTER_CLASS(ProjectSettings); GDREGISTER_VIRTUAL_CLASS(IP); - GDREGISTER_CLASS(_Geometry2D); - GDREGISTER_CLASS(_Geometry3D); - GDREGISTER_CLASS(_ResourceLoader); - GDREGISTER_CLASS(_ResourceSaver); - GDREGISTER_CLASS(_OS); - GDREGISTER_CLASS(_Engine); - GDREGISTER_CLASS(_ClassDB); - GDREGISTER_CLASS(_Marshalls); + GDREGISTER_CLASS(core_bind::Geometry2D); + GDREGISTER_CLASS(core_bind::Geometry3D); + GDREGISTER_CLASS(core_bind::ResourceLoader); + GDREGISTER_CLASS(core_bind::ResourceSaver); + GDREGISTER_CLASS(core_bind::OS); + GDREGISTER_CLASS(core_bind::Engine); + GDREGISTER_CLASS(core_bind::special::ClassDB); + GDREGISTER_CLASS(core_bind::Marshalls); GDREGISTER_CLASS(TranslationServer); GDREGISTER_VIRTUAL_CLASS(Input); GDREGISTER_CLASS(InputMap); GDREGISTER_CLASS(Expression); - GDREGISTER_CLASS(_EngineDebugger); + GDREGISTER_CLASS(core_bind::EngineDebugger); GDREGISTER_CLASS(Time); Engine::get_singleton()->add_singleton(Engine::Singleton("ProjectSettings", ProjectSettings::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("IP", IP::get_singleton(), "IP")); - Engine::get_singleton()->add_singleton(Engine::Singleton("Geometry2D", _Geometry2D::get_singleton())); - Engine::get_singleton()->add_singleton(Engine::Singleton("Geometry3D", _Geometry3D::get_singleton())); - Engine::get_singleton()->add_singleton(Engine::Singleton("ResourceLoader", _ResourceLoader::get_singleton())); - Engine::get_singleton()->add_singleton(Engine::Singleton("ResourceSaver", _ResourceSaver::get_singleton())); - Engine::get_singleton()->add_singleton(Engine::Singleton("OS", _OS::get_singleton())); - Engine::get_singleton()->add_singleton(Engine::Singleton("Engine", _Engine::get_singleton())); + Engine::get_singleton()->add_singleton(Engine::Singleton("Geometry2D", core_bind::Geometry2D::get_singleton())); + Engine::get_singleton()->add_singleton(Engine::Singleton("Geometry3D", core_bind::Geometry3D::get_singleton())); + Engine::get_singleton()->add_singleton(Engine::Singleton("ResourceLoader", core_bind::ResourceLoader::get_singleton())); + Engine::get_singleton()->add_singleton(Engine::Singleton("ResourceSaver", core_bind::ResourceSaver::get_singleton())); + Engine::get_singleton()->add_singleton(Engine::Singleton("OS", core_bind::OS::get_singleton())); + Engine::get_singleton()->add_singleton(Engine::Singleton("Engine", core_bind::Engine::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("ClassDB", _classdb)); - Engine::get_singleton()->add_singleton(Engine::Singleton("Marshalls", _Marshalls::get_singleton())); + Engine::get_singleton()->add_singleton(Engine::Singleton("Marshalls", core_bind::Marshalls::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("TranslationServer", TranslationServer::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("Input", Input::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("InputMap", InputMap::get_singleton())); - Engine::get_singleton()->add_singleton(Engine::Singleton("EngineDebugger", _EngineDebugger::get_singleton())); + Engine::get_singleton()->add_singleton(Engine::Singleton("EngineDebugger", core_bind::EngineDebugger::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("Time", Time::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("NativeExtensionManager", NativeExtensionManager::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("ResourceUID", ResourceUID::get_singleton())); @@ -303,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 2fdf6e84e5..a30d6b9102 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -39,12 +39,9 @@ #include "core/string/ucaps.h" #include "core/variant/variant.h" -#include <cstdint> - -#ifndef NO_USE_STDLIB #include <stdio.h> #include <stdlib.h> -#endif +#include <cstdint> #ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS // to disable build-time warning which suggested to use strcpy_s instead strcpy @@ -1393,10 +1390,15 @@ String String::num(double p_num, int p_decimals) { return "inf"; } } -#ifndef NO_USE_STDLIB if (p_decimals < 0) { - p_decimals = 14 - (int)floor(log10(p_num)); + p_decimals = 14; + const double abs_num = ABS(p_num); + if (abs_num > 10) { + // We want to align the digits to the above sane default, so we only + // need to subtract log10 for numbers with a positive power of ten. + p_decimals -= (int)floor(log10(abs_num)); + } } if (p_decimals > MAX_DECIMALS) { p_decimals = MAX_DECIMALS; @@ -1460,87 +1462,6 @@ String String::num(double p_num, int p_decimals) { } return buf; -#else - - String s; - String sd; - /* integer part */ - - bool neg = p_num < 0; - p_num = ABS(p_num); - int intn = (int)p_num; - - /* decimal part */ - - if (p_decimals > 0 || (p_decimals == -1 && (int)p_num != p_num)) { - double dec = p_num - (double)((int)p_num); - - int digit = 0; - if (p_decimals > MAX_DECIMALS) { - p_decimals = MAX_DECIMALS; - } - - int dec_int = 0; - int dec_max = 0; - - while (true) { - dec *= 10.0; - dec_int = dec_int * 10 + (int)dec % 10; - dec_max = dec_max * 10 + 9; - digit++; - - if (p_decimals == -1) { - if (digit == MAX_DECIMALS) { //no point in going to infinite - break; - } - - if (dec - (double)((int)dec) < 1e-6) { - break; - } - } - - if (digit == p_decimals) { - break; - } - } - dec *= 10; - int last = (int)dec % 10; - - if (last > 5) { - if (dec_int == dec_max) { - dec_int = 0; - intn++; - } else { - dec_int++; - } - } - - String decimal; - for (int i = 0; i < digit; i++) { - char num[2] = { 0, 0 }; - num[0] = '0' + dec_int % 10; - decimal = num + decimal; - dec_int /= 10; - } - sd = '.' + decimal; - } - - if (intn == 0) - - s = "0"; - else { - while (intn) { - char32_t num = '0' + (intn % 10); - intn /= 10; - s = num + s; - } - } - - s = s + sd; - if (neg) - s = "-" + s; - return s; -#endif } String String::num_int64(int64_t p_num, int base, bool capitalize_hex) { @@ -1625,24 +1546,31 @@ String String::num_real(double p_num, bool p_trailing) { String s; String sd; - /* integer part */ + + // Integer part. bool neg = p_num < 0; p_num = ABS(p_num); int intn = (int)p_num; - /* decimal part */ + // Decimal part. - if ((int)p_num != p_num) { - double dec = p_num - (double)((int)p_num); + if (intn != p_num) { + double dec = p_num - (double)(intn); int digit = 0; #if REAL_T_IS_DOUBLE - int decimals = 14 - (int)floor(log10(p_num)); + int decimals = 14; #else - int decimals = 6 - (int)floor(log10(p_num)); + int decimals = 6; #endif + // We want to align the digits to the above sane default, so we only + // need to subtract log10 for numbers with a positive power of ten. + if (p_num > 10) { + decimals -= (int)floor(log10(p_num)); + } + if (decimals > MAX_DECIMALS) { decimals = MAX_DECIMALS; } @@ -1720,7 +1648,6 @@ String String::num_scientific(double p_num) { return "inf"; } } -#ifndef NO_USE_STDLIB char buf[256]; @@ -1743,10 +1670,6 @@ String String::num_scientific(double p_num) { buf[255] = 0; return buf; -#else - - return String::num(p_num); -#endif } String String::md5(const uint8_t *p_md5) { 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/variant/array.cpp b/core/variant/array.cpp index 09cf785390..78ad796283 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -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..e9634ccece 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -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/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/type_info.h b/core/variant/type_info.h index 76cb065d10..b70d29bbac 100644 --- a/core/variant/type_info.h +++ b/core/variant/type_info.h @@ -241,14 +241,27 @@ struct GetTypeInfo<const T *, typename EnableIf<TypeInherits<Object, T>::value>: } }; -#define TEMPL_MAKE_ENUM_TYPE_INFO(m_enum, m_impl) \ - template <> \ - struct GetTypeInfo<m_impl> { \ - static const Variant::Type VARIANT_TYPE = Variant::INT; \ - static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ - static inline PropertyInfo get_class_info() { \ - return PropertyInfo(Variant::INT, String(), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CLASS_IS_ENUM, String(#m_enum).replace("::", ".")); \ - } \ +namespace godot { +namespace details { +inline String enum_qualified_name_to_class_info_name(const String &p_qualified_name) { + Vector<String> parts = p_qualified_name.split("::", false); + if (parts.size() <= 2) + return String(".").join(parts); + // Contains namespace. We only want the class and enum names. + return parts[parts.size() - 2] + "." + parts[parts.size() - 1]; +} +} // namespace details +} // namespace godot + +#define TEMPL_MAKE_ENUM_TYPE_INFO(m_enum, m_impl) \ + template <> \ + struct GetTypeInfo<m_impl> { \ + static const Variant::Type VARIANT_TYPE = Variant::INT; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::INT, String(), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CLASS_IS_ENUM, \ + godot::details::enum_qualified_name_to_class_info_name(String(#m_enum))); \ + } \ }; #define MAKE_ENUM_TYPE_INFO(m_enum) \ diff --git a/core/variant/typed_array.h b/core/variant/typed_array.h index 900dcf7689..2e96f4e445 100644 --- a/core/variant/typed_array.h +++ b/core/variant/typed_array.h @@ -125,7 +125,7 @@ struct PtrToArg<TypedArray<T>> { _FORCE_INLINE_ static TypedArray<T> convert(const void *p_ptr) { return TypedArray<T>(*reinterpret_cast<const Array *>(p_ptr)); } - + typedef Array EncodeT; _FORCE_INLINE_ static void encode(TypedArray<T> p_val, void *p_ptr) { *(Array *)p_ptr = p_val; } @@ -133,13 +133,13 @@ struct PtrToArg<TypedArray<T>> { template <class T> struct PtrToArg<const TypedArray<T> &> { - _FORCE_INLINE_ static TypedArray<T> convert(const void *p_ptr) { + typedef Array EncodeT; + _FORCE_INLINE_ static TypedArray<T> + convert(const void *p_ptr) { return TypedArray<T>(*reinterpret_cast<const Array *>(p_ptr)); } }; -#ifdef DEBUG_METHODS_ENABLED - template <class T> struct GetTypeInfo<TypedArray<T>> { static const Variant::Type VARIANT_TYPE = Variant::ARRAY; @@ -218,6 +218,4 @@ MAKE_TYPED_ARRAY_INFO(Vector<Vector2>, Variant::PACKED_VECTOR2_ARRAY) MAKE_TYPED_ARRAY_INFO(Vector<Vector3>, Variant::PACKED_VECTOR3_ARRAY) MAKE_TYPED_ARRAY_INFO(Vector<Color>, Variant::PACKED_COLOR_ARRAY) -#endif - #endif // TYPED_ARRAY_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..382b256c08 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1422,6 +1422,7 @@ static void _register_variant_builtin_methods() { bind_method(String, is_absolute_path, sarray(), varray()); bind_method(String, is_rel_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)); @@ -1806,6 +1807,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_internal.h b/core/variant/variant_internal.h index 566b14736e..40c8a1bfde 100644 --- a/core/variant/variant_internal.h +++ b/core/variant/variant_internal.h @@ -104,7 +104,7 @@ public: init_color_array(v); break; case Variant::OBJECT: - object_assign_null(v); + init_object(v); break; default: break; @@ -280,6 +280,10 @@ public: v->_data.packed_array = Variant::PackedArrayRef<Color>::create(Vector<Color>()); v->type = Variant::PACKED_COLOR_ARRAY; } + _FORCE_INLINE_ static void init_object(Variant *v) { + object_assign_null(v); + v->type = Variant::OBJECT; + } _FORCE_INLINE_ static void clear(Variant *v) { v->clear(); @@ -1173,7 +1177,7 @@ struct VariantInitializer<PackedColorArray> { template <> struct VariantInitializer<Object *> { - static _FORCE_INLINE_ void init(Variant *v) { VariantInternal::object_assign_null(v); } + static _FORCE_INLINE_ void init(Variant *v) { VariantInternal::init_object(v); } }; template <class T> @@ -1405,4 +1409,22 @@ struct VariantTypeConstructor { } }; +template <> +struct VariantTypeConstructor<Object *> { + _FORCE_INLINE_ static void variant_from_type(void *p_variant, void *p_value) { + Variant *variant = reinterpret_cast<Variant *>(p_variant); + VariantInitializer<Object *>::init(variant); + Object *value = *(reinterpret_cast<Object **>(p_value)); + if (value) { + VariantInternalAccessor<Object *>::set(variant, value); + VariantInternalAccessor<ObjectID>::set(variant, value->get_instance_id()); + } + } + + _FORCE_INLINE_ static void type_from_variant(void *p_value, void *p_variant) { + Object **value = reinterpret_cast<Object **>(p_value); + *value = VariantInternalAccessor<Object *>::get(reinterpret_cast<Variant *>(p_variant)); + } +}; + #endif // VARIANT_INTERNAL_H diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index 50c48fd386..a214a238a6 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -1423,7 +1423,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str p_store_string_func(p_store_string_ud, itos(p_variant.operator int64_t())); } break; case Variant::FLOAT: { - String s = rtosfix(p_variant.operator real_t()); + String s = rtosfix(p_variant.operator double()); if (s != "inf" && s != "nan") { if (s.find(".") == -1 && s.find("e") == -1) { s += ".0"; diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 6c57d1de10..f5cb2d40d6 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" @@ -728,6 +730,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 @@ -1265,6 +1274,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 d4680d1836..585b94a522 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -711,6 +711,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 +1085,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 +2364,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> @@ -2458,6 +2473,8 @@ </constant> <constant name="METHOD_FLAG_STATIC" value="256" enum="MethodFlags"> </constant> + <constant name="METHOD_FLAG_OBJECT_CORE" value="512" enum="MethodFlags"> + </constant> <constant name="METHOD_FLAGS_DEFAULT" value="1" enum="MethodFlags"> Default method flags. </constant> diff --git a/doc/classes/AStar.xml b/doc/classes/AStar.xml index 63dd250dbc..3e91184a65 100644 --- a/doc/classes/AStar.xml +++ b/doc/classes/AStar.xml @@ -38,7 +38,7 @@ <tutorials> </tutorials> <methods> - <method name="_compute_cost" qualifiers="virtual"> + <method name="_compute_cost" qualifiers="virtual const"> <return type="float" /> <argument index="0" name="from_id" type="int" /> <argument index="1" name="to_id" type="int" /> @@ -47,7 +47,7 @@ Note that this function is hidden in the default [code]AStar[/code] class. </description> </method> - <method name="_estimate_cost" qualifiers="virtual"> + <method name="_estimate_cost" qualifiers="virtual const"> <return type="float" /> <argument index="0" name="from_id" type="int" /> <argument index="1" name="to_id" type="int" /> diff --git a/doc/classes/AStar2D.xml b/doc/classes/AStar2D.xml index 31d695b051..453e8b6315 100644 --- a/doc/classes/AStar2D.xml +++ b/doc/classes/AStar2D.xml @@ -9,7 +9,7 @@ <tutorials> </tutorials> <methods> - <method name="_compute_cost" qualifiers="virtual"> + <method name="_compute_cost" qualifiers="virtual const"> <return type="float" /> <argument index="0" name="from_id" type="int" /> <argument index="1" name="to_id" type="int" /> @@ -18,7 +18,7 @@ Note that this function is hidden in the default [code]AStar2D[/code] class. </description> </method> - <method name="_estimate_cost" qualifiers="virtual"> + <method name="_estimate_cost" qualifiers="virtual const"> <return type="float" /> <argument index="0" name="from_id" type="int" /> <argument index="1" name="to_id" type="int" /> diff --git a/doc/classes/AnimationNode.xml b/doc/classes/AnimationNode.xml index a9a08efcf1..173ff43d2a 100644 --- a/doc/classes/AnimationNode.xml +++ b/doc/classes/AnimationNode.xml @@ -11,46 +11,46 @@ <link title="AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> </tutorials> <methods> - <method name="_get_caption" qualifiers="virtual"> + <method name="_get_caption" qualifiers="virtual const"> <return type="String" /> <description> Gets the text caption for this node (used by some editors). </description> </method> - <method name="_get_child_by_name" qualifiers="virtual"> - <return type="Object" /> - <argument index="0" name="name" type="String" /> + <method name="_get_child_by_name" qualifiers="virtual const"> + <return type="AnimationNode" /> + <argument index="0" name="name" type="StringName" /> <description> Gets a child node by index (used by editors inheriting from [AnimationRootNode]). </description> </method> - <method name="_get_child_nodes" qualifiers="virtual"> + <method name="_get_child_nodes" qualifiers="virtual const"> <return type="Dictionary" /> <description> Gets all children nodes in order as a [code]name: node[/code] dictionary. Only useful when inheriting [AnimationRootNode]. </description> </method> - <method name="_get_parameter_default_value" qualifiers="virtual"> + <method name="_get_parameter_default_value" qualifiers="virtual const"> <return type="Variant" /> - <argument index="0" name="name" type="StringName" /> + <argument index="0" name="parameter" type="StringName" /> <description> Gets the default value of a parameter. Parameters are custom local memory used for your nodes, given a resource can be reused in multiple trees. </description> </method> - <method name="_get_parameter_list" qualifiers="virtual"> + <method name="_get_parameter_list" qualifiers="virtual const"> <return type="Array" /> <description> Gets the property information for parameter. Parameters are custom local memory used for your nodes, given a resource can be reused in multiple trees. Format is similar to [method Object.get_property_list]. </description> </method> - <method name="_has_filter" qualifiers="virtual"> + <method name="_has_filter" qualifiers="virtual const"> <return type="bool" /> <description> Returns [code]true[/code] whether you want the blend tree editor to display filter editing on this node. </description> </method> - <method name="_process" qualifiers="virtual"> - <return type="void" /> + <method name="_process" qualifiers="virtual const"> + <return type="float" /> <argument index="0" name="time" type="float" /> <argument index="1" name="seek" type="bool" /> <description> 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..df0e3a0459 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -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> 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..a954a06117 100644 --- a/doc/classes/AudioStream.xml +++ b/doc/classes/AudioStream.xml @@ -13,6 +13,21 @@ <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="get_length" qualifiers="const"> <return type="float" /> <description> 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/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/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 e23ceedc28..e5f60541b9 100644 --- a/doc/classes/CharacterBody2D.xml +++ b/doc/classes/CharacterBody2D.xml @@ -111,6 +111,7 @@ This method should be used in [method Node._physics_process] (or in a method called by [method Node._physics_process]), as it uses the physics step's [code]delta[/code] value automatically in calculations. Otherwise, the simulation will run at an incorrect speed. Modifies [member linear_velocity] if a slide collision occurred. To get the latest collision call [method get_last_slide_collision], for detailed information about collisions that occurred, use [method get_slide_collision]. When the body touches a moving platform, the platform's velocity is automatically added to the body motion. If a collision occurs due to the platform's motion, it will always be first in the slide collisions. + The general behaviour and available properties change according to the [member motion_mode]. Returns [code]true[/code] if the body collided, otherwise, returns [code]false[/code]. </description> </method> @@ -139,12 +140,18 @@ <member name="floor_stop_on_slope" type="bool" setter="set_floor_stop_on_slope_enabled" getter="is_floor_stop_on_slope_enabled" default="false"> If [code]true[/code], the body will not slide on floor's slopes when you include gravity in [code]linear_velocity[/code] when calling [method move_and_slide] and the body is standing still. </member> + <member name="free_mode_min_slide_angle" type="float" setter="set_free_mode_min_slide_angle" getter="get_free_mode_min_slide_angle" default="0.261799"> + Minimum angle (in radians) where the body is allowed to slide when it encounters a slope. The default value equals 15 degrees. + </member> <member name="linear_velocity" type="Vector2" setter="set_linear_velocity" getter="get_linear_velocity" default="Vector2(0, 0)"> Current velocity vector in pixels per second, used and modified during calls to [method move_and_slide]. </member> <member name="max_slides" type="int" setter="set_max_slides" getter="get_max_slides" default="4"> Maximum number of times the body can change direction before it stops when calling [method move_and_slide]. </member> + <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> @@ -156,5 +163,11 @@ </member> </members> <constants> + <constant name="MOTION_MODE_GROUNDED" value="0" enum="MotionMode"> + Apply when notions of walls, ceiling and floor are relevant. In this mode the body motion will react to slopes (acceleration/slowdown). This mode is suitable for sided games like platformers. + </constant> + <constant name="MOTION_MODE_FREE" value="1" enum="MotionMode"> + Apply when there is no notion of floor or ceiling. All collisions will be reported as [code]on_wall[/code]. In this mode, when you slide, the speed will be always constant. This mode is suitable for top-down games. + </constant> </constants> </class> diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml index 47078330f3..93f72d45ae 100644 --- a/doc/classes/CodeEdit.xml +++ b/doc/classes/CodeEdit.xml @@ -17,9 +17,9 @@ Override this method to define how the selected entry should be inserted. If [code]replace[/code] is true, any existing text should be replaced. </description> </method> - <method name="_filter_code_completion_candidates" qualifiers="virtual"> + <method name="_filter_code_completion_candidates" qualifiers="virtual const"> <return type="Array" /> - <argument index="0" name="candidates" type="Array" /> + <argument index="0" name="candidates" type="Dictionary[]" /> <description> Override this method to define what items in [code]candidates[/code] should be displayed. Both [code]candidates[/code] and the return is a [Array] of [Dictionary], see [method get_code_completion_option] for [Dictionary] content. 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..d6da4bc248 100644 --- a/doc/classes/ConfigFile.xml +++ b/doc/classes/ConfigFile.xml @@ -149,6 +149,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 0834521c91..5392189f6a 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -22,9 +22,9 @@ <link title="All GUI Demos">https://github.com/godotengine/godot-demo-projects/tree/master/gui</link> </tutorials> <methods> - <method name="_can_drop_data" qualifiers="virtual"> + <method name="_can_drop_data" qualifiers="virtual const"> <return type="bool" /> - <argument index="0" name="position" type="Vector2" /> + <argument index="0" name="at_position" type="Vector2" /> <argument index="1" name="data" type="Variant" /> <description> Godot calls this method to test if [code]data[/code] from a control's [method _get_drag_data] can be dropped at [code]position[/code]. [code]position[/code] is local to this control. @@ -49,7 +49,7 @@ </method> <method name="_drop_data" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="position" type="Vector2" /> + <argument index="0" name="at_position" type="Vector2" /> <argument index="1" name="data" type="Variant" /> <description> Godot calls this method to pass you the [code]data[/code] from a control's [method _get_drag_data] result. Godot first calls [method _can_drop_data] to test if [code]data[/code] is allowed to drop at [code]position[/code] where [code]position[/code] is local to this control. @@ -73,9 +73,9 @@ [/codeblocks] </description> </method> - <method name="_get_drag_data" qualifiers="virtual"> + <method name="_get_drag_data" qualifiers="virtual const"> <return type="Variant" /> - <argument index="0" name="position" type="Vector2" /> + <argument index="0" name="at_position" type="Vector2" /> <description> Godot calls this method to get data that can be dragged and dropped onto controls that expect drop data. Returns [code]null[/code] if there is no data to drag. Controls that want to receive drop data should implement [method _can_drop_data] and [method _drop_data]. [code]position[/code] is local to this control. Drag may be forced with [method force_drag]. A preview that will follow the mouse that should represent the data can be set with [method set_drag_preview]. A good time to set the preview is in this method. @@ -97,7 +97,7 @@ [/codeblocks] </description> </method> - <method name="_get_minimum_size" qualifiers="virtual"> + <method name="_get_minimum_size" qualifiers="virtual const"> <return type="Vector2" /> <description> Virtual method to be implemented by the user. Returns the minimum size for this control. Alternative to [member rect_min_size] for controlling minimum size via code. The actual minimum size will be the max value of these two (in each axis separately). @@ -141,15 +141,15 @@ </method> <method name="_has_point" qualifiers="virtual const"> <return type="bool" /> - <argument index="0" name="" type="Vector2" /> + <argument index="0" name="position" type="Vector2" /> <description> Virtual method to be implemented by the user. Returns whether the given [code]point[/code] is inside this control. If not overridden, default behavior is checking if the point is within control's Rect. [b]Note:[/b] If you want to check if a point is inside the control, you can use [code]get_rect().has_point(point)[/code]. </description> </method> - <method name="_make_custom_tooltip" qualifiers="virtual"> - <return type="Control" /> + <method name="_make_custom_tooltip" qualifiers="virtual const"> + <return type="Object" /> <argument index="0" name="for_text" type="String" /> <description> Virtual method to be implemented by the user. Returns a [Control] node that should be used as a tooltip instead of the default one. The [code]for_text[/code] includes the contents of the [member hint_tooltip] property. @@ -193,8 +193,8 @@ [/codeblocks] </description> </method> - <method name="_structured_text_parser" qualifiers="virtual"> - <return type="void" /> + <method name="_structured_text_parser" qualifiers="virtual const"> + <return type="Array" /> <argument index="0" name="args" type="Array" /> <argument index="1" name="text" type="String" /> <description> diff --git a/doc/classes/DirectionalLight3D.xml b/doc/classes/DirectionalLight3D.xml index 0060368207..e3badea0f4 100644 --- a/doc/classes/DirectionalLight3D.xml +++ b/doc/classes/DirectionalLight3D.xml @@ -36,7 +36,7 @@ <member name="directional_shadow_split_3" type="float" setter="set_param" getter="get_param" default="0.5"> The distance from shadow split 2 to split 3. Relative to [member directional_shadow_max_distance]. Only used when [member directional_shadow_mode] is [code]SHADOW_PARALLEL_4_SPLITS[/code]. </member> - <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" override="true" default="1.0" /> + <member name="shadow_bias" type="float" setter="set_param" getter="get_param" override="true" default="0.1" /> <member name="use_in_sky_only" type="bool" setter="set_sky_only" getter="is_sky_only" default="false"> If [code]true[/code], this [DirectionalLight3D] will not be used for anything except sky shaders. Use this for lights that impact your sky shader that you may want to hide from affecting the rest of the scene. For example, you may want to enable this when the sun in your sky shader falls below the horizon. </member> diff --git a/doc/classes/Directory.xml b/doc/classes/Directory.xml index 919960cc9f..e8e5a286b4 100644 --- a/doc/classes/Directory.xml +++ b/doc/classes/Directory.xml @@ -187,7 +187,7 @@ <argument index="0" name="from" type="String" /> <argument index="1" name="to" type="String" /> <description> - Renames (move) the [code]from[/code] file to the [code]to[/code] destination. Both arguments should be paths to files, either relative or absolute. If the destination file exists and is not access-protected, it will be overwritten. + Renames (move) the [code]from[/code] file or directory to the [code]to[/code] destination. Both arguments should be paths to files or directories, either relative or absolute. If the destination file or directory exists and is not access-protected, it will be overwritten. Returns one of the [enum Error] code constants ([code]OK[/code] on success). </description> </method> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index abb715b34e..4f495eaec9 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -649,6 +649,8 @@ <argument index="0" name="min_size" type="Vector2i" /> <argument index="1" name="window_id" type="int" default="0" /> <description> + Sets the minimum size for the given window to [code]min_size[/code] (in pixels). + [b]Note:[/b] By default, the main window has a minimum size of [code]Vector2i(64, 64)[/code]. This prevents issues that can arise when the window is resized to a near-zero size. </description> </method> <method name="window_set_mode"> diff --git a/doc/classes/EditorImportPlugin.xml b/doc/classes/EditorImportPlugin.xml index b379ccc8c6..da6738d6b7 100644 --- a/doc/classes/EditorImportPlugin.xml +++ b/doc/classes/EditorImportPlugin.xml @@ -113,28 +113,28 @@ <link title="Import plugins">https://docs.godotengine.org/en/latest/tutorials/plugins/editor/import_plugins.html</link> </tutorials> <methods> - <method name="_get_import_options" qualifiers="virtual"> + <method name="_get_import_options" qualifiers="virtual const"> <return type="Array" /> - <argument index="0" name="preset" type="int" /> + <argument index="0" name="preset_index" type="int" /> <description> Gets the options and default values for the preset at this index. Returns an Array of Dictionaries with the following keys: [code]name[/code], [code]default_value[/code], [code]property_hint[/code] (optional), [code]hint_string[/code] (optional), [code]usage[/code] (optional). </description> </method> - <method name="_get_import_order" qualifiers="virtual"> + <method name="_get_import_order" qualifiers="virtual const"> <return type="int" /> <description> Gets the order of this importer to be run when importing resources. Importers with [i]lower[/i] import orders will be called first, and higher values will be called later. Use this to ensure the importer runs after the dependencies are already imported. The default import order is [code]0[/code] unless overridden by a specific importer. See [enum ResourceImporter.ImportOrder] for some predefined values. </description> </method> - <method name="_get_importer_name" qualifiers="virtual"> + <method name="_get_importer_name" qualifiers="virtual const"> <return type="String" /> <description> Gets the unique name of the importer. </description> </method> - <method name="_get_option_visibility" qualifiers="virtual"> + <method name="_get_option_visibility" qualifiers="virtual const"> <return type="bool" /> - <argument index="0" name="option" type="String" /> + <argument index="0" name="option_name" type="StringName" /> <argument index="1" name="options" type="Dictionary" /> <description> This method can be overridden to hide specific import options if conditions are met. This is mainly useful for hiding options that depend on others if one of them is disabled. For example: @@ -163,50 +163,50 @@ Return [code]true[/code] to make all options always visible. </description> </method> - <method name="_get_preset_count" qualifiers="virtual"> + <method name="_get_preset_count" qualifiers="virtual const"> <return type="int" /> <description> Gets the number of initial presets defined by the plugin. Use [method _get_import_options] to get the default options for the preset and [method _get_preset_name] to get the name of the preset. </description> </method> - <method name="_get_preset_name" qualifiers="virtual"> + <method name="_get_preset_name" qualifiers="virtual const"> <return type="String" /> - <argument index="0" name="preset" type="int" /> + <argument index="0" name="preset_index" type="int" /> <description> Gets the name of the options preset at this index. </description> </method> - <method name="_get_priority" qualifiers="virtual"> + <method name="_get_priority" qualifiers="virtual const"> <return type="float" /> <description> Gets the priority of this plugin for the recognized extension. Higher priority plugins will be preferred. The default priority is [code]1.0[/code]. </description> </method> - <method name="_get_recognized_extensions" qualifiers="virtual"> - <return type="Array" /> + <method name="_get_recognized_extensions" qualifiers="virtual const"> + <return type="PackedStringArray" /> <description> Gets the list of file extensions to associate with this loader (case-insensitive). e.g. [code]["obj"][/code]. </description> </method> - <method name="_get_resource_type" qualifiers="virtual"> + <method name="_get_resource_type" qualifiers="virtual const"> <return type="String" /> <description> Gets the Godot resource type associated with this loader. e.g. [code]"Mesh"[/code] or [code]"Animation"[/code]. </description> </method> - <method name="_get_save_extension" qualifiers="virtual"> + <method name="_get_save_extension" qualifiers="virtual const"> <return type="String" /> <description> Gets the extension used to save this resource in the [code].godot/imported[/code] directory. </description> </method> - <method name="_get_visible_name" qualifiers="virtual"> + <method name="_get_visible_name" qualifiers="virtual const"> <return type="String" /> <description> Gets the name to display in the import window. You should choose this name as a continuation to "Import as", e.g. "Import as Special Mesh". </description> </method> - <method name="_import" qualifiers="virtual"> + <method name="_import" qualifiers="virtual const"> <return type="int" /> <argument index="0" name="source_file" type="String" /> <argument index="1" name="save_path" type="String" /> diff --git a/doc/classes/EditorInspectorPlugin.xml b/doc/classes/EditorInspectorPlugin.xml index 62fd7a1d6e..ee93379210 100644 --- a/doc/classes/EditorInspectorPlugin.xml +++ b/doc/classes/EditorInspectorPlugin.xml @@ -16,9 +16,9 @@ <link title="Inspector plugins">https://docs.godotengine.org/en/latest/tutorials/plugins/editor/inspector_plugins.html</link> </tutorials> <methods> - <method name="_can_handle" qualifiers="virtual"> + <method name="_can_handle" qualifiers="virtual const"> <return type="bool" /> - <argument index="0" name="object" type="Object" /> + <argument index="0" name="object" type="Variant" /> <description> Returns [code]true[/code] if this object can be handled by this plugin. </description> @@ -31,9 +31,9 @@ </method> <method name="_parse_category" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="category" type="String" /> + <argument index="0" name="object" type="Object" /> + <argument index="1" name="category" type="String" /> <description> - Called to allow adding controls at the beginning of the category. </description> </method> <method name="_parse_end" qualifiers="virtual"> @@ -44,11 +44,13 @@ </method> <method name="_parse_property" qualifiers="virtual"> <return type="bool" /> - <argument index="0" name="type" type="int" /> - <argument index="1" name="path" type="String" /> - <argument index="2" name="hint" type="int" /> - <argument index="3" name="hint_text" type="String" /> - <argument index="4" name="usage" type="int" /> + <argument index="0" name="object" type="Object" /> + <argument index="1" name="type" type="int" /> + <argument index="2" name="name" type="String" /> + <argument index="3" name="hint_type" type="int" /> + <argument index="4" name="hint_string" type="String" /> + <argument index="5" name="usage_flags" type="int" /> + <argument index="6" name="wide" type="bool" /> <description> Called to allow adding property specific editors to the inspector. Usually these inherit [EditorProperty]. Returning [code]true[/code] removes the built-in editor for this property, otherwise allows to insert a custom editor before the built-in one. </description> diff --git a/doc/classes/EditorNode3DGizmo.xml b/doc/classes/EditorNode3DGizmo.xml index 39f6805254..91e024cc1c 100644 --- a/doc/classes/EditorNode3DGizmo.xml +++ b/doc/classes/EditorNode3DGizmo.xml @@ -13,7 +13,7 @@ <return type="void" /> <argument index="0" name="id" type="int" /> <argument index="1" name="restore" type="Variant" /> - <argument index="2" name="cancel" type="bool" default="false" /> + <argument index="2" name="cancel" type="bool" /> <description> Override this method to commit a handle being edited (handles must have been previously added by [method add_handles]). This usually means creating an [UndoRedo] action for the change, using the current handle value as "do" and the [code]restore[/code] argument as "undo". If the [code]cancel[/code] argument is [code]true[/code], the [code]restore[/code] value should be directly set, without any [UndoRedo] action. @@ -22,14 +22,14 @@ <method name="_commit_subgizmos" qualifiers="virtual"> <return type="void" /> <argument index="0" name="ids" type="PackedInt32Array" /> - <argument index="1" name="restore" type="Array" /> - <argument index="2" name="cancel" type="bool" default="false" /> + <argument index="1" name="restores" type="Transform3D[]" /> + <argument index="2" name="cancel" type="bool" /> <description> Override this method to commit a group of subgizmos being edited (see [method _subgizmos_intersect_ray] and [method _subgizmos_intersect_frustum]). This usually means creating an [UndoRedo] action for the change, using the current transforms as "do" and the [code]restore[/code] transforms as "undo". If the [code]cancel[/code] argument is [code]true[/code], the [code]restore[/code] transforms should be directly set, without any [UndoRedo] action. </description> </method> - <method name="_get_handle_name" qualifiers="virtual"> + <method name="_get_handle_name" qualifiers="virtual const"> <return type="String" /> <argument index="0" name="id" type="int" /> <description> @@ -37,21 +37,21 @@ Handles can be named for reference to the user when editing. </description> </method> - <method name="_get_handle_value" qualifiers="virtual"> + <method name="_get_handle_value" qualifiers="virtual const"> <return type="Variant" /> <argument index="0" name="id" type="int" /> <description> Override this method to return the current value of a handle. This value will be requested at the start of an edit and used as the [code]restore[/code] argument in [method _commit_handle]. </description> </method> - <method name="_get_subgizmo_transform" qualifiers="virtual"> + <method name="_get_subgizmo_transform" qualifiers="virtual const"> <return type="Transform3D" /> <argument index="0" name="id" type="int" /> <description> Override this method to return the current transform of a subgizmo. This transform will be requested at the start of an edit and used as the [code]restore[/code] argument in [method _commit_subgizmos]. </description> </method> - <method name="_is_handle_highlighted" qualifiers="virtual"> + <method name="_is_handle_highlighted" qualifiers="virtual const"> <return type="bool" /> <argument index="0" name="id" type="int" /> <description> @@ -81,15 +81,15 @@ Override this method to update the node properties during subgizmo editing (see [method _subgizmos_intersect_ray] and [method _subgizmos_intersect_frustum]). The [code]transform[/code] is given in the Node3D's local coordinate system. </description> </method> - <method name="_subgizmos_intersect_frustum" qualifiers="virtual"> + <method name="_subgizmos_intersect_frustum" qualifiers="virtual const"> <return type="PackedInt32Array" /> <argument index="0" name="camera" type="Camera3D" /> - <argument index="1" name="frustum" type="Array" /> + <argument index="1" name="frustum" type="Plane[]" /> <description> Override this method to allow selecting subgizmos using mouse drag box selection. Given a [code]camera[/code] and a [code]frustum[/code], this method should return which subgizmos are contained within the frustum. The [code]frustum[/code] argument consists of an [code]Array[/code] with all the [code]Plane[/code]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, which can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos]. </description> </method> - <method name="_subgizmos_intersect_ray" qualifiers="virtual"> + <method name="_subgizmos_intersect_ray" qualifiers="virtual const"> <return type="int" /> <argument index="0" name="camera" type="Camera3D" /> <argument index="1" name="point" type="Vector2" /> diff --git a/doc/classes/EditorNode3DGizmoPlugin.xml b/doc/classes/EditorNode3DGizmoPlugin.xml index fb72427a7a..4ba455a336 100644 --- a/doc/classes/EditorNode3DGizmoPlugin.xml +++ b/doc/classes/EditorNode3DGizmoPlugin.xml @@ -10,7 +10,7 @@ <link title="Spatial gizmo plugins">https://docs.godotengine.org/en/latest/tutorials/plugins/editor/spatial_gizmos.html</link> </tutorials> <methods> - <method name="_can_be_hidden" qualifiers="virtual"> + <method name="_can_be_hidden" qualifiers="virtual const"> <return type="bool" /> <description> Override this method to define whether the gizmos handled by this plugin can be hidden or not. Returns [code]true[/code] if not overridden. @@ -19,9 +19,9 @@ <method name="_commit_handle" qualifiers="virtual"> <return type="void" /> <argument index="0" name="gizmo" type="EditorNode3DGizmo" /> - <argument index="1" name="id" type="int" /> + <argument index="1" name="handle_id" type="int" /> <argument index="2" name="restore" type="Variant" /> - <argument index="3" name="cancel" type="bool" default="false" /> + <argument index="3" name="cancel" type="bool" /> <description> Override this method to commit a handle being edited (handles must have been previously added by [method EditorNode3DGizmo.add_handles] during [method _redraw]). This usually means creating an [UndoRedo] action for the change, using the current handle value as "do" and the [code]restore[/code] argument as "undo". If the [code]cancel[/code] argument is [code]true[/code], the [code]restore[/code] value should be directly set, without any [UndoRedo] action. Called for this plugin's active gizmos. @@ -31,73 +31,73 @@ <return type="void" /> <argument index="0" name="gizmo" type="EditorNode3DGizmo" /> <argument index="1" name="ids" type="PackedInt32Array" /> - <argument index="2" name="restore" type="Array" /> - <argument index="3" name="cancel" type="bool" default="false" /> + <argument index="2" name="restores" type="Transform3D[]" /> + <argument index="3" name="cancel" type="bool" /> <description> Override this method to commit a group of subgizmos being edited (see [method _subgizmos_intersect_ray] and [method _subgizmos_intersect_frustum]). This usually means creating an [UndoRedo] action for the change, using the current transforms as "do" and the [code]restore[/code] transforms as "undo". If the [code]cancel[/code] argument is [code]true[/code], the [code]restore[/code] transforms should be directly set, without any [UndoRedo] action. As with all subgizmo methods, transforms are given in local space respect to the gizmo's Node3D. Called for this plugin's active gizmos. </description> </method> - <method name="_create_gizmo" qualifiers="virtual"> + <method name="_create_gizmo" qualifiers="virtual const"> <return type="EditorNode3DGizmo" /> - <argument index="0" name="spatial" type="Node3D" /> + <argument index="0" name="for_node_3d" type="Node3D" /> <description> Override this method to return a custom [EditorNode3DGizmo] for the spatial nodes of your choice, return [code]null[/code] for the rest of nodes. See also [method _has_gizmo]. </description> </method> - <method name="_get_gizmo_name" qualifiers="virtual"> + <method name="_get_gizmo_name" qualifiers="virtual const"> <return type="String" /> <description> Override this method to provide the name that will appear in the gizmo visibility menu. </description> </method> - <method name="_get_handle_name" qualifiers="virtual"> + <method name="_get_handle_name" qualifiers="virtual const"> <return type="String" /> <argument index="0" name="gizmo" type="EditorNode3DGizmo" /> - <argument index="1" name="id" type="int" /> + <argument index="1" name="handle_id" type="int" /> <description> Override this method to provide gizmo's handle names. Called for this plugin's active gizmos. </description> </method> - <method name="_get_handle_value" qualifiers="virtual"> + <method name="_get_handle_value" qualifiers="virtual const"> <return type="Variant" /> <argument index="0" name="gizmo" type="EditorNode3DGizmo" /> - <argument index="1" name="id" type="int" /> + <argument index="1" name="handle_id" type="int" /> <description> Override this method to return the current value of a handle. This value will be requested at the start of an edit and used as the [code]restore[/code] argument in [method _commit_handle]. Called for this plugin's active gizmos. </description> </method> - <method name="_get_priority" qualifiers="virtual"> + <method name="_get_priority" qualifiers="virtual const"> <return type="int" /> <description> Override this method to set the gizmo's priority. Gizmos with higher priority will have precedence when processing inputs like handles or subgizmos selection. All built-in editor gizmos return a priority of [code]-1[/code]. If not overridden, this method will return [code]0[/code], which means custom gizmos will automatically get higher priority than built-in gizmos. </description> </method> - <method name="_get_subgizmo_transform" qualifiers="virtual"> + <method name="_get_subgizmo_transform" qualifiers="virtual const"> <return type="Transform3D" /> <argument index="0" name="gizmo" type="EditorNode3DGizmo" /> - <argument index="1" name="id" type="int" /> + <argument index="1" name="subgizmo_id" type="int" /> <description> Override this method to return the current transform of a subgizmo. As with all subgizmo methods, the transform should be in local space respect to the gizmo's Node3D. This transform will be requested at the start of an edit and used in the [code]restore[/code] argument in [method _commit_subgizmos]. Called for this plugin's active gizmos. </description> </method> - <method name="_has_gizmo" qualifiers="virtual"> + <method name="_has_gizmo" qualifiers="virtual const"> <return type="bool" /> - <argument index="0" name="spatial" type="Node3D" /> + <argument index="0" name="for_node_3d" type="Node3D" /> <description> Override this method to define which Node3D nodes have a gizmo from this plugin. Whenever a [Node3D] node is added to a scene this method is called, if it returns [code]true[/code] the node gets a generic [EditorNode3DGizmo] assigned and is added to this plugin's list of active gizmos. </description> </method> - <method name="_is_handle_highlighted" qualifiers="virtual"> + <method name="_is_handle_highlighted" qualifiers="virtual const"> <return type="bool" /> <argument index="0" name="gizmo" type="EditorNode3DGizmo" /> - <argument index="1" name="id" type="int" /> + <argument index="1" name="handle_id" type="int" /> <description> Override this method to return [code]true[/code] whenever to given handle should be highlighted in the editor. Called for this plugin's active gizmos. </description> </method> - <method name="_is_selectable_when_hidden" qualifiers="virtual"> + <method name="_is_selectable_when_hidden" qualifiers="virtual const"> <return type="bool" /> <description> Override this method to define whether Node3D with this gizmo should be selectable even when the gizmo is hidden. @@ -113,9 +113,9 @@ <method name="_set_handle" qualifiers="virtual"> <return type="void" /> <argument index="0" name="gizmo" type="EditorNode3DGizmo" /> - <argument index="1" name="id" type="int" /> + <argument index="1" name="handle_id" type="int" /> <argument index="2" name="camera" type="Camera3D" /> - <argument index="3" name="point" type="Vector2" /> + <argument index="3" name="screen_pos" type="Vector2" /> <description> Override this method to update the node's properties when the user drags a gizmo handle (previously added with [method EditorNode3DGizmo.add_handles]). The provided [code]point[/code] is the mouse position in screen coordinates and the [code]camera[/code] can be used to convert it to raycasts. Called for this plugin's active gizmos. </description> @@ -123,26 +123,26 @@ <method name="_set_subgizmo_transform" qualifiers="virtual"> <return type="void" /> <argument index="0" name="gizmo" type="EditorNode3DGizmo" /> - <argument index="1" name="id" type="int" /> + <argument index="1" name="subgizmo_id" type="int" /> <argument index="2" name="transform" type="Transform3D" /> <description> Override this method to update the node properties during subgizmo editing (see [method _subgizmos_intersect_ray] and [method _subgizmos_intersect_frustum]). The [code]transform[/code] is given in the Node3D's local coordinate system. Called for this plugin's active gizmos. </description> </method> - <method name="_subgizmos_intersect_frustum" qualifiers="virtual"> + <method name="_subgizmos_intersect_frustum" qualifiers="virtual const"> <return type="PackedInt32Array" /> <argument index="0" name="gizmo" type="EditorNode3DGizmo" /> <argument index="1" name="camera" type="Camera3D" /> - <argument index="2" name="frustum" type="Array" /> + <argument index="2" name="frustum_planes" type="Plane[]" /> <description> Override this method to allow selecting subgizmos using mouse drag box selection. Given a [code]camera[/code] and a [code]frustum[/code], this method should return which subgizmos are contained within the frustum. The [code]frustum[/code] argument consists of an [code]Array[/code] with all the [code]Plane[/code]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, these identifiers can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos]. Called for this plugin's active gizmos. </description> </method> - <method name="_subgizmos_intersect_ray" qualifiers="virtual"> + <method name="_subgizmos_intersect_ray" qualifiers="virtual const"> <return type="int" /> <argument index="0" name="gizmo" type="EditorNode3DGizmo" /> <argument index="1" name="camera" type="Camera3D" /> - <argument index="2" name="point" type="Vector2" /> + <argument index="2" name="screen_pos" type="Vector2" /> <description> Override this method to allow selecting subgizmos using mouse clicks. Given a [code]camera[/code] and a [code]point[/code] in screen coordinates, this method should return which subgizmo should be selected. The returned value should be a unique subgizmo identifier, which can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos]. Called for this plugin's active gizmos. </description> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 37efca7f48..e564e8045c 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -38,7 +38,7 @@ </method> <method name="_edit" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="object" type="Object" /> + <argument index="0" name="object" type="Variant" /> <description> This function is used for plugins that edit specific object types (nodes or resources). It requests the editor to edit the given object. </description> @@ -49,18 +49,18 @@ Called by the engine when the user enables the [EditorPlugin] in the Plugin tab of the project settings window. </description> </method> - <method name="_forward_canvas_draw_over_viewport" qualifiers="virtual"> + <method name="_forward_3d_draw_over_viewport" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="overlay" type="Control" /> + <argument index="0" name="viewport_control" type="Control" /> <description> - Called by the engine when the 2D editor's viewport is updated. Use the [code]overlay[/code] [Control] for drawing. You can update the viewport manually by calling [method update_overlays]. + 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_canvas_draw_over_viewport(overlay): + func _forward_spatial_3d_over_viewport(overlay): # Draw a circle at cursor position. - overlay.draw_circle(overlay.get_local_mouse_position(), 64, Color.white) + overlay.draw_circle(overlay.get_local_mouse_position(), 64) - func _forward_canvas_gui_input(event): + func _forward_spatial_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 ForwardCanvasDrawOverViewport(Godot.Control overlay) + public override void ForwardSpatialDrawOverViewport(Godot.Control overlay) { // Draw a circle at cursor position. overlay.DrawCircle(overlay.GetLocalMousePosition(), 64, Colors.White); } - public override bool ForwardCanvasGuiInput(InputEvent @event) + public override bool ForwardSpatialGuiInput(Godot.Camera3D camera, InputEvent @event) { if (@event is InputEventMouseMotion) { @@ -87,28 +87,29 @@ [/codeblocks] </description> </method> - <method name="_forward_canvas_force_draw_over_viewport" qualifiers="virtual"> + <method name="_forward_3d_force_draw_over_viewport" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="overlay" type="Control" /> + <argument index="0" name="viewport_control" type="Control" /> <description> - This method is the same as [method _forward_canvas_draw_over_viewport], except it draws on top of everything. Useful when you need an extra layer that shows over anything else. + This method is the same as [method _forward_3d_draw_over_viewport], except it draws on top of everything. Useful when you need an extra layer that shows over anything else. You need to enable calling of this method by using [method set_force_draw_over_forwarding_enabled]. </description> </method> - <method name="_forward_canvas_gui_input" qualifiers="virtual"> + <method name="_forward_3d_gui_input" qualifiers="virtual"> <return type="bool" /> - <argument index="0" name="event" type="InputEvent" /> + <argument index="0" name="viewport_camera" type="Camera3D" /> + <argument index="1" name="event" type="InputEvent" /> <description> - Called when there is a root node in the current edited scene, [method _handles] is implemented and an [InputEvent] happens in the 2D viewport. Intercepts the [InputEvent], if [code]return true[/code] [EditorPlugin] consumes the [code]event[/code], otherwise forwards [code]event[/code] to other Editor classes. Example: + Called when there is a root node in the current edited scene, [method _handles] is implemented and an [InputEvent] happens in the 3D viewport. Intercepts the [InputEvent], if [code]return true[/code] [EditorPlugin] consumes the [code]event[/code], otherwise forwards [code]event[/code] to other Editor classes. Example: [codeblocks] [gdscript] - # Prevents the InputEvent to reach other Editor classes - func _forward_canvas_gui_input(event): + # Prevents the InputEvent to reach other Editor classes. + func _forward_spatial_gui_input(camera, event): return true [/gdscript] [csharp] - // Prevents the InputEvent to reach other Editor classes - public override bool ForwardCanvasGuiInput(InputEvent @event) + // Prevents the InputEvent to reach other Editor classes. + public override bool ForwardSpatialGuiInput(Camera3D camera, InputEvent @event) { return true; } @@ -118,12 +119,12 @@ [codeblocks] [gdscript] # Consumes InputEventMouseMotion and forwards other InputEvent types. - func _forward_canvas_gui_input(event): + func _forward_spatial_gui_input(camera, event): return event is InputEventMouseMotion [/gdscript] [csharp] // Consumes InputEventMouseMotion and forwards other InputEvent types. - public override bool ForwardCanvasGuiInput(InputEvent @event) + public override bool ForwardSpatialGuiInput(Camera3D camera, InputEvent @event) { return @event is InputEventMouseMotion; } @@ -131,18 +132,18 @@ [/codeblocks] </description> </method> - <method name="_forward_spatial_draw_over_viewport" qualifiers="virtual"> + <method name="_forward_canvas_draw_over_viewport" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="overlay" type="Control" /> + <argument index="0" name="viewport_control" type="Control" /> <description> - 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]. + Called by the engine when the 2D 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_draw_over_viewport(overlay): + func _forward_canvas_draw_over_viewport(overlay): # Draw a circle at cursor position. - overlay.draw_circle(overlay.get_local_mouse_position(), 64) + overlay.draw_circle(overlay.get_local_mouse_position(), 64, Color.white) - func _forward_spatial_gui_input(camera, event): + func _forward_canvas_gui_input(event): if event is InputEventMouseMotion: # Redraw viewport when cursor is moved. update_overlays() @@ -150,13 +151,13 @@ return false [/gdscript] [csharp] - public override void ForwardSpatialDrawOverViewport(Godot.Control overlay) + public override void ForwardCanvasDrawOverViewport(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 ForwardCanvasGuiInput(InputEvent @event) { if (@event is InputEventMouseMotion) { @@ -169,29 +170,28 @@ [/codeblocks] </description> </method> - <method name="_forward_spatial_force_draw_over_viewport" qualifiers="virtual"> + <method name="_forward_canvas_force_draw_over_viewport" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="overlay" type="Control" /> + <argument index="0" name="viewport_control" type="Control" /> <description> - This method is the same as [method _forward_spatial_draw_over_viewport], except it draws on top of everything. Useful when you need an extra layer that shows over anything else. + This method is the same as [method _forward_canvas_draw_over_viewport], except it draws on top of everything. Useful when you need an extra layer that shows over anything else. You need to enable calling of this method by using [method set_force_draw_over_forwarding_enabled]. </description> </method> - <method name="_forward_spatial_gui_input" qualifiers="virtual"> + <method name="_forward_canvas_gui_input" qualifiers="virtual"> <return type="bool" /> - <argument index="0" name="camera" type="Camera3D" /> - <argument index="1" name="event" type="InputEvent" /> + <argument index="0" name="event" type="InputEvent" /> <description> - Called when there is a root node in the current edited scene, [method _handles] is implemented and an [InputEvent] happens in the 3D viewport. Intercepts the [InputEvent], if [code]return true[/code] [EditorPlugin] consumes the [code]event[/code], otherwise forwards [code]event[/code] to other Editor classes. Example: + Called when there is a root node in the current edited scene, [method _handles] is implemented and an [InputEvent] happens in the 2D viewport. Intercepts the [InputEvent], if [code]return true[/code] [EditorPlugin] consumes the [code]event[/code], otherwise forwards [code]event[/code] to other Editor classes. Example: [codeblocks] [gdscript] - # Prevents the InputEvent to reach other Editor classes. - func _forward_spatial_gui_input(camera, event): + # Prevents the InputEvent to reach other Editor classes + func _forward_canvas_gui_input(event): return true [/gdscript] [csharp] - // Prevents the InputEvent to reach other Editor classes. - public override bool ForwardSpatialGuiInput(Camera3D camera, InputEvent @event) + // Prevents the InputEvent to reach other Editor classes + public override bool ForwardCanvasGuiInput(InputEvent @event) { return true; } @@ -201,12 +201,12 @@ [codeblocks] [gdscript] # Consumes InputEventMouseMotion and forwards other InputEvent types. - func _forward_spatial_gui_input(camera, event): + func _forward_canvas_gui_input(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 ForwardCanvasGuiInput(InputEvent @event) { return @event is InputEventMouseMotion; } @@ -214,13 +214,13 @@ [/codeblocks] </description> </method> - <method name="_get_breakpoints" qualifiers="virtual"> + <method name="_get_breakpoints" qualifiers="virtual const"> <return type="PackedStringArray" /> <description> This is for editors that edit script-based objects. You can return a list of breakpoints in the format ([code]script:line[/code]), for example: [code]res://path_to_script.gd:25[/code]. </description> </method> - <method name="_get_plugin_icon" qualifiers="virtual"> + <method name="_get_plugin_icon" qualifiers="virtual const"> <return type="Texture2D" /> <description> Override this method in your plugin to return a [Texture2D] in order to give it an icon. @@ -246,14 +246,14 @@ [/codeblocks] </description> </method> - <method name="_get_plugin_name" qualifiers="virtual"> + <method name="_get_plugin_name" qualifiers="virtual const"> <return type="String" /> <description> Override this method in your plugin to provide the name of the plugin when displayed in the Godot editor. For main screen plugins, this appears at the top of the screen, to the right of the "2D", "3D", "Script", and "AssetLib" buttons. </description> </method> - <method name="_get_state" qualifiers="virtual"> + <method name="_get_state" qualifiers="virtual const"> <return type="Dictionary" /> <description> Gets the state of your plugin editor. This is used when saving the scene (so state is kept when opening it again) and for switching tabs (so state can be restored when the tab returns). @@ -261,19 +261,19 @@ </method> <method name="_get_window_layout" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="layout" type="ConfigFile" /> + <argument index="0" name="configuration" type="ConfigFile" /> <description> Gets the GUI layout of the plugin. This is used to save the project's editor layout when [method queue_save_layout] is called or the editor layout was changed(For example changing the position of a dock). </description> </method> - <method name="_handles" qualifiers="virtual"> + <method name="_handles" qualifiers="virtual const"> <return type="bool" /> - <argument index="0" name="object" type="Object" /> + <argument index="0" name="object" type="Variant" /> <description> - Implement this function if your plugin edits a specific type of object (Resource or Node). If you return [code]true[/code], then you will get the functions [method _edit] and [method _make_visible] called when the editor requests them. If you have declared the methods [method _forward_canvas_gui_input] and [method _forward_spatial_gui_input] these will be called too. + Implement this function if your plugin edits a specific type of object (Resource or Node). If you return [code]true[/code], then you will get the functions [method _edit] and [method _make_visible] called when the editor requests them. If you have declared the methods [method _forward_canvas_gui_input] and [method _forward_3d_gui_input] these will be called too. </description> </method> - <method name="_has_main_screen" qualifiers="virtual"> + <method name="_has_main_screen" qualifiers="virtual const"> <return type="bool" /> <description> Returns [code]true[/code] if this is a main screen editor plugin (it goes in the workspace selector together with [b]2D[/b], [b]3D[/b], [b]Script[/b] and [b]AssetLib[/b]). @@ -302,7 +302,7 @@ </method> <method name="_set_window_layout" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="layout" type="ConfigFile" /> + <argument index="0" name="configuration" type="ConfigFile" /> <description> Restore the plugin GUI layout saved by [method _get_window_layout]. </description> @@ -586,19 +586,19 @@ <method name="set_force_draw_over_forwarding_enabled"> <return type="void" /> <description> - Enables calling of [method _forward_canvas_force_draw_over_viewport] for the 2D editor and [method _forward_spatial_force_draw_over_viewport] for the 3D editor when their viewports are updated. You need to call this method only once and it will work permanently for this plugin. + Enables calling of [method _forward_canvas_force_draw_over_viewport] for the 2D editor and [method _forward_3d_force_draw_over_viewport] for the 3D editor when their viewports are updated. You need to call this method only once and it will work permanently for this plugin. </description> </method> <method name="set_input_event_forwarding_always_enabled"> <return type="void" /> <description> - Use this method if you always want to receive inputs from 3D view screen inside [method _forward_spatial_gui_input]. It might be especially usable if your plugin will want to use raycast in the scene. + Use this method if you always want to receive inputs from 3D view screen inside [method _forward_3d_gui_input]. It might be especially usable if your plugin will want to use raycast in the scene. </description> </method> <method name="update_overlays" qualifiers="const"> <return type="int" /> <description> - Updates the overlays of the 2D and 3D editor viewport. Causes methods [method _forward_canvas_draw_over_viewport], [method _forward_canvas_force_draw_over_viewport], [method _forward_spatial_draw_over_viewport] and [method _forward_spatial_force_draw_over_viewport] to be called. + Updates the overlays of the 2D and 3D editor viewport. Causes methods [method _forward_canvas_draw_over_viewport], [method _forward_canvas_force_draw_over_viewport], [method _forward_3d_draw_over_viewport] and [method _forward_3d_force_draw_over_viewport] to be called. </description> </method> </methods> diff --git a/doc/classes/EditorProperty.xml b/doc/classes/EditorProperty.xml index 725b0ba8ff..822bcfd255 100644 --- a/doc/classes/EditorProperty.xml +++ b/doc/classes/EditorProperty.xml @@ -57,6 +57,11 @@ Puts the [code]editor[/code] control below the property label. The control must be previously added using [method Node.add_child]. </description> </method> + <method name="update_property"> + <return type="void" /> + <description> + </description> + </method> </methods> <members> <member name="checkable" type="bool" setter="set_checkable" getter="is_checkable" default="false"> diff --git a/doc/classes/EditorResourceConversionPlugin.xml b/doc/classes/EditorResourceConversionPlugin.xml index 3de508c88d..8543afa4ae 100644 --- a/doc/classes/EditorResourceConversionPlugin.xml +++ b/doc/classes/EditorResourceConversionPlugin.xml @@ -7,17 +7,23 @@ <tutorials> </tutorials> <methods> - <method name="_convert" qualifiers="virtual"> + <method name="_convert" qualifiers="virtual const"> <return type="Resource" /> <argument index="0" name="resource" type="Resource" /> <description> </description> </method> - <method name="_converts_to" qualifiers="virtual"> + <method name="_converts_to" qualifiers="virtual const"> <return type="String" /> <description> </description> </method> + <method name="_handles" qualifiers="virtual const"> + <return type="bool" /> + <argument index="0" name="resource" type="Resource" /> + <description> + </description> + </method> </methods> <constants> </constants> diff --git a/doc/classes/EditorResourcePreviewGenerator.xml b/doc/classes/EditorResourcePreviewGenerator.xml index 7054b1f4d0..033e03c5b5 100644 --- a/doc/classes/EditorResourcePreviewGenerator.xml +++ b/doc/classes/EditorResourcePreviewGenerator.xml @@ -9,41 +9,41 @@ <tutorials> </tutorials> <methods> - <method name="_can_generate_small_preview" qualifiers="virtual"> + <method name="_can_generate_small_preview" qualifiers="virtual const"> <return type="bool" /> <description> If this function returns [code]true[/code], the generator will call [method _generate] or [method _generate_from_path] for small previews as well. By default, it returns [code]false[/code]. </description> </method> - <method name="_generate" qualifiers="virtual"> + <method name="_generate" qualifiers="virtual const"> <return type="Texture2D" /> - <argument index="0" name="from" type="Resource" /> - <argument index="1" name="size" type="Vector2" /> + <argument index="0" name="resource" type="Resource" /> + <argument index="1" name="size" type="Vector2i" /> <description> Generate a preview from a given resource with the specified size. This must always be implemented. Returning an empty texture is an OK way to fail and let another generator take care. Care must be taken because this function is always called from a thread (not the main thread). </description> </method> - <method name="_generate_from_path" qualifiers="virtual"> + <method name="_generate_from_path" qualifiers="virtual const"> <return type="Texture2D" /> <argument index="0" name="path" type="String" /> - <argument index="1" name="size" type="Vector2" /> + <argument index="1" name="size" type="Vector2i" /> <description> Generate a preview directly from a path with the specified size. Implementing this is optional, as default code will load and call [method _generate]. Returning an empty texture is an OK way to fail and let another generator take care. Care must be taken because this function is always called from a thread (not the main thread). </description> </method> - <method name="_generate_small_preview_automatically" qualifiers="virtual"> + <method name="_generate_small_preview_automatically" qualifiers="virtual const"> <return type="bool" /> <description> If this function returns [code]true[/code], the generator will automatically generate the small previews from the normal preview texture generated by the methods [method _generate] or [method _generate_from_path]. By default, it returns [code]false[/code]. </description> </method> - <method name="_handles" qualifiers="virtual"> + <method name="_handles" qualifiers="virtual const"> <return type="bool" /> <argument index="0" name="type" type="String" /> <description> diff --git a/doc/classes/EditorSceneImporter.xml b/doc/classes/EditorSceneImporter.xml index 2e9d6a43d8..a400db551f 100644 --- a/doc/classes/EditorSceneImporter.xml +++ b/doc/classes/EditorSceneImporter.xml @@ -8,12 +8,12 @@ <tutorials> </tutorials> <methods> - <method name="_get_extensions" qualifiers="virtual"> - <return type="Array" /> + <method name="_get_extensions" qualifiers="virtual const"> + <return type="PackedStringArray" /> <description> </description> </method> - <method name="_get_import_flags" qualifiers="virtual"> + <method name="_get_import_flags" qualifiers="virtual const"> <return type="int" /> <description> </description> @@ -27,7 +27,7 @@ </description> </method> <method name="_import_scene" qualifiers="virtual"> - <return type="Node" /> + <return type="Object" /> <argument index="0" name="path" type="String" /> <argument index="1" name="flags" type="int" /> <argument index="2" name="bake_fps" type="int" /> diff --git a/doc/classes/EditorSceneImporterMesh.xml b/doc/classes/EditorSceneImporterMesh.xml index 3a9eea87bb..b0f233da2f 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"> @@ -23,44 +28,56 @@ <argument index="4" name="material" type="Material" default="null" /> <argument index="5" name="name" type="String" default="""" /> <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 +85,20 @@ <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_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 +106,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 +114,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/EditorScenePostImport.xml b/doc/classes/EditorScenePostImport.xml index 95b0b42d9f..43ca3db5fa 100644 --- a/doc/classes/EditorScenePostImport.xml +++ b/doc/classes/EditorScenePostImport.xml @@ -57,7 +57,7 @@ <methods> <method name="_post_import" qualifiers="virtual"> <return type="Object" /> - <argument index="0" name="scene" type="Object" /> + <argument index="0" name="scene" type="Node" /> <description> Called after the scene was imported. This method must return the modified version of the scene. </description> diff --git a/doc/classes/EditorSyntaxHighlighter.xml b/doc/classes/EditorSyntaxHighlighter.xml index 37644a8595..394a4ada46 100644 --- a/doc/classes/EditorSyntaxHighlighter.xml +++ b/doc/classes/EditorSyntaxHighlighter.xml @@ -5,24 +5,18 @@ </brief_description> <description> Base syntax highlighter resource all editor syntax highlighters extend from, it is used in the [ScriptEditor]. - Add a syntax highlighter to an individual script by calling [method ScriptEditorBase._add_syntax_highlighter]. To apply to all scripts on open, call [method ScriptEditor.register_syntax_highlighter] + Add a syntax highlighter to an individual script by calling ScriptEditorBase._add_syntax_highlighter (currently not working). To apply to all scripts on open, call [method ScriptEditor.register_syntax_highlighter] </description> <tutorials> </tutorials> <methods> - <method name="_get_name" qualifiers="virtual"> + <method name="_get_name" qualifiers="virtual const"> <return type="String" /> <description> Virtual method which can be overridden to return the syntax highlighter name. </description> </method> - <method name="_get_supported_extentions" qualifiers="virtual"> - <return type="Array" /> - <description> - Virtual method which can be overridden to return the supported file extensions. - </description> - </method> - <method name="_get_supported_languages" qualifiers="virtual"> + <method name="_get_supported_languages" qualifiers="virtual const"> <return type="Array" /> <description> Virtual method which can be overridden to return the supported language names. diff --git a/doc/classes/EditorTranslationParserPlugin.xml b/doc/classes/EditorTranslationParserPlugin.xml index 47da8ec37b..94e96e985f 100644 --- a/doc/classes/EditorTranslationParserPlugin.xml +++ b/doc/classes/EditorTranslationParserPlugin.xml @@ -102,8 +102,8 @@ <tutorials> </tutorials> <methods> - <method name="_get_recognized_extensions" qualifiers="virtual"> - <return type="Array" /> + <method name="_get_recognized_extensions" qualifiers="virtual const"> + <return type="PackedStringArray" /> <description> Gets the list of file extensions to associate with this parser, e.g. [code]["csv"][/code]. </description> diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index 886a18900e..8b399f64c9 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -84,11 +84,16 @@ </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 +130,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 +141,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 907cf8587d..c870026d58 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -10,6 +10,14 @@ <tutorials> </tutorials> <methods> + <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" /> + <description> + Virtual method which can be overridden to customize how connections are drawn. + </description> + </method> <method name="add_valid_connection_type"> <return type="void" /> <argument index="0" name="from_type" type="int" /> @@ -64,6 +72,14 @@ Removes the connection between the [code]from_port[/code] slot of the [code]from[/code] GraphNode and the [code]to_port[/code] slot of the [code]to[/code] GraphNode. If the connection does not exist, no connection is removed. </description> </method> + <method name="get_connection_line"> + <return type="PackedVector2Array" /> + <argument index="0" name="from" type="Vector2" /> + <argument index="1" name="to" type="Vector2" /> + <description> + Returns the points which would make up a connection between [code]from[/code] and [code]to[/code]. + </description> + </method> <method name="get_connection_list" qualifiers="const"> <return type="Array" /> <description> diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml index 22398cc3ce..861627b526 100644 --- a/doc/classes/HTTPClient.xml +++ b/doc/classes/HTTPClient.xml @@ -138,7 +138,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 +166,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/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/TextFile.xml b/doc/classes/InputEventShortcut.xml index 1c2c2ff25c..35cca02cf7 100644 --- a/doc/classes/TextFile.xml +++ b/doc/classes/InputEventShortcut.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="TextFile" inherits="Resource" version="4.0"> +<class name="InputEventShortcut" inherits="InputEvent" version="4.0"> <brief_description> </brief_description> <description> @@ -8,6 +8,10 @@ </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/Light3D.xml b/doc/classes/Light3D.xml index 380e9314d4..cd2f4eca18 100644 --- a/doc/classes/Light3D.xml +++ b/doc/classes/Light3D.xml @@ -61,7 +61,7 @@ <member name="light_specular" type="float" setter="set_param" getter="get_param" default="0.5"> The intensity of the specular blob in objects affected by the light. At [code]0[/code], the light becomes a pure diffuse light. When not baking emission, this can be used to avoid unrealistic reflections when placing lights above an emissive surface. </member> - <member name="shadow_bias" type="float" setter="set_param" getter="get_param" default="0.1"> + <member name="shadow_bias" type="float" setter="set_param" getter="get_param" default="0.2"> Used to adjust shadow appearance. Too small a value results in self-shadowing ("shadow acne"), while too large a value causes shadows to separate from casters ("peter-panning"). Adjust as needed. </member> <member name="shadow_blur" type="float" setter="set_param" getter="get_param" default="1.0"> @@ -75,7 +75,7 @@ </member> <member name="shadow_fog_fade" type="float" setter="set_param" getter="get_param" default="0.1"> </member> - <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" default="2.0"> + <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" default="1.0"> Offsets the lookup into the shadow map by the object's normal. This can be used to reduce self-shadowing artifacts without using [member shadow_bias]. In practice, this value should be tweaked along with [member shadow_bias] to reduce artifacts as much as possible. </member> <member name="shadow_reverse_cull_face" type="bool" setter="set_shadow_reverse_cull_face" getter="get_shadow_reverse_cull_face" default="false"> 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/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml index b9f50ad02a..610b00efe9 100644 --- a/doc/classes/MultiplayerAPI.xml +++ b/doc/classes/MultiplayerAPI.xml @@ -66,34 +66,6 @@ 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. </description> </method> - <method name="send_despawn"> - <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="path" type="NodePath" /> - <argument index="3" name="data" type="PackedByteArray" default="PackedByteArray()" /> - <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 SPAWN_MODE_SERVER] (see [method spawnable_config]) and the request is sent by the server (see [method is_network_server]), the receiving peer(s) will automatically queue for deletion the node at [code]path[/code] and emit the signal [signal network_despawn]. In all other cases no deletion happens, and the signal [signal network_despawn_request] is emitted instead. - </description> - </method> - <method name="send_spawn"> - <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="path" type="NodePath" /> - <argument index="3" name="data" type="PackedByteArray" default="PackedByteArray()" /> - <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 SPAWN_MODE_SERVER] (see [method spawnable_config]) and the request is sent by the server (see [method 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 network_spawn]. In all other cases no instantiation happens, and the signal [signal network_spawn_request] is emitted instead. - </description> - </method> - <method name="spawnable_config"> - <return type="int" enum="Error" /> - <argument index="0" name="scene_id" type="int" /> - <argument index="1" name="spawn_mode" type="int" enum="MultiplayerAPI.SpawnMode" /> - <description> - Configures the MultiplayerAPI to track instances of the [PackedScene] idenfied by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication. See [enum SpawnMode] for the possible configurations. - </description> - </method> </methods> <members> <member name="allow_object_decoding" type="bool" setter="set_allow_object_decoding" getter="is_object_decoding_allowed" default="false"> @@ -106,6 +78,8 @@ <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> + <member name="replicator" type="MultiplayerReplicator" setter="" getter="get_replicator"> + </member> <member name="root_node" type="Node" setter="set_root_node" getter="get_root_node"> The root node to use for RPCs. Instead of an absolute path, a relative path will be used to find the node upon which the RPC should be executed. This effectively allows to have different branches of the scene tree to be managed by different MultiplayerAPI, allowing for example to run both client and server in the same scene. @@ -122,25 +96,6 @@ Emitted when this MultiplayerAPI's [member network_peer] fails to establish a connection to a server. Only emitted on clients. </description> </signal> - <signal name="network_despawn"> - <argument index="0" name="id" type="int" /> - <argument index="1" name="scene_id" type="int" /> - <argument index="2" name="node" type="Node" /> - <argument index="3" name="data" type="PackedByteArray" /> - <description> - Emitted on a client before deleting a local Node upon receiving a despawn request from the server. - </description> - </signal> - <signal name="network_despawn_request"> - <argument index="0" name="id" type="int" /> - <argument index="1" name="scene_id" type="int" /> - <argument index="2" name="parent" type="Node" /> - <argument index="3" name="name" type="String" /> - <argument index="4" name="data" type="PackedByteArray" /> - <description> - Emitted when a network despawn request has been received from a client, or for a [PackedScene] that has been configured as [constant SPAWN_MODE_CUSTOM]. - </description> - </signal> <signal name="network_peer_connected"> <argument index="0" name="id" type="int" /> <description> @@ -160,39 +115,6 @@ 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. </description> </signal> - <signal name="network_spawn"> - <argument index="0" name="id" type="int" /> - <argument index="1" name="scene_id" type="int" /> - <argument index="2" name="node" type="Node" /> - <argument index="3" name="data" type="PackedByteArray" /> - <description> - Emitted on a client after a new Node is instantiated locally and added to the SceneTree upon receiving a spawn request from the server. - </description> - </signal> - <signal name="network_spawn_request"> - <argument index="0" name="id" type="int" /> - <argument index="1" name="scene_id" type="int" /> - <argument index="2" name="parent" type="Node" /> - <argument index="3" name="name" type="String" /> - <argument index="4" name="data" type="PackedByteArray" /> - <description> - Emitted when a network spawn request has been received from a client, or for a [PackedScene] that has been configured as [constant SPAWN_MODE_CUSTOM]. - </description> - </signal> - <signal name="network_spawnable_added"> - <argument index="0" name="scene_id" type="int" /> - <argument index="1" name="node" type="Node" /> - <description> - Emitted when an instance of a [PackedScene] that has been configured for networking enters the [SceneTree]. See [method spawnable_config]. - </description> - </signal> - <signal name="network_spawnable_removed"> - <argument index="0" name="scene_id" type="int" /> - <argument index="1" name="node" type="Node" /> - <description> - Emitted when an instance of a [PackedScene] that has been configured for networking leaves the [SceneTree]. See [method spawnable_config]. - </description> - </signal> <signal name="server_disconnected"> <description> Emitted when this MultiplayerAPI's [member network_peer] disconnects from server. Only emitted on clients. @@ -212,14 +134,5 @@ <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> - <constant name="SPAWN_MODE_NONE" value="0" enum="SpawnMode"> - Used with [method spawnable_config] to identify a [PackedScene] that should not be replicated. - </constant> - <constant name="SPAWN_MODE_SERVER" value="1" enum="SpawnMode"> - Used with [method spawnable_config] to identify a [PackedScene] that should be automatically replicated from server to clients. - </constant> - <constant name="SPAWN_MODE_CUSTOM" value="2" enum="SpawnMode"> - Used with [method spawnable_config] to identify a [PackedScene] that can be manually replicated among peers. - </constant> </constants> </class> diff --git a/doc/classes/MultiplayerReplicator.xml b/doc/classes/MultiplayerReplicator.xml new file mode 100644 index 0000000000..15029e181f --- /dev/null +++ b/doc/classes/MultiplayerReplicator.xml @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="MultiplayerReplicator" inherits="Object" version="4.0"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + <method name="decode_state"> + <return type="int" enum="Error" /> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="object" type="Object" /> + <argument index="2" name="data" type="PackedByteArray" /> + <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]. + </description> + </method> + <method name="despawn"> + <return type="int" enum="Error" /> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="object" type="Object" /> + <argument index="2" name="peer_id" type="int" default="0" /> + <description> + </description> + </method> + <method name="encode_state"> + <return type="PackedByteArray" /> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="object" type="Object" /> + <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]. + </description> + </method> + <method name="send_despawn"> + <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="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. + </description> + </method> + <method name="send_spawn"> + <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="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. + </description> + </method> + <method name="spawn"> + <return type="int" enum="Error" /> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="object" type="Object" /> + <argument index="2" name="peer_id" type="int" default="0" /> + <description> + </description> + </method> + <method name="spawn_config"> + <return type="int" enum="Error" /> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="spawn_mode" type="int" enum="MultiplayerReplicator.ReplicationMode" /> + <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 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. + Tip: You can use a custom property in the scene main script to return a customly optimized state representation. + </description> + </method> + </methods> + <signals> + <signal name="despawn_requested"> + <argument index="0" name="id" type="int" /> + <argument index="1" name="scene_id" type="int" /> + <argument index="2" name="parent" type="Node" /> + <argument index="3" name="name" type="String" /> + <argument index="4" name="data" type="PackedByteArray" /> + <description> + Emitted when a network despawn request has been received from a client, or for a [PackedScene] that has been configured as [constant REPLICATION_MODE_CUSTOM]. + </description> + </signal> + <signal name="despawned"> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="node" type="Node" /> + <description> + Emitted on a client before deleting a local Node upon receiving a despawn request from the server. + </description> + </signal> + <signal name="replicated_instance_added"> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="node" type="Node" /> + <description> + Emitted when an instance of a [PackedScene] that has been configured for networking enters the [SceneTree]. See [method spawn_config]. + </description> + </signal> + <signal name="replicated_instance_removed"> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="node" type="Node" /> + <description> + Emitted when an instance of a [PackedScene] that has been configured for networking leaves the [SceneTree]. See [method spawn_config]. + </description> + </signal> + <signal name="spawn_requested"> + <argument index="0" name="id" type="int" /> + <argument index="1" name="scene_id" type="int" /> + <argument index="2" name="parent" type="Node" /> + <argument index="3" name="name" type="String" /> + <argument index="4" name="data" type="PackedByteArray" /> + <description> + Emitted when a network spawn request has been received from a client, or for a [PackedScene] that has been configured as [constant REPLICATION_MODE_CUSTOM]. + </description> + </signal> + <signal name="spawned"> + <argument index="0" name="scene_id" type="int" /> + <argument index="1" name="node" type="Node" /> + <description> + Emitted on a client after a new Node is instantiated locally and added to the SceneTree upon receiving a spawn request from the server. + </description> + </signal> + </signals> + <constants> + <constant name="REPLICATION_MODE_NONE" value="0" enum="ReplicationMode"> + Used with [method spawn_config] to identify a [PackedScene] that should not be replicated. + </constant> + <constant name="REPLICATION_MODE_SERVER" value="1" enum="ReplicationMode"> + Used with [method spawn_config] to identify a [PackedScene] that should be automatically replicated from server to clients. + </constant> + <constant name="REPLICATION_MODE_CUSTOM" value="2" enum="ReplicationMode"> + Used with [method spawn_config] to identify a [PackedScene] that can be manually replicated among peers. + </constant> + </constants> +</class> 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 096fbbf2c0..f5bc705ecb 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -35,8 +35,8 @@ Corresponds to the [constant NOTIFICATION_EXIT_TREE] notification in [method Object._notification] and signal [signal tree_exiting]. To get notified when the node has already left the active tree, connect to the [signal tree_exited]. </description> </method> - <method name="_get_configuration_warnings" qualifiers="virtual"> - <return type="String[]" /> + <method name="_get_configuration_warnings" qualifiers="virtual const"> + <return type="PackedStringArray" /> <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. @@ -96,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. @@ -376,7 +376,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"> @@ -584,7 +591,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"> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index e5d45189c7..757730f6c8 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -197,12 +197,6 @@ Returns the path to the current engine executable. </description> </method> - <method name="get_external_data_dir" qualifiers="const"> - <return type="String" /> - <description> - On Android, returns the absolute directory path where user data can be written to external storage if available. On all other platforms, this will return the same location as [method get_user_data_dir]. - </description> - </method> <method name="get_granted_permissions" qualifiers="const"> <return type="PackedStringArray" /> <description> @@ -270,9 +264,11 @@ <method name="get_system_dir" qualifiers="const"> <return type="String" /> <argument index="0" name="dir" type="int" enum="OS.SystemDir" /> + <argument index="1" name="shared_storage" type="bool" default="true" /> <description> Returns the actual path to commonly used folders across different platforms. Available locations are specified in [enum SystemDir]. [b]Note:[/b] This method is implemented on Android, Linux, macOS and Windows. + [b]Note:[/b] Shared storage is implemented on Android and allows to differentiate between app specific and shared directories. Shared directories have additional restrictions on Android. </description> </method> <method name="get_thread_caller_id" qualifiers="const"> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 9ad79dc17a..c9e9a0699c 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]. 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/PhysicsServer2D.xml b/doc/classes/PhysicsServer2D.xml index 1df2fd0158..d0ae665139 100644 --- a/doc/classes/PhysicsServer2D.xml +++ b/doc/classes/PhysicsServer2D.xml @@ -595,7 +595,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 +727,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 +846,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"> diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index d46e38ac5f..c47a311161 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -571,7 +571,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 +850,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 +1177,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 +1231,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> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 0d1fa0e70f..58c9d9e44b 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -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 c329821646..24f7f4274e 100644 --- a/doc/classes/RenderingDevice.xml +++ b/doc/classes/RenderingDevice.xml @@ -448,6 +448,7 @@ <method name="shader_compile_binary_from_spirv"> <return type="PackedByteArray" /> <argument index="0" name="spirv_data" type="RDShaderSPIRV" /> + <argument index="1" name="name" type="String" default="""" /> <description> </description> </method> @@ -465,8 +466,9 @@ </description> </method> <method name="shader_create_from_spirv"> - <return type="PackedByteArray" /> + <return type="RID" /> <argument index="0" name="spirv_data" type="RDShaderSPIRV" /> + <argument index="1" name="name" type="String" default="""" /> <description> </description> </method> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index df8bfb7e34..638c657492 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -3082,6 +3082,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 +3904,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/Resource.xml b/doc/classes/Resource.xml index 701ecf815c..65dedf5280 100644 --- a/doc/classes/Resource.xml +++ b/doc/classes/Resource.xml @@ -12,12 +12,6 @@ <link title="When and how to avoid using nodes for everything">https://docs.godotengine.org/en/latest/getting_started/workflow/best_practices/node_alternatives.html</link> </tutorials> <methods> - <method name="_setup_local_to_scene" qualifiers="virtual"> - <return type="void" /> - <description> - Virtual function which can be overridden to customize the behavior value of [method setup_local_to_scene]. - </description> - </method> <method name="duplicate" qualifiers="const"> <return type="Resource" /> <argument index="0" name="subresources" type="bool" default="false" /> @@ -54,7 +48,7 @@ <method name="setup_local_to_scene"> <return type="void" /> <description> - This method is called when a resource with [member resource_local_to_scene] enabled is loaded from a [PackedScene] instantiation. Its behavior can be customized by overriding [method _setup_local_to_scene] from script. + This method is called when a resource with [member resource_local_to_scene] enabled is loaded from a [PackedScene] instantiation. Its behavior can be customized by connecting [signal setup_local_to_scene_requested] from script. For most resources, this method performs no base logic. [ViewportTexture] performs custom logic to properly set the proxy texture and flags in the local viewport. </description> </method> @@ -84,6 +78,10 @@ [b]Note:[/b] This signal is not emitted automatically for custom resources, which means that you need to create a setter and emit the signal yourself. </description> </signal> + <signal name="setup_local_to_scene_requested"> + <description> + </description> + </signal> </signals> <constants> </constants> diff --git a/doc/classes/ResourceFormatLoader.xml b/doc/classes/ResourceFormatLoader.xml index bce5785cc1..0c2bb26c02 100644 --- a/doc/classes/ResourceFormatLoader.xml +++ b/doc/classes/ResourceFormatLoader.xml @@ -11,22 +11,28 @@ <tutorials> </tutorials> <methods> - <method name="_get_dependencies" qualifiers="virtual"> - <return type="void" /> + <method name="_exists" qualifiers="virtual const"> + <return type="bool" /> + <argument index="0" name="path" type="String" /> + <description> + </description> + </method> + <method name="_get_dependencies" qualifiers="virtual const"> + <return type="PackedStringArray" /> <argument index="0" name="path" type="String" /> - <argument index="1" name="add_types" type="String" /> + <argument index="1" name="add_types" type="bool" /> <description> If implemented, gets the dependencies of a given resource. If [code]add_types[/code] is [code]true[/code], paths should be appended [code]::TypeName[/code], where [code]TypeName[/code] is the class name of the dependency. [b]Note:[/b] Custom resource types defined by scripts aren't known by the [ClassDB], so you might just return [code]"Resource"[/code] for them. </description> </method> - <method name="_get_recognized_extensions" qualifiers="virtual"> + <method name="_get_recognized_extensions" qualifiers="virtual const"> <return type="PackedStringArray" /> <description> Gets the list of extensions for files this loader is able to read. </description> </method> - <method name="_get_resource_type" qualifiers="virtual"> + <method name="_get_resource_type" qualifiers="virtual const"> <return type="String" /> <argument index="0" name="path" type="String" /> <description> @@ -34,15 +40,21 @@ [b]Note:[/b] Custom resource types defined by scripts aren't known by the [ClassDB], so you might just return [code]"Resource"[/code] for them. </description> </method> - <method name="_handles_type" qualifiers="virtual"> + <method name="_get_resource_uid" qualifiers="virtual const"> + <return type="int" /> + <argument index="0" name="path" type="String" /> + <description> + </description> + </method> + <method name="_handles_type" qualifiers="virtual const"> <return type="bool" /> - <argument index="0" name="typename" type="StringName" /> + <argument index="0" name="type" type="StringName" /> <description> Tells which resource class this loader can load. [b]Note:[/b] Custom resource types defined by scripts aren't known by the [ClassDB], so you might just handle [code]"Resource"[/code] for them. </description> </method> - <method name="_load" qualifiers="virtual"> + <method name="_load" qualifiers="virtual const"> <return type="Variant" /> <argument index="0" name="path" type="String" /> <argument index="1" name="original_path" type="String" /> @@ -53,10 +65,10 @@ The [code]cache_mode[/code] property defines whether and how the cache should be used or updated when loading the resource. See [enum CacheMode] for details. </description> </method> - <method name="_rename_dependencies" qualifiers="virtual"> + <method name="_rename_dependencies" qualifiers="virtual const"> <return type="int" /> <argument index="0" name="path" type="String" /> - <argument index="1" name="renames" type="String" /> + <argument index="1" name="renames" type="Dictionary" /> <description> If implemented, renames dependencies within the given resource and saves it. [code]renames[/code] is a dictionary [code]{ String => String }[/code] mapping old dependency paths to new paths. Returns [constant OK] on success, or an [enum Error] constant in case of failure. diff --git a/doc/classes/ResourceFormatSaver.xml b/doc/classes/ResourceFormatSaver.xml index ef9eebc953..7ee8875321 100644 --- a/doc/classes/ResourceFormatSaver.xml +++ b/doc/classes/ResourceFormatSaver.xml @@ -10,14 +10,14 @@ <tutorials> </tutorials> <methods> - <method name="_get_recognized_extensions" qualifiers="virtual"> + <method name="_get_recognized_extensions" qualifiers="virtual const"> <return type="PackedStringArray" /> <argument index="0" name="resource" type="Resource" /> <description> Returns the list of extensions available for saving the resource object, provided it is recognized (see [method _recognize]). </description> </method> - <method name="_recognize" qualifiers="virtual"> + <method name="_recognize" qualifiers="virtual const"> <return type="bool" /> <argument index="0" name="resource" type="Resource" /> <description> diff --git a/doc/classes/RichTextEffect.xml b/doc/classes/RichTextEffect.xml index 142afbb860..fd93f6be56 100644 --- a/doc/classes/RichTextEffect.xml +++ b/doc/classes/RichTextEffect.xml @@ -23,7 +23,7 @@ <link title="RichTextEffect test project (third-party)">https://github.com/Eoin-ONeill-Yokai/Godot-Rich-Text-Effect-Test-Project</link> </tutorials> <methods> - <method name="_process_custom_fx" qualifiers="virtual"> + <method name="_process_custom_fx" qualifiers="virtual const"> <return type="bool" /> <argument index="0" name="char_fx" type="CharFXTransform" /> <description> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 7bbcc5e0b5..e77232a613 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -555,7 +555,7 @@ <theme_item name="italics_font_size" data_type="font_size" type="int"> The font size used for italics text. </theme_item> - <theme_item name="line_separation" data_type="constant" type="int" default="1"> + <theme_item name="line_separation" data_type="constant" type="int" default="0"> The vertical space between lines. </theme_item> <theme_item name="mono_font" data_type="font" type="Font"> diff --git a/doc/classes/ScriptEditorBase.xml b/doc/classes/ScriptEditorBase.xml index 0efcde5638..08baa705e8 100644 --- a/doc/classes/ScriptEditorBase.xml +++ b/doc/classes/ScriptEditorBase.xml @@ -9,13 +9,6 @@ <tutorials> </tutorials> <methods> - <method name="_add_syntax_highlighter" qualifiers="virtual"> - <return type="void" /> - <argument index="0" name="highlighter" type="Object" /> - <description> - Adds a [EditorSyntaxHighlighter] to the open script. - </description> - </method> <method name="get_base_editor" qualifiers="const"> <return type="Control" /> <description> 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/String.xml b/doc/classes/String.xml index 0376a3f96e..de9eb518c6 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -181,7 +181,17 @@ <method name="get_extension" qualifiers="const"> <return type="String" /> <description> - If the string is a valid file path, returns the extension. + Returns the extension without the leading period character ([code].[/code]) if the string is a valid file name or path. If the string does not contain an extension, returns an empty string instead. + [codeblock] + print("/path/to/file.txt".get_extension()) # "txt" + print("file.txt".get_extension()) # "txt" + print("file.sample.txt".get_extension()) # "txt" + print(".txt".get_extension()) # "txt" + print("file.txt.".get_extension()) # "" (empty string) + print("file.txt..".get_extension()) # "" (empty string) + print("txt".get_extension()) # "" (empty string) + print("".get_extension()) # "" (empty string) + [/codeblock] </description> </method> <method name="get_file" qualifiers="const"> @@ -229,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"> @@ -241,7 +251,7 @@ <method name="is_rel_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"> @@ -410,6 +420,21 @@ <argument index="0" name="number" type="float" /> <argument index="1" name="decimals" type="int" default="-1" /> <description> + Converts a [float] to a string representation of a decimal number. + The number of decimal places can be specified with [code]decimals[/code]. If [code]decimals[/code] is [code]-1[/code] (default), decimal places will be automatically adjusted so that the string representation has 14 significant digits (counting both digits to the left and the right of the decimal point). + Trailing zeros are not included in the string. The last digit will be rounded and not truncated. + Some examples: + [codeblock] + String.num(3.141593) # "3.141593" + String.num(3.141593, 3) # "3.142" + String.num(3.14159300) # "3.141593", no trailing zeros. + # Last digit will be rounded up here, which reduces total digit count since + # trailing zeros are removed: + String.num(42.129999, 5) # "42.13" + # If `decimals` is not specified, the total amount of significant digits is 14: + String.num(-0.0000012345432123454321) # "-0.00000123454321" + String.num(-10000.0000012345432123454321) # "-10000.0000012345" + [/codeblock] </description> </method> <method name="num_scientific" qualifiers="static"> @@ -621,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/SyntaxHighlighter.xml b/doc/classes/SyntaxHighlighter.xml index ea0d1c27a3..c478cb0eb6 100644 --- a/doc/classes/SyntaxHighlighter.xml +++ b/doc/classes/SyntaxHighlighter.xml @@ -17,7 +17,7 @@ Virtual method which can be overridden to clear any local caches. </description> </method> - <method name="_get_line_syntax_highlighting" qualifiers="virtual"> + <method name="_get_line_syntax_highlighting" qualifiers="virtual const"> <return type="Dictionary" /> <argument index="0" name="line" type="int" /> <description> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 68d20e41d0..abb4119584 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -30,7 +30,7 @@ </method> <method name="_handle_unicode_input" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="unicode" type="int" /> + <argument index="0" name="unicode_char" type="int" /> <description> Override this method to define what happens when the types in the provided key [code]unicode[/code]. </description> @@ -464,12 +464,24 @@ Returns if the user has IME text. </description> </method> + <method name="has_redo" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if a "redo" action is available. + </description> + </method> <method name="has_selection" qualifiers="const"> <return type="bool" /> <description> Returns [code]true[/code] if the user has selected text. </description> </method> + <method name="has_undo" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if an "undo" action is available. + </description> + </method> <method name="insert_line_at"> <return type="void" /> <argument index="0" name="line" 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..b32168ebc5 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="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/Theme.xml b/doc/classes/Theme.xml index 1124fb8084..edf5874432 100644 --- a/doc/classes/Theme.xml +++ b/doc/classes/Theme.xml @@ -81,19 +81,6 @@ Unmarks [code]theme_type[/code] as being a variation of any other type. </description> </method> - <method name="copy_default_theme"> - <return type="void" /> - <description> - Sets the theme's values to a copy of the default theme values. - </description> - </method> - <method name="copy_theme"> - <return type="void" /> - <argument index="0" name="other" type="Theme" /> - <description> - Sets the theme's values to a copy of a given theme. - </description> - </method> <method name="get_color" qualifiers="const"> <return type="Color" /> <argument index="0" name="name" type="StringName" /> @@ -340,6 +327,14 @@ Returns [code]true[/code] if [code]theme_type[/code] is marked as a variation of [code]base_type[/code] in this theme. </description> </method> + <method name="merge_with"> + <return type="void" /> + <argument index="0" name="other" type="Theme" /> + <description> + Adds missing and overrides existing definitions with values from the [code]other[/code] [Theme]. + [b]Note:[/b] This modifies the current theme. If you want to merge two themes together without modifying either one, create a new empty theme and merge the other two into it one after another. + </description> + </method> <method name="rename_color"> <return type="void" /> <argument index="0" name="old_name" type="StringName" /> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 50a573d30f..ed24905254 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -552,6 +552,9 @@ <theme_item name="hseparation" data_type="constant" type="int" default="4"> The horizontal space between item cells. This is also used as the margin at the start of an item when folding is disabled. </theme_item> + <theme_item name="indeterminate" data_type="icon" type="Texture2D"> + The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is indeterminate. + </theme_item> <theme_item name="item_margin" data_type="constant" type="int" default="12"> The horizontal margin at the start of an item. This is used when folding is enabled for the item. </theme_item> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 944b9fdc06..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"> @@ -340,6 +348,13 @@ Returns [code]true[/code] if column [code]column[/code] is editable. </description> </method> + <method name="is_indeterminate" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="column" type="int" /> + <description> + Returns [code]true[/code] if the given column is indeterminate. + </description> + </method> <method name="is_selectable" qualifiers="const"> <return type="bool" /> <argument index="0" name="column" type="int" /> @@ -415,7 +430,7 @@ <argument index="0" name="column" type="int" /> <argument index="1" name="checked" type="bool" /> <description> - If [code]true[/code], the column [code]column[/code] is checked. + If [code]true[/code], the column [code]column[/code] is checked. Clears column's indeterminate status. </description> </method> <method name="set_custom_as_button"> @@ -457,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"> @@ -507,6 +531,15 @@ Sets the given column's icon's texture region. </description> </method> + <method name="set_indeterminate"> + <return type="void" /> + <argument index="0" name="column" type="int" /> + <argument index="1" name="indeterminate" type="bool" /> + <description> + If [code]true[/code], the column [code]column[/code] is marked indeterminate. + [b]Note:[/b] If set [code]true[/code] from [code]false[/code], then column is cleared of checked status. + </description> + </method> <method name="set_language"> <return type="void" /> <argument index="0" name="column" type="int" /> diff --git a/doc/classes/UndoRedo.xml b/doc/classes/UndoRedo.xml index 4417447891..def6fe5d1f 100644 --- a/doc/classes/UndoRedo.xml +++ b/doc/classes/UndoRedo.xml @@ -166,13 +166,13 @@ This is useful mostly to check if something changed from a saved version. </description> </method> - <method name="has_redo"> + <method name="has_redo" qualifiers="const"> <return type="bool" /> <description> Returns [code]true[/code] if a "redo" action is available. </description> </method> - <method name="has_undo"> + <method name="has_undo" qualifiers="const"> <return type="bool" /> <description> Returns [code]true[/code] if an "undo" action is available. diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 4a62d3ec7b..46f38e91f7 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -113,26 +113,33 @@ 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> </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 +157,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 +211,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 +274,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/VisualShaderNodeCustom.xml b/doc/classes/VisualShaderNodeCustom.xml index f6dbd2ad43..3a489419c1 100644 --- a/doc/classes/VisualShaderNodeCustom.xml +++ b/doc/classes/VisualShaderNodeCustom.xml @@ -16,17 +16,17 @@ <link title="Visual Shader plugins">https://docs.godotengine.org/en/latest/tutorials/plugins/editor/visual_shader_plugins.html</link> </tutorials> <methods> - <method name="_get_category" qualifiers="virtual"> + <method name="_get_category" qualifiers="virtual const"> <return type="String" /> <description> Override this method to define the path to the associated custom node in the Visual Shader Editor's members dialog. The path may look like [code]"MyGame/MyFunctions/Noise"[/code]. Defining this method is [b]optional[/b]. If not overridden, the node will be filed under the "Addons" category. </description> </method> - <method name="_get_code" qualifiers="virtual"> + <method name="_get_code" qualifiers="virtual const"> <return type="String" /> - <argument index="0" name="input_vars" type="Array" /> - <argument index="1" name="output_vars" type="Array" /> + <argument index="0" name="input_vars" type="PackedStringArray" /> + <argument index="1" name="output_vars" type="String[]" /> <argument index="2" name="mode" type="int" /> <argument index="3" name="type" type="int" /> <description> @@ -37,14 +37,14 @@ Defining this method is [b]required[/b]. </description> </method> - <method name="_get_description" qualifiers="virtual"> + <method name="_get_description" qualifiers="virtual const"> <return type="String" /> <description> Override this method to define the description of the associated custom node in the Visual Shader Editor's members dialog. Defining this method is [b]optional[/b]. </description> </method> - <method name="_get_global_code" qualifiers="virtual"> + <method name="_get_global_code" qualifiers="virtual const"> <return type="String" /> <argument index="0" name="mode" type="int" /> <description> @@ -54,22 +54,22 @@ Defining this method is [b]optional[/b]. </description> </method> - <method name="_get_input_port_count" qualifiers="virtual"> + <method name="_get_input_port_count" qualifiers="virtual const"> <return type="int" /> <description> Override this method to define the amount of input ports of the associated custom node. Defining this method is [b]required[/b]. If not overridden, the node has no input ports. </description> </method> - <method name="_get_input_port_name" qualifiers="virtual"> - <return type="StringName" /> + <method name="_get_input_port_name" qualifiers="virtual const"> + <return type="String" /> <argument index="0" name="port" type="int" /> <description> Override this method to define the names of input ports of the associated custom node. The names are used both for the input slots in the editor and as identifiers in the shader code, and are passed in the [code]input_vars[/code] array in [method _get_code]. Defining this method is [b]optional[/b], but recommended. If not overridden, input ports are named as [code]"in" + str(port)[/code]. </description> </method> - <method name="_get_input_port_type" qualifiers="virtual"> + <method name="_get_input_port_type" qualifiers="virtual const"> <return type="int" /> <argument index="0" name="port" type="int" /> <description> @@ -77,29 +77,29 @@ Defining this method is [b]optional[/b], but recommended. If not overridden, input ports will return the [constant VisualShaderNode.PORT_TYPE_SCALAR] type. </description> </method> - <method name="_get_name" qualifiers="virtual"> + <method name="_get_name" qualifiers="virtual const"> <return type="String" /> <description> Override this method to define the name of the associated custom node in the Visual Shader Editor's members dialog and graph. Defining this method is [b]optional[/b], but recommended. If not overridden, the node will be named as "Unnamed". </description> </method> - <method name="_get_output_port_count" qualifiers="virtual"> + <method name="_get_output_port_count" qualifiers="virtual const"> <return type="int" /> <description> Override this method to define the amount of output ports of the associated custom node. Defining this method is [b]required[/b]. If not overridden, the node has no output ports. </description> </method> - <method name="_get_output_port_name" qualifiers="virtual"> - <return type="StringName" /> + <method name="_get_output_port_name" qualifiers="virtual const"> + <return type="String" /> <argument index="0" name="port" type="int" /> <description> Override this method to define the names of output ports of the associated custom node. The names are used both for the output slots in the editor and as identifiers in the shader code, and are passed in the [code]output_vars[/code] array in [method _get_code]. Defining this method is [b]optional[/b], but recommended. If not overridden, output ports are named as [code]"out" + str(port)[/code]. </description> </method> - <method name="_get_output_port_type" qualifiers="virtual"> + <method name="_get_output_port_type" qualifiers="virtual const"> <return type="int" /> <argument index="0" name="port" type="int" /> <description> @@ -107,14 +107,14 @@ Defining this method is [b]optional[/b], but recommended. If not overridden, output ports will return the [constant VisualShaderNode.PORT_TYPE_SCALAR] type. </description> </method> - <method name="_get_return_icon_type" qualifiers="virtual"> + <method name="_get_return_icon_type" qualifiers="virtual const"> <return type="int" /> <description> Override this method to define the return icon of the associated custom node in the Visual Shader Editor's members dialog. Defining this method is [b]optional[/b]. If not overridden, no return icon is shown. </description> </method> - <method name="_is_highend" qualifiers="virtual"> + <method name="_is_highend" qualifiers="virtual const"> <return type="bool" /> <description> Override this method to enable high-end mark in the Visual Shader Editor's members dialog. 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..46d8d7e4e8 --- /dev/null +++ b/doc/classes/XRInterfaceExtension.xml @@ -0,0 +1,126 @@ +<?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="" type="RID" /> + <argument index="1" name="" 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="rect" type="Rect2i" /> + <argument index="2" name="use_layer" type="bool" /> + <argument index="3" name="layer" type="int" /> + <argument index="4" name="apply_lens_distortion" type="bool" /> + <argument index="5" name="eye_center" type="Vector2" /> + <argument index="6" name="k1" type="float" /> + <argument index="7" name="k2" type="float" /> + <argument index="8" name="upscale" type="float" /> + <argument index="9" 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> + </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/classes/float.xml b/doc/classes/float.xml index 60878eb0bd..4bf04fe25f 100644 --- a/doc/classes/float.xml +++ b/doc/classes/float.xml @@ -4,9 +4,13 @@ Float built-in type. </brief_description> <description> - Float built-in type. + The [float] built-in type is a 64-bit double-precision floating-point number, equivalent to [code]double[/code] in C++. This type has 14 reliable decimal digits of precision. The [float] type can be stored in [Variant], which is the generic type used by the engine. The maximum value of [float] is approximately [code]1.79769e308[/code], and the minimum is approximately [code]-1.79769e308[/code]. + Many methods and properties in the engine use 32-bit single-precision floating-point numbers instead, equivalent to [code]float[/code] in C++, which have 6 reliable decimal digits of precision. For data structures such as [Vector2] and [Vector3], Godot uses 32-bit floating-point numbers by default, but it can be changed to use 64-bit doubles if Godot is compiled with the [code]float=64[/code] option. + Math done using the [float] type is not guaranteed to be exact or deterministic, and will often result in small errors. You should usually use the [method @GlobalScope.is_equal_approx] and [method @GlobalScope.is_zero_approx] methods instead of [code]==[/code] to compare [float] values for equality. </description> <tutorials> + <link title="Wikipedia: Double-precision floating-point format">https://en.wikipedia.org/wiki/Double-precision_floating-point_format</link> + <link title="Wikipedia: Single-precision floating-point format">https://en.wikipedia.org/wiki/Single-precision_floating-point_format</link> </tutorials> <methods> <method name="float" qualifiers="constructor"> @@ -231,11 +235,13 @@ <method name="operator unary+" qualifiers="operator"> <return type="float" /> <description> + Returns the same value as if the [code]+[/code] was not there. Unary [code]+[/code] does nothing, but sometimes it can make your code more readable. </description> </method> <method name="operator unary-" qualifiers="operator"> <return type="float" /> <description> + Returns the negative value of the [float]. If positive, turns the number negative. If negative, turns the number positive. With floats, the number zero can be either positive or negative. </description> </method> </methods> diff --git a/doc/classes/int.xml b/doc/classes/int.xml index 84a01aa0d0..32b5fe1012 100644 --- a/doc/classes/int.xml +++ b/doc/classes/int.xml @@ -327,11 +327,13 @@ <method name="operator unary+" qualifiers="operator"> <return type="int" /> <description> + Returns the same value as if the [code]+[/code] was not there. Unary [code]+[/code] does nothing, but sometimes it can make your code more readable. </description> </method> <method name="operator unary-" qualifiers="operator"> <return type="int" /> <description> + Returns the negated value of the [int]. If positive, turns the number negative. If negative, turns the number positive. If zero, does nothing. </description> </method> <method name="operator |" qualifiers="operator"> diff --git a/doc/tools/makerst.py b/doc/tools/makerst.py index 6f9a07af1f..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] @@ -1031,9 +1033,6 @@ def make_enum(t, state): # type: (str, State) -> str if c in state.classes and e not in state.classes[c].enums: c = "@GlobalScope" - if not c in state.classes and c.startswith("_"): - c = c[1:] # Remove the underscore prefix - if c in state.classes and e in state.classes[c].enums: return ":ref:`{0}<enum_{1}_{0}>`".format(e, c) 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/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index dbe932f74c..6ad24f255f 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_ @@ -4442,7 +4450,10 @@ bool RenderingDeviceVulkan::_uniform_add_binding(Vector<Vector<VkDescriptorSetLa } #endif -#define SHADER_BINARY_VERSION 1 +//version 1: initial +//version 2: Added shader name + +#define SHADER_BINARY_VERSION 2 String RenderingDeviceVulkan::shader_get_binary_cache_key() const { return "Vulkan-SV" + itos(SHADER_BINARY_VERSION); @@ -4476,9 +4487,10 @@ struct RenderingDeviceVulkanShaderBinaryData { uint32_t push_constant_size; uint32_t push_constants_vk_stage; uint32_t stage_count; + uint32_t shader_name_len; }; -Vector<uint8_t> RenderingDeviceVulkan::shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv) { +Vector<uint8_t> RenderingDeviceVulkan::shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv, const String &p_shader_name) { RenderingDeviceVulkanShaderBinaryData binary_data; binary_data.vertex_input_mask = 0; binary_data.fragment_outputs = 0; @@ -4835,9 +4847,19 @@ Vector<uint8_t> RenderingDeviceVulkan::shader_compile_binary_from_spirv(const Ve binary_data.set_count = uniform_info.size(); binary_data.stage_count = p_spirv.size(); + CharString shader_name_utf = p_shader_name.utf8(); + + binary_data.shader_name_len = shader_name_utf.length(); + uint32_t total_size = sizeof(uint32_t) * 3; //header + version + main datasize; total_size += sizeof(RenderingDeviceVulkanShaderBinaryData); + total_size += binary_data.shader_name_len; + + if ((binary_data.shader_name_len % 4) != 0) { //alignment rules are really strange + total_size += 4 - (binary_data.shader_name_len % 4); + } + for (int i = 0; i < uniform_info.size(); i++) { total_size += sizeof(uint32_t); total_size += uniform_info[i].size() * sizeof(RenderingDeviceVulkanShaderBinaryDataBinding); @@ -4864,6 +4886,12 @@ Vector<uint8_t> RenderingDeviceVulkan::shader_compile_binary_from_spirv(const Ve offset += sizeof(uint32_t); memcpy(binptr + offset, &binary_data, sizeof(RenderingDeviceVulkanShaderBinaryData)); offset += sizeof(RenderingDeviceVulkanShaderBinaryData); + memcpy(binptr + offset, shader_name_utf.ptr(), binary_data.shader_name_len); + offset += binary_data.shader_name_len; + + if ((binary_data.shader_name_len % 4) != 0) { //alignment rules are really strange + offset += 4 - (binary_data.shader_name_len % 4); + } for (int i = 0; i < uniform_info.size(); i++) { int count = uniform_info[i].size(); @@ -4934,6 +4962,16 @@ RID RenderingDeviceVulkan::shader_create_from_bytecode(const Vector<uint8_t> &p_ read_offset += sizeof(uint32_t) * 3 + bin_data_size; + String name; + + if (binary_data.shader_name_len) { + name.parse_utf8((const char *)(binptr + read_offset), binary_data.shader_name_len); + read_offset += binary_data.shader_name_len; + if ((binary_data.shader_name_len % 4) != 0) { //alignment rules are really strange + read_offset += 4 - (binary_data.shader_name_len % 4); + } + } + Vector<Vector<VkDescriptorSetLayoutBinding>> set_bindings; Vector<Vector<UniformInfo>> uniform_info; @@ -5088,6 +5126,7 @@ RID RenderingDeviceVulkan::shader_create_from_bytecode(const Vector<uint8_t> &p_ shader.compute_local_size[1] = compute_local_size[1]; shader.compute_local_size[2] = compute_local_size[2]; shader.specialization_constants = specialization_constants; + shader.name = name; String error_text; @@ -6449,7 +6488,6 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma specialization_info.write[i].pData = data_ptr; specialization_info.write[i].mapEntryCount = specialization_map_entries[i].size(); specialization_info.write[i].pMapEntries = specialization_map_entries[i].ptr(); - pipeline_stages.write[i].pSpecializationInfo = specialization_info.ptr() + i; } } @@ -6476,7 +6514,7 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma RenderPipeline pipeline; VkResult err = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &graphics_pipeline_create_info, nullptr, &pipeline.pipeline); - ERR_FAIL_COND_V_MSG(err, RID(), "vkCreateGraphicsPipelines failed with error " + itos(err) + "."); + ERR_FAIL_COND_V_MSG(err, RID(), "vkCreateGraphicsPipelines failed with error " + itos(err) + " for shader '" + shader->name + "'."); pipeline.set_formats = shader->set_formats; pipeline.push_constant_stages = shader->push_constant.push_constants_vk_stage; @@ -7927,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; } diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h index 6175369285..dc1b78c1d5 100644 --- a/drivers/vulkan/rendering_device_vulkan.h +++ b/drivers/vulkan/rendering_device_vulkan.h @@ -639,6 +639,7 @@ class RenderingDeviceVulkan : public RenderingDevice { Vector<VkPipelineShaderStageCreateInfo> pipeline_stages; Vector<SpecializationConstant> specialization_constants; VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; + String name; //used for debug }; String _shader_uniform_debug(RID p_shader, int p_set = -1); @@ -1043,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); @@ -1088,7 +1090,7 @@ public: /****************/ virtual String shader_get_binary_cache_key() const; - virtual Vector<uint8_t> shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv); + virtual Vector<uint8_t> shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv, const String &p_shader_name = ""); virtual RID shader_create_from_bytecode(const Vector<uint8_t> &p_shader_binary); diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 87749450c4..625c222b67 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -976,37 +976,35 @@ Error VulkanContext::_create_device() { sdevice.queueCreateInfoCount = 2; } -#ifdef VK_VERSION_1_2 VkPhysicalDeviceVulkan11Features vulkan11features; - - vulkan11features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; - vulkan11features.pNext = nullptr; - // !BAS! Need to figure out which ones of these we want enabled... - vulkan11features.storageBuffer16BitAccess = 0; - vulkan11features.uniformAndStorageBuffer16BitAccess = 0; - vulkan11features.storagePushConstant16 = 0; - vulkan11features.storageInputOutput16 = 0; - vulkan11features.multiview = multiview_capabilities.is_supported; - vulkan11features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported; - vulkan11features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported; - vulkan11features.variablePointersStorageBuffer = 0; - vulkan11features.variablePointers = 0; - vulkan11features.protectedMemory = 0; - vulkan11features.samplerYcbcrConversion = 0; - vulkan11features.shaderDrawParameters = 0; - - sdevice.pNext = &vulkan11features; -#elif VK_VERSION_1_1 VkPhysicalDeviceMultiviewFeatures multiview_features; + if (vulkan_major > 1 || vulkan_minor >= 2) { + vulkan11features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + vulkan11features.pNext = nullptr; + // !BAS! Need to figure out which ones of these we want enabled... + vulkan11features.storageBuffer16BitAccess = 0; + vulkan11features.uniformAndStorageBuffer16BitAccess = 0; + vulkan11features.storagePushConstant16 = 0; + vulkan11features.storageInputOutput16 = 0; + vulkan11features.multiview = multiview_capabilities.is_supported; + vulkan11features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported; + vulkan11features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported; + vulkan11features.variablePointersStorageBuffer = 0; + vulkan11features.variablePointers = 0; + vulkan11features.protectedMemory = 0; + vulkan11features.samplerYcbcrConversion = 0; + vulkan11features.shaderDrawParameters = 0; + + sdevice.pNext = &vulkan11features; + } else if (vulkan_major == 1 && vulkan_minor == 1) { + multiview_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; + multiview_features.pNext = nullptr; + multiview_features.multiview = multiview_capabilities.is_supported; + multiview_features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported; + multiview_features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported; - multiview_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; - multiview_features.pNext = nullptr; - multiview_features.multiview = multiview_capabilities.is_supported; - multiview_features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported; - multiview_features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported; - - sdevice.pNext = &multiview_features; -#endif + sdevice.pNext = &multiview_features; + } err = vkCreateDevice(gpu, &sdevice, nullptr, &device); ERR_FAIL_COND_V(err, ERR_CANT_CREATE); 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/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 48f2be450b..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; @@ -1429,13 +1428,13 @@ void CodeTextEditor::toggle_inline_comment(const String &delimiter) { void CodeTextEditor::goto_line(int p_line) { text_editor->deselect(); text_editor->unfold_line(p_line); - text_editor->call_deferred(SNAME("cursor_set_line"), p_line); + text_editor->call_deferred(SNAME("set_caret_line"), p_line); } void CodeTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { text_editor->unfold_line(p_line); - text_editor->call_deferred(SNAME("cursor_set_line"), p_line); - text_editor->call_deferred(SNAME("cursor_set_column"), p_begin); + text_editor->call_deferred(SNAME("set_caret_line"), p_line); + text_editor->call_deferred(SNAME("set_caret_column"), p_begin); text_editor->select(p_line, p_begin, p_line, p_end); } @@ -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/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index 5d654ca756..a9cb1a0131 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -655,6 +655,17 @@ void EditorDebuggerNode::live_debug_reparent_node(const NodePath &p_at, const No }); } +void EditorDebuggerNode::set_camera_override(CameraOverride p_override) { + _for_all(tabs, [&](ScriptEditorDebugger *dbg) { + dbg->set_camera_override(p_override); + }); + camera_override = p_override; +} + +EditorDebuggerNode::CameraOverride EditorDebuggerNode::get_camera_override() { + return camera_override; +} + void EditorDebuggerNode::add_debugger_plugin(const Ref<Script> &p_script) { ERR_FAIL_COND_MSG(debugger_plugins.has(p_script), "Debugger plugin already exists."); ERR_FAIL_COND_MSG(p_script.is_null(), "Debugger plugin script is null"); diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h index 0849ecf1c9..39a95326be 100644 --- a/editor/debugger/editor_debugger_node.h +++ b/editor/debugger/editor_debugger_node.h @@ -185,9 +185,8 @@ public: void live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name); void live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos); - // Camera - void set_camera_override(CameraOverride p_override) { camera_override = p_override; } - CameraOverride get_camera_override() { return camera_override; } + void set_camera_override(CameraOverride p_override); + CameraOverride get_camera_override(); Error start(const String &p_protocol = "tcp://"); diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index 56a9d2c258..fee2deddda 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]; @@ -245,9 +252,6 @@ void DocTools::generate(bool p_basic_types) { } String cname = name; - if (cname.begins_with("_")) { //proxy class - cname = cname.substr(1, name.length()); - } class_list[cname] = DocData::ClassDoc(); DocData::ClassDoc &c = class_list[cname]; @@ -430,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); } @@ -740,9 +756,6 @@ void DocTools::generate(bool p_basic_types) { while (String(ClassDB::get_parent_class(pd.type)) != "Object") { pd.type = ClassDB::get_parent_class(pd.type); } - if (pd.type.begins_with("_")) { - pd.type = pd.type.substr(1, pd.type.length()); - } c.properties.push_back(pd); } @@ -873,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); @@ -1221,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 6349beeef4..25250e231e 100644 --- a/editor/editor_command_palette.cpp +++ b/editor/editor_command_palette.cpp @@ -70,8 +70,12 @@ void EditorCommandPalette::_update_command_search(const String &search_text) { r.key_name = command_keys[i]; r.display_name = commands[r.key_name].name; r.shortcut_text = commands[r.key_name].shortcut; + if (search_text.is_subsequence_ofi(r.display_name)) { - r.score = _score_path(search_text, r.display_name.to_lower()); + if (!search_text.is_empty()) { + r.score = _score_path(search_text, r.display_name.to_lower()); + } + entries.push_back(r); } } @@ -81,60 +85,55 @@ void EditorCommandPalette::_update_command_search(const String &search_text) { TreeItem *root = search_options->get_root(); root->clear_children(); - if (entries.size() > 0) { - if (!search_text.is_empty()) { - SortArray<CommandEntry, CommandEntryComparator> sorter; - sorter.sort(entries.ptrw(), entries.size()); - } + if (entries.is_empty()) { + get_ok_button()->set_disabled(true); - const int entry_limit = MIN(entries.size(), 300); - for (int i = 0; i < entry_limit; i++) { - String section_name = entries[i].key_name.get_slice("/", 0); - TreeItem *section; + return; + } - if (sections.has(section_name)) { - section = sections[section_name]; - } else { - section = search_options->create_item(root); + if (!search_text.is_empty()) { + SortArray<CommandEntry, CommandEntryComparator> sorter; + sorter.sort(entries.ptrw(), entries.size()); + } - if (!first_section) { - first_section = section; - } + const int entry_limit = MIN(entries.size(), 300); + for (int i = 0; i < entry_limit; i++) { + String section_name = entries[i].key_name.get_slice("/", 0); + TreeItem *section; - String item_name = section_name.capitalize(); - section->set_text(0, item_name); + if (sections.has(section_name)) { + section = sections[section_name]; + } else { + section = search_options->create_item(root); - sections[section_name] = section; - section->set_custom_bg_color(0, search_options->get_theme_color("prop_subsection", "Editor")); - section->set_custom_bg_color(1, search_options->get_theme_color("prop_subsection", "Editor")); + if (!first_section) { + first_section = section; } - TreeItem *ti = search_options->create_item(section); - String shortcut_text = entries[i].shortcut_text == "None" ? "" : entries[i].shortcut_text; - ti->set_text(0, entries[i].display_name); - ti->set_metadata(0, entries[i].key_name); - ti->set_text_align(1, TreeItem::TextAlign::ALIGN_RIGHT); - ti->set_text(1, shortcut_text); - Color c = Color(1, 1, 1, 0.5); - ti->set_custom_color(1, c); - } - - TreeItem *to_select = first_section->get_first_child(); - to_select->select(0); - to_select->set_as_cursor(0); - search_options->scroll_to_item(to_select); + String item_name = section_name.capitalize(); + section->set_text(0, item_name); + section->set_selectable(0, false); + section->set_selectable(1, false); + section->set_custom_bg_color(0, search_options->get_theme_color("prop_subsection", "Editor")); + section->set_custom_bg_color(1, search_options->get_theme_color("prop_subsection", "Editor")); - get_ok_button()->set_disabled(false); - } else { - TreeItem *ti = search_options->create_item(root); - ti->set_text(0, TTR("No matching commands found")); - ti->set_metadata(0, ""); - Color c = Color(0.5, 0.5, 0.5, 0.5); - ti->set_custom_color(0, c); - search_options->deselect_all(); + sections[section_name] = section; + } - get_ok_button()->set_disabled(true); + TreeItem *ti = search_options->create_item(section); + String shortcut_text = entries[i].shortcut_text == "None" ? "" : entries[i].shortcut_text; + ti->set_text(0, entries[i].display_name); + ti->set_metadata(0, entries[i].key_name); + ti->set_text_align(1, TreeItem::TextAlign::ALIGN_RIGHT); + ti->set_text(1, shortcut_text); + Color c = Color(1, 1, 1, 0.5); + ti->set_custom_color(1, c); } + + TreeItem *to_select = first_section->get_first_child(); + to_select->select(0); + to_select->set_as_cursor(0); + search_options->ensure_cursor_is_visible(); } void EditorCommandPalette::_bind_methods() { @@ -150,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; @@ -169,8 +168,11 @@ void EditorCommandPalette::_confirmed() { void EditorCommandPalette::open_popup() { popup_centered_clamped(Size2i(600, 440), 0.8f); + command_search_box->clear(); command_search_box->grab_focus(); + + search_options->scroll_to_item(search_options->get_root()); } void EditorCommandPalette::get_actions_list(List<String> *p_list) const { @@ -224,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(); @@ -236,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); @@ -258,6 +260,9 @@ EditorCommandPalette *EditorCommandPalette::get_singleton() { } EditorCommandPalette::EditorCommandPalette() { + set_hide_on_ok(false); + connect("confirmed", callable_mp(this, &EditorCommandPalette::_confirmed)); + VBoxContainer *vbc = memnew(VBoxContainer); vbc->connect("theme_changed", callable_mp(this, &EditorCommandPalette::_theme_changed)); add_child(vbc); @@ -275,18 +280,15 @@ EditorCommandPalette::EditorCommandPalette() { search_options = memnew(Tree); search_options->connect("item_activated", callable_mp(this, &EditorCommandPalette::_confirmed)); + search_options->connect("item_selected", callable_mp((BaseButton *)get_ok_button(), &BaseButton::set_disabled), varray(false)); + search_options->connect("nothing_selected", callable_mp((BaseButton *)get_ok_button(), &BaseButton::set_disabled), varray(true)); search_options->create_item(); search_options->set_hide_root(true); - search_options->set_hide_folding(true); - search_options->add_theme_constant_override("draw_guides", 1); search_options->set_columns(2); search_options->set_v_size_flags(Control::SIZE_EXPAND_FILL); search_options->set_h_size_flags(Control::SIZE_EXPAND_FILL); search_options->set_column_custom_minimum_width(0, int(8 * EDSCALE)); vbc->add_child(search_options, true); - - connect("confirmed", callable_mp(this, &EditorCommandPalette::_confirmed)); - set_hide_on_ok(false); } Ref<Shortcut> ED_SHORTCUT_AND_COMMAND(const String &p_path, const String &p_name, Key p_keycode, String p_command_name) { 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_export.cpp b/editor/editor_export.cpp index b374f56f6d..1240496028 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" @@ -624,21 +625,15 @@ Vector<String> EditorExportPlugin::get_ios_project_static_libs() const { } void EditorExportPlugin::_export_file_script(const String &p_path, const String &p_type, const Vector<String> &p_features) { - if (get_script_instance()) { - get_script_instance()->call("_export_file", p_path, p_type, p_features); - } + GDVIRTUAL_CALL(_export_file, p_path, p_type, p_features); } void EditorExportPlugin::_export_begin_script(const Vector<String> &p_features, bool p_debug, const String &p_path, int p_flags) { - if (get_script_instance()) { - get_script_instance()->call("_export_begin", p_features, p_debug, p_path, p_flags); - } + GDVIRTUAL_CALL(_export_begin, p_features, p_debug, p_path, p_flags); } void EditorExportPlugin::_export_end_script() { - if (get_script_instance()) { - get_script_instance()->call("_export_end"); - } + GDVIRTUAL_CALL(_export_end); } void EditorExportPlugin::_export_file(const String &p_path, const String &p_type, const Set<String> &p_features) { @@ -663,9 +658,9 @@ void EditorExportPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_ios_cpp_code", "code"), &EditorExportPlugin::add_ios_cpp_code); ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip); - BIND_VMETHOD(MethodInfo("_export_file", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "type"), PropertyInfo(Variant::PACKED_STRING_ARRAY, "features"))); - BIND_VMETHOD(MethodInfo("_export_begin", PropertyInfo(Variant::PACKED_STRING_ARRAY, "features"), PropertyInfo(Variant::BOOL, "is_debug"), PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "flags"))); - BIND_VMETHOD(MethodInfo("_export_end")); + GDVIRTUAL_BIND(_export_file, "path", "type", "features"); + GDVIRTUAL_BIND(_export_begin, "features", "is_debug", "path", "flags"); + GDVIRTUAL_BIND(_export_end); } EditorExportPlugin::EditorExportPlugin() { @@ -1056,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"); diff --git a/editor/editor_export.h b/editor/editor_export.h index c9401df9c2..b681f52330 100644 --- a/editor/editor_export.h +++ b/editor/editor_export.h @@ -349,6 +349,10 @@ protected: static void _bind_methods(); + GDVIRTUAL3(_export_file, String, String, Vector<String>) + GDVIRTUAL4(_export_begin, Vector<String>, bool, String, uint32_t) + GDVIRTUAL0(_export_end) + public: Vector<String> get_ios_frameworks() const; Vector<String> get_ios_embedded_frameworks() const; diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp index 5ccbed1b49..1e9d579708 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) { @@ -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..aa89a14725 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" @@ -605,6 +606,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 +2235,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..24b6ba1a14 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 { @@ -1825,8 +1851,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 +1926,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 9de8b0451a..fee27dae58 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" @@ -379,9 +381,7 @@ StringName EditorProperty::get_edited_property() { } void EditorProperty::update_property() { - if (get_script_instance()) { - get_script_instance()->call("_update_property"); - } + GDVIRTUAL_CALL(_update_property); } void EditorProperty::set_read_only(bool p_read_only) { @@ -694,7 +694,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()) { @@ -766,7 +766,7 @@ void EditorProperty::_gui_input(const Ref<InputEvent> &p_event) { call_deferred(SNAME("emit_changed"), property, object->get(property).operator int64_t() + 1, "", false); } - call_deferred(SNAME("_update_property")); + call_deferred(SNAME("update_property")); } } if (delete_rect.has_point(mpos)) { @@ -784,6 +784,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(); } } @@ -897,6 +921,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); @@ -922,9 +960,8 @@ 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); ClassDB::bind_method(D_METHOD("add_focusable", "control"), &EditorProperty::add_focusable); ClassDB::bind_method(D_METHOD("set_bottom_editor", "editor"), &EditorProperty::set_bottom_editor); @@ -948,7 +985,7 @@ void EditorProperty::_bind_methods() { ADD_SIGNAL(MethodInfo("object_id_selected", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "focusable_idx"))); - BIND_VMETHOD(MethodInfo("_update_property")); + GDVIRTUAL_BIND(_update_property) } EditorProperty::EditorProperty() { @@ -974,6 +1011,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); } //////////////////////////////////////////////// @@ -1003,43 +1055,31 @@ void EditorInspectorPlugin::add_property_editor_for_multiple_properties(const St } bool EditorInspectorPlugin::can_handle(Object *p_object) { - if (get_script_instance()) { - return get_script_instance()->call("_can_handle", p_object); + bool success; + if (GDVIRTUAL_CALL(_can_handle, p_object, success)) { + return success; } return false; } void EditorInspectorPlugin::parse_begin(Object *p_object) { - if (get_script_instance()) { - get_script_instance()->call("_parse_begin", p_object); - } + GDVIRTUAL_CALL(_parse_begin); } void EditorInspectorPlugin::parse_category(Object *p_object, const String &p_parse_category) { - if (get_script_instance()) { - get_script_instance()->call("_parse_category", p_object, p_parse_category); - } + GDVIRTUAL_CALL(_parse_category, p_object, p_parse_category); } bool EditorInspectorPlugin::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 (get_script_instance()) { - Variant arg[6] = { - p_object, p_type, p_path, p_hint, p_hint_text, p_usage - }; - const Variant *argptr[6] = { - &arg[0], &arg[1], &arg[2], &arg[3], &arg[4], &arg[5] - }; - - Callable::CallError err; - return get_script_instance()->call("_parse_property", (const Variant **)&argptr, 6, err); + bool ret; + if (GDVIRTUAL_CALL(_parse_property, p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide, ret)) { + return ret; } return false; } void EditorInspectorPlugin::parse_end() { - if (get_script_instance()) { - get_script_instance()->call("_parse_end"); - } + GDVIRTUAL_CALL(_parse_end); } void EditorInspectorPlugin::_bind_methods() { @@ -1047,11 +1087,11 @@ void EditorInspectorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_property_editor", "property", "editor"), &EditorInspectorPlugin::add_property_editor); ClassDB::bind_method(D_METHOD("add_property_editor_for_multiple_properties", "label", "properties", "editor"), &EditorInspectorPlugin::add_property_editor_for_multiple_properties); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_handle", PropertyInfo(Variant::OBJECT, "object"))); - BIND_VMETHOD(MethodInfo(Variant::NIL, "_parse_begin")); - BIND_VMETHOD(MethodInfo(Variant::NIL, "_parse_category", PropertyInfo(Variant::STRING, "category"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_parse_property", PropertyInfo(Variant::INT, "type"), PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "hint"), PropertyInfo(Variant::STRING, "hint_text"), PropertyInfo(Variant::INT, "usage"))); - BIND_VMETHOD(MethodInfo(Variant::NIL, "_parse_end")); + GDVIRTUAL_BIND(_can_handle, "object") + GDVIRTUAL_BIND(_parse_begin) + GDVIRTUAL_BIND(_parse_category, "object", "category") + GDVIRTUAL_BIND(_parse_property, "object", "type", "name", "hint_type", "hint_string", "usage_flags", "wide"); + GDVIRTUAL_BIND(_parse_end) } //////////////////////////////////////////////// @@ -1332,7 +1372,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) { @@ -1391,7 +1431,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() { @@ -2243,6 +2282,13 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo undo_redo->add_do_property(object, p_name, p_value); undo_redo->add_undo_property(object, p_name, object->get(p_name)); + PropertyInfo prop_info; + if (ClassDB::get_property_info(object->get_class_name(), p_name, &prop_info)) { + for (const String &linked_prop : prop_info.linked_properties) { + undo_redo->add_undo_property(object, linked_prop, object->get(linked_prop)); + } + } + Variant v_undo_redo = (Object *)undo_redo; Variant v_object = object; Variant v_name = p_name; @@ -2610,13 +2656,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) { @@ -2624,6 +2672,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); @@ -2666,6 +2722,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; @@ -2675,4 +2732,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 3c9ba9f39d..8c522f00ef 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -51,6 +51,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 +91,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,16 +105,19 @@ private: Vector<Control *> focusables; Control *label_reference; Control *bottom_editor; + PopupMenu *menu; mutable String tooltip_text; Map<StringName, Variant> cache; + GDVIRTUAL0(_update_property) protected: 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; + 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); @@ -174,6 +185,8 @@ public: bool can_revert_to_default() const { return can_revert; } + void menu_option(int p_option); + EditorProperty(); }; @@ -192,6 +205,12 @@ class EditorInspectorPlugin : public RefCounted { protected: static void _bind_methods(); + GDVIRTUAL1RC(bool, _can_handle, Variant) + GDVIRTUAL0(_parse_begin) + GDVIRTUAL2(_parse_category, Object *, String) + GDVIRTUAL7R(bool, _parse_property, Object *, int, String, int, String, int, bool) + GDVIRTUAL0(_parse_end) + public: void add_custom_control(Control *control); void add_property_editor(const String &p_for_property, Control *p_prop); @@ -245,7 +264,7 @@ class EditorInspectorSection : public Container { protected: 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; @@ -314,6 +333,7 @@ class EditorInspector : public ScrollContainer { String property_prefix; //used for sectioned inspector String object_class; + Variant property_clipboard; bool restrict_to_basic = false; @@ -405,6 +425,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 4cd2e8bdd0..b1c546a8c0 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" @@ -388,20 +392,22 @@ void EditorNode::_version_control_menu_option(int p_idx) { } void EditorNode::_update_title() { - String appname = ProjectSettings::get_singleton()->get("application/config/name"); - String title = appname.is_empty() ? String(VERSION_FULL_NAME) : String(VERSION_NAME + String(" - ") + appname); - String edited = editor_data.get_edited_scene_root() ? editor_data.get_edited_scene_root()->get_filename() : String(); + const String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String title = (appname.is_empty() ? "Unnamed Project" : appname) + String(" - ") + VERSION_NAME; + const String edited = editor_data.get_edited_scene_root() ? editor_data.get_edited_scene_root()->get_filename() : String(); if (!edited.is_empty()) { - title += " - " + String(edited.get_file()); + // Display the edited scene name before the program name so that it can be seen in the OS task bar. + title = vformat("%s - %s", edited.get_file(), title); } if (unsaved_cache) { - title += " (*)"; + // Display the "modified" mark before anything else so that it can always be seen in the OS task bar. + title = vformat("(*) %s", 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; @@ -2592,26 +2598,26 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case EDIT_UNDO: { if (Input::get_singleton()->get_mouse_button_mask() & 0x7) { - log->add_message("Can't undo while mouse buttons are pressed.", EditorLog::MSG_TYPE_EDITOR); + log->add_message(TTR("Can't undo while mouse buttons are pressed."), EditorLog::MSG_TYPE_EDITOR); } else { String action = editor_data.get_undo_redo().get_current_action_name(); if (!editor_data.get_undo_redo().undo()) { - log->add_message("Nothing to undo.", EditorLog::MSG_TYPE_EDITOR); + log->add_message(TTR("Nothing to undo."), EditorLog::MSG_TYPE_EDITOR); } else if (action != "") { - log->add_message("Undo: " + action, EditorLog::MSG_TYPE_EDITOR); + log->add_message(vformat(TTR("Undo: %s"), action), EditorLog::MSG_TYPE_EDITOR); } } } break; case EDIT_REDO: { if (Input::get_singleton()->get_mouse_button_mask() & 0x7) { - log->add_message("Can't redo while mouse buttons are pressed.", EditorLog::MSG_TYPE_EDITOR); + log->add_message(TTR("Can't redo while mouse buttons are pressed."), EditorLog::MSG_TYPE_EDITOR); } else { if (!editor_data.get_undo_redo().redo()) { - log->add_message("Nothing to redo.", EditorLog::MSG_TYPE_EDITOR); + log->add_message(TTR("Nothing to redo."), EditorLog::MSG_TYPE_EDITOR); } else { String action = editor_data.get_undo_redo().get_current_action_name(); - log->add_message("Redo: " + action, EditorLog::MSG_TYPE_EDITOR); + log->add_message(vformat(TTR("Redo: %s"), action), EditorLog::MSG_TYPE_EDITOR); } } } break; @@ -3014,8 +3020,13 @@ void EditorNode::_update_file_menu_opened() { close_scene_sc->set_name(TTR("Close Scene")); Ref<Shortcut> reopen_closed_scene_sc = ED_GET_SHORTCUT("editor/reopen_closed_scene"); reopen_closed_scene_sc->set_name(TTR("Reopen Closed Scene")); + PopupMenu *pop = file_menu->get_popup(); pop->set_item_disabled(pop->get_item_index(FILE_OPEN_PREV), previous_scenes.is_empty()); + + const UndoRedo &undo_redo = editor_data.get_undo_redo(); + pop->set_item_disabled(pop->get_item_index(EDIT_UNDO), !undo_redo.has_undo()); + pop->set_item_disabled(pop->get_item_index(EDIT_REDO), !undo_redo.has_redo()); } void EditorNode::_update_file_menu_closed() { @@ -4793,6 +4804,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(); } @@ -5581,7 +5618,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)); @@ -5821,6 +5857,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); @@ -6226,6 +6274,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); @@ -6787,7 +6838,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..51d01d07ef 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); @@ -421,6 +423,7 @@ private: EditorResourcePreview *resource_preview; EditorFolding editor_folding; + DynamicFontImportSettings *fontdata_import_settings; SceneImportSettings *scene_import_settings; struct BottomPanelItem { String name; @@ -530,7 +533,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 +678,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); @@ -898,6 +904,8 @@ public: 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_plugin.cpp b/editor/editor_plugin.cpp index 98b5ec7d56..73ea4fb5ef 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -558,22 +558,19 @@ void EditorPlugin::notify_resource_saved(const Ref<Resource> &p_resource) { } bool EditorPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { - if (get_script_instance() && get_script_instance()->has_method("_forward_canvas_gui_input")) { - return get_script_instance()->call("_forward_canvas_gui_input", p_event); + bool success; + if (GDVIRTUAL_CALL(_forward_canvas_gui_input, p_event, success)) { + return success; } return false; } void EditorPlugin::forward_canvas_draw_over_viewport(Control *p_overlay) { - if (get_script_instance() && get_script_instance()->has_method("_forward_canvas_draw_over_viewport")) { - get_script_instance()->call("_forward_canvas_draw_over_viewport", p_overlay); - } + GDVIRTUAL_CALL(_forward_canvas_draw_over_viewport, p_overlay); } void EditorPlugin::forward_canvas_force_draw_over_viewport(Control *p_overlay) { - if (get_script_instance() && get_script_instance()->has_method("_forward_canvas_force_draw_over_viewport")) { - get_script_instance()->call("_forward_canvas_force_draw_over_viewport", p_overlay); - } + GDVIRTUAL_CALL(_forward_canvas_force_draw_over_viewport, p_overlay); } // Updates the overlays of the 2D viewport or, if in 3D mode, of every 3D viewport. @@ -596,110 +593,101 @@ int EditorPlugin::update_overlays() const { } bool EditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { - if (get_script_instance() && get_script_instance()->has_method("_forward_spatial_gui_input")) { - return get_script_instance()->call("_forward_spatial_gui_input", p_camera, p_event); + bool success; + + if (GDVIRTUAL_CALL(_forward_3d_gui_input, p_camera, p_event, success)) { + return success; } return false; } void EditorPlugin::forward_spatial_draw_over_viewport(Control *p_overlay) { - if (get_script_instance() && get_script_instance()->has_method("_forward_spatial_draw_over_viewport")) { - get_script_instance()->call("_forward_spatial_draw_over_viewport", p_overlay); - } + GDVIRTUAL_CALL(_forward_3d_draw_over_viewport, p_overlay); } void EditorPlugin::forward_spatial_force_draw_over_viewport(Control *p_overlay) { - if (get_script_instance() && get_script_instance()->has_method("_forward_spatial_force_draw_over_viewport")) { - get_script_instance()->call("_forward_spatial_force_draw_over_viewport", p_overlay); - } + GDVIRTUAL_CALL(_forward_3d_force_draw_over_viewport, p_overlay); } String EditorPlugin::get_name() const { - if (get_script_instance() && get_script_instance()->has_method("_get_plugin_name")) { - return get_script_instance()->call("_get_plugin_name"); + String name; + if (GDVIRTUAL_CALL(_get_plugin_name, name)) { + return name; } return String(); } const Ref<Texture2D> EditorPlugin::get_icon() const { - if (get_script_instance() && get_script_instance()->has_method("_get_plugin_icon")) { - return get_script_instance()->call("_get_plugin_icon"); + Ref<Texture2D> icon; + if (GDVIRTUAL_CALL(_get_plugin_icon, icon)) { + return icon; } return Ref<Texture2D>(); } bool EditorPlugin::has_main_screen() const { - if (get_script_instance() && get_script_instance()->has_method("_has_main_screen")) { - return get_script_instance()->call("_has_main_screen"); + bool success; + if (GDVIRTUAL_CALL(_has_main_screen, success)) { + return success; } return false; } void EditorPlugin::make_visible(bool p_visible) { - if (get_script_instance() && get_script_instance()->has_method("_make_visible")) { - get_script_instance()->call("_make_visible", p_visible); - } + GDVIRTUAL_CALL(_make_visible, p_visible); } void EditorPlugin::edit(Object *p_object) { - if (get_script_instance() && get_script_instance()->has_method("_edit")) { - if (p_object->is_class("Resource")) { - get_script_instance()->call("_edit", Ref<Resource>(Object::cast_to<Resource>(p_object))); - } else { - get_script_instance()->call("_edit", p_object); - } + if (p_object->is_class("Resource")) { + GDVIRTUAL_CALL(_edit, Ref<Resource>(Object::cast_to<Resource>(p_object))); + } else { + GDVIRTUAL_CALL(_edit, p_object); } } bool EditorPlugin::handles(Object *p_object) const { - if (get_script_instance() && get_script_instance()->has_method("_handles")) { - return get_script_instance()->call("_handles", p_object); + bool success; + if (GDVIRTUAL_CALL(_handles, p_object, success)) { + return success; } return false; } Dictionary EditorPlugin::get_state() const { - if (get_script_instance() && get_script_instance()->has_method("_get_state")) { - return get_script_instance()->call("_get_state"); + Dictionary state; + if (GDVIRTUAL_CALL(_get_state, state)) { + return state; } return Dictionary(); } void EditorPlugin::set_state(const Dictionary &p_state) { - if (get_script_instance() && get_script_instance()->has_method("_set_state")) { - get_script_instance()->call("_set_state", p_state); - } + GDVIRTUAL_CALL(_set_state, p_state); } void EditorPlugin::clear() { - if (get_script_instance() && get_script_instance()->has_method("_clear")) { - get_script_instance()->call("_clear"); - } + GDVIRTUAL_CALL(_clear); } // if editor references external resources/scenes, save them void EditorPlugin::save_external_data() { - if (get_script_instance() && get_script_instance()->has_method("_save_external_data")) { - get_script_instance()->call("_save_external_data"); - } + GDVIRTUAL_CALL(_save_external_data); } // if changes are pending in editor, apply them void EditorPlugin::apply_changes() { - if (get_script_instance() && get_script_instance()->has_method("_apply_changes")) { - get_script_instance()->call("_apply_changes"); - } + GDVIRTUAL_CALL(_apply_changes); } void EditorPlugin::get_breakpoints(List<String> *p_breakpoints) { - if (get_script_instance() && get_script_instance()->has_method("_get_breakpoints")) { - PackedStringArray arr = get_script_instance()->call("_get_breakpoints"); + PackedStringArray arr; + if (GDVIRTUAL_CALL(_get_breakpoints, arr)) { for (int i = 0; i < arr.size(); i++) { p_breakpoints->push_back(arr[i]); } @@ -796,37 +784,28 @@ int find(const PackedStringArray &a, const String &v) { void EditorPlugin::enable_plugin() { // Called when the plugin gets enabled in project settings, after it's added to the tree. // You can implement it to register autoloads. - if (get_script_instance() && get_script_instance()->has_method("_enable_plugin")) { - get_script_instance()->call("_enable_plugin"); - } + GDVIRTUAL_CALL(_enable_plugin); } void EditorPlugin::disable_plugin() { // Last function called when the plugin gets disabled in project settings. // Implement it to cleanup things from the project, such as unregister autoloads. - - if (get_script_instance() && get_script_instance()->has_method("_disable_plugin")) { - get_script_instance()->call("_disable_plugin"); - } + GDVIRTUAL_CALL(_disable_plugin); } void EditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) { - if (get_script_instance() && get_script_instance()->has_method("_set_window_layout")) { - get_script_instance()->call("_set_window_layout", p_layout); - } + GDVIRTUAL_CALL(_set_window_layout, p_layout); } void EditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) { - if (get_script_instance() && get_script_instance()->has_method("_get_window_layout")) { - get_script_instance()->call("_get_window_layout", p_layout); - } + GDVIRTUAL_CALL(_get_window_layout, p_layout); } bool EditorPlugin::build() { - if (get_script_instance() && get_script_instance()->has_method("_build")) { - return get_script_instance()->call("_build"); + bool success; + if (GDVIRTUAL_CALL(_build, success)) { + return success; } - return true; } @@ -915,29 +894,29 @@ void EditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_debugger_plugin", "script"), &EditorPlugin::add_debugger_plugin); ClassDB::bind_method(D_METHOD("remove_debugger_plugin", "script"), &EditorPlugin::remove_debugger_plugin); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_forward_canvas_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); - BIND_VMETHOD(MethodInfo("_forward_canvas_draw_over_viewport", PropertyInfo(Variant::OBJECT, "overlay", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); - BIND_VMETHOD(MethodInfo("_forward_canvas_force_draw_over_viewport", PropertyInfo(Variant::OBJECT, "overlay", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_forward_spatial_gui_input", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); - BIND_VMETHOD(MethodInfo("_forward_spatial_draw_over_viewport", PropertyInfo(Variant::OBJECT, "overlay", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); - BIND_VMETHOD(MethodInfo("_forward_spatial_force_draw_over_viewport", PropertyInfo(Variant::OBJECT, "overlay", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_plugin_name")); - BIND_VMETHOD(MethodInfo(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "_get_plugin_icon")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_main_screen")); - BIND_VMETHOD(MethodInfo("_make_visible", PropertyInfo(Variant::BOOL, "visible"))); - BIND_VMETHOD(MethodInfo("_edit", PropertyInfo(Variant::OBJECT, "object"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_handles", PropertyInfo(Variant::OBJECT, "object"))); - BIND_VMETHOD(MethodInfo(Variant::DICTIONARY, "_get_state")); - BIND_VMETHOD(MethodInfo("_set_state", PropertyInfo(Variant::DICTIONARY, "state"))); - BIND_VMETHOD(MethodInfo("_clear")); - BIND_VMETHOD(MethodInfo("_save_external_data")); - BIND_VMETHOD(MethodInfo("_apply_changes")); - BIND_VMETHOD(MethodInfo(Variant::PACKED_STRING_ARRAY, "_get_breakpoints")); - BIND_VMETHOD(MethodInfo("_set_window_layout", PropertyInfo(Variant::OBJECT, "layout", PROPERTY_HINT_RESOURCE_TYPE, "ConfigFile"))); - BIND_VMETHOD(MethodInfo("_get_window_layout", PropertyInfo(Variant::OBJECT, "layout", PROPERTY_HINT_RESOURCE_TYPE, "ConfigFile"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_build")); - BIND_VMETHOD(MethodInfo("_enable_plugin")); - BIND_VMETHOD(MethodInfo("_disable_plugin")); + GDVIRTUAL_BIND(_forward_canvas_gui_input, "event"); + GDVIRTUAL_BIND(_forward_canvas_draw_over_viewport, "viewport_control"); + GDVIRTUAL_BIND(_forward_canvas_force_draw_over_viewport, "viewport_control"); + GDVIRTUAL_BIND(_forward_3d_gui_input, "viewport_camera", "event"); + GDVIRTUAL_BIND(_forward_3d_draw_over_viewport, "viewport_control"); + GDVIRTUAL_BIND(_forward_3d_force_draw_over_viewport, "viewport_control"); + GDVIRTUAL_BIND(_get_plugin_name); + GDVIRTUAL_BIND(_get_plugin_icon); + GDVIRTUAL_BIND(_has_main_screen); + GDVIRTUAL_BIND(_make_visible, "visible"); + GDVIRTUAL_BIND(_edit, "object"); + GDVIRTUAL_BIND(_handles, "object"); + GDVIRTUAL_BIND(_get_state); + GDVIRTUAL_BIND(_set_state, "state"); + GDVIRTUAL_BIND(_clear); + GDVIRTUAL_BIND(_save_external_data); + GDVIRTUAL_BIND(_apply_changes); + GDVIRTUAL_BIND(_get_breakpoints); + GDVIRTUAL_BIND(_set_window_layout, "configuration"); + GDVIRTUAL_BIND(_get_window_layout, "configuration"); + GDVIRTUAL_BIND(_build); + GDVIRTUAL_BIND(_enable_plugin); + GDVIRTUAL_BIND(_disable_plugin); ADD_SIGNAL(MethodInfo("scene_changed", PropertyInfo(Variant::OBJECT, "scene_root", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("scene_closed", PropertyInfo(Variant::STRING, "filepath"))); diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index d665278144..169106d901 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -39,9 +39,9 @@ #include "editor/import/editor_import_plugin.h" #include "editor/import/resource_importer_scene.h" #include "editor/script_create_dialog.h" +#include "scene/3d/camera_3d.h" #include "scene/main/node.h" #include "scene/resources/texture.h" - class EditorNode; class Node3D; class Camera3D; @@ -148,6 +148,30 @@ protected: void add_custom_type(const String &p_type, const String &p_base, const Ref<Script> &p_script, const Ref<Texture2D> &p_icon); void remove_custom_type(const String &p_type); + GDVIRTUAL1R(bool, _forward_canvas_gui_input, Ref<InputEvent>) + GDVIRTUAL1(_forward_canvas_draw_over_viewport, Control *) + GDVIRTUAL1(_forward_canvas_force_draw_over_viewport, Control *) + GDVIRTUAL2R(bool, _forward_3d_gui_input, Camera3D *, Ref<InputEvent>) + GDVIRTUAL1(_forward_3d_draw_over_viewport, Control *) + GDVIRTUAL1(_forward_3d_force_draw_over_viewport, Control *) + GDVIRTUAL0RC(String, _get_plugin_name) + GDVIRTUAL0RC(Ref<Texture2D>, _get_plugin_icon) + GDVIRTUAL0RC(bool, _has_main_screen) + GDVIRTUAL1(_make_visible, bool) + GDVIRTUAL1(_edit, Variant) + GDVIRTUAL1RC(bool, _handles, Variant) + GDVIRTUAL0RC(Dictionary, _get_state) + GDVIRTUAL1(_set_state, Dictionary) + GDVIRTUAL0(_clear) + GDVIRTUAL0(_save_external_data) + GDVIRTUAL0(_apply_changes) + GDVIRTUAL0RC(Vector<String>, _get_breakpoints) + GDVIRTUAL1(_set_window_layout, Ref<ConfigFile>) + GDVIRTUAL1(_get_window_layout, Ref<ConfigFile>) + GDVIRTUAL0R(bool, _build) + GDVIRTUAL0(_enable_plugin) + GDVIRTUAL0(_disable_plugin) + public: enum CustomControlContainer { CONTAINER_TOOLBAR, diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 99619cfc40..cb7fffe592 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -735,7 +735,7 @@ public: return String(); } - void _gui_input(const Ref<InputEvent> &p_ev) { + void gui_input(const Ref<InputEvent> &p_ev) override { const Ref<InputEventMouseMotion> mm = p_ev; if (mm.is_valid()) { bool expand_was_hovered = expand_hovered; @@ -931,7 +931,6 @@ public: } static void _bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &EditorPropertyLayersGrid::_gui_input); ADD_SIGNAL(MethodInfo("flag_changed", PropertyInfo(Variant::INT, "flag"))); } }; diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index 7f4ee7848f..8fc1345f3e 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -40,22 +40,25 @@ #include "editor_settings.h" bool EditorResourcePreviewGenerator::handles(const String &p_type) const { - if (get_script_instance() && get_script_instance()->has_method("_handles")) { - return get_script_instance()->call("_handles", p_type); + bool success; + if (GDVIRTUAL_CALL(_handles, p_type, success)) { + return success; } ERR_FAIL_V_MSG(false, "EditorResourcePreviewGenerator::_handles needs to be overridden."); } Ref<Texture2D> EditorResourcePreviewGenerator::generate(const RES &p_from, const Size2 &p_size) const { - if (get_script_instance() && get_script_instance()->has_method("_generate")) { - return get_script_instance()->call("_generate", p_from, p_size); + Ref<Texture2D> preview; + if (GDVIRTUAL_CALL(_generate, p_from, p_size, preview)) { + return preview; } ERR_FAIL_V_MSG(Ref<Texture2D>(), "EditorResourcePreviewGenerator::_generate needs to be overridden."); } Ref<Texture2D> EditorResourcePreviewGenerator::generate_from_path(const String &p_path, const Size2 &p_size) const { - if (get_script_instance() && get_script_instance()->has_method("_generate_from_path")) { - return get_script_instance()->call("_generate_from_path", p_path, p_size); + Ref<Texture2D> preview; + if (GDVIRTUAL_CALL(_generate_from_path, p_path, p_size, preview)) { + return preview; } RES res = ResourceLoader::load(p_path); @@ -66,27 +69,29 @@ Ref<Texture2D> EditorResourcePreviewGenerator::generate_from_path(const String & } bool EditorResourcePreviewGenerator::generate_small_preview_automatically() const { - if (get_script_instance() && get_script_instance()->has_method("_generate_small_preview_automatically")) { - return get_script_instance()->call("_generate_small_preview_automatically"); + bool success; + if (GDVIRTUAL_CALL(_generate_small_preview_automatically, success)) { + return success; } return false; } bool EditorResourcePreviewGenerator::can_generate_small_preview() const { - if (get_script_instance() && get_script_instance()->has_method("_can_generate_small_preview")) { - return get_script_instance()->call("_can_generate_small_preview"); + bool success; + if (GDVIRTUAL_CALL(_can_generate_small_preview, success)) { + return success; } return false; } void EditorResourcePreviewGenerator::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_handles", PropertyInfo(Variant::STRING, "type"))); - BIND_VMETHOD(MethodInfo(CLASS_INFO(Texture2D), "_generate", PropertyInfo(Variant::OBJECT, "from", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), PropertyInfo(Variant::VECTOR2, "size"))); - BIND_VMETHOD(MethodInfo(CLASS_INFO(Texture2D), "_generate_from_path", PropertyInfo(Variant::STRING, "path", PROPERTY_HINT_FILE), PropertyInfo(Variant::VECTOR2, "size"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_generate_small_preview_automatically")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_generate_small_preview")); + GDVIRTUAL_BIND(_handles, "type"); + GDVIRTUAL_BIND(_generate, "resource", "size"); + GDVIRTUAL_BIND(_generate_from_path, "path", "size"); + GDVIRTUAL_BIND(_generate_small_preview_automatically); + GDVIRTUAL_BIND(_can_generate_small_preview); } EditorResourcePreviewGenerator::EditorResourcePreviewGenerator() { diff --git a/editor/editor_resource_preview.h b/editor/editor_resource_preview.h index 67f83220d0..ea16c8fde0 100644 --- a/editor/editor_resource_preview.h +++ b/editor/editor_resource_preview.h @@ -43,6 +43,12 @@ class EditorResourcePreviewGenerator : public RefCounted { protected: static void _bind_methods(); + GDVIRTUAL1RC(bool, _handles, String) + GDVIRTUAL2RC(Ref<Texture2D>, _generate, RES, Vector2i) + GDVIRTUAL2RC(Ref<Texture2D>, _generate_from_path, String, Vector2i) + GDVIRTUAL0RC(bool, _generate_small_preview_automatically) + GDVIRTUAL0RC(bool, _can_generate_small_preview) + public: virtual bool handles(const String &p_type) const; virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const; diff --git a/editor/editor_run_script.cpp b/editor/editor_run_script.cpp index 83ce50a9f9..27923ef413 100644 --- a/editor/editor_run_script.cpp +++ b/editor/editor_run_script.cpp @@ -60,18 +60,8 @@ Node *EditorScript::get_scene() { } void EditorScript::_run() { - Ref<Script> s = get_script(); - ERR_FAIL_COND(!s.is_valid()); - if (!get_script_instance()) { - EditorNode::add_io_error(TTR("Couldn't instance script:") + "\n " + s->get_path() + "\n" + TTR("Did you forget the 'tool' keyword?")); - return; - } - - Callable::CallError ce; - ce.error = Callable::CallError::CALL_OK; - get_script_instance()->call("_run", nullptr, 0, ce); - if (ce.error != Callable::CallError::CALL_OK) { - EditorNode::add_io_error(TTR("Couldn't run script:") + "\n " + s->get_path() + "\n" + TTR("Did you forget the '_run' method?")); + if (!GDVIRTUAL_CALL(_run)) { + EditorNode::add_io_error(TTR("Couldn't run editor script, did you forget to override the '_run' method?")); } } @@ -83,7 +73,7 @@ void EditorScript::_bind_methods() { ClassDB::bind_method(D_METHOD("add_root_node", "node"), &EditorScript::add_root_node); ClassDB::bind_method(D_METHOD("get_scene"), &EditorScript::get_scene); ClassDB::bind_method(D_METHOD("get_editor_interface"), &EditorScript::get_editor_interface); - BIND_VMETHOD(MethodInfo("_run")); + GDVIRTUAL_BIND(_run); } EditorScript::EditorScript() { diff --git a/editor/editor_run_script.h b/editor/editor_run_script.h index c8412c3c92..6c7e37774d 100644 --- a/editor/editor_run_script.h +++ b/editor/editor_run_script.h @@ -41,6 +41,7 @@ class EditorScript : public RefCounted { protected: static void _bind_methods(); + GDVIRTUAL0(_run) public: void add_root_node(Node *p_node); 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 a802afda0f..3f65b101f7 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) { @@ -195,11 +195,11 @@ void EditorSpinSlider::_update_value_input_stylebox() { if (!value_input) { return; } + // Add a left margin to the stylebox to make the number align with the Label // when it's edited. The LineEdit "focus" stylebox uses the "normal" stylebox's // default margins. - Ref<StyleBoxFlat> stylebox = - EditorNode::get_singleton()->get_theme_base()->get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))->duplicate(); + Ref<StyleBox> stylebox = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))->duplicate(); // EditorSpinSliders with a label have more space on the left, so add an // higher margin to match the location where the text begins. // The margin values below were determined by empirical testing. @@ -210,188 +210,197 @@ void EditorSpinSlider::_update_value_input_stylebox() { stylebox->set_default_margin(SIDE_LEFT, (get_label() != String() ? 23 : 16) * EDSCALE); stylebox->set_default_margin(SIDE_RIGHT, 0); } + value_input->add_theme_style_override("normal", stylebox); } -void EditorSpinSlider::_notification(int p_what) { - if (p_what == NOTIFICATION_WM_WINDOW_FOCUS_OUT || - p_what == NOTIFICATION_WM_WINDOW_FOCUS_IN || - p_what == NOTIFICATION_EXIT_TREE) { - if (grabbing_spinner) { - grabber->hide(); - Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); - grabbing_spinner = false; - grabbing_spinner_attempt = false; - } - } - if (p_what == NOTIFICATION_READY || p_what == NOTIFICATION_THEME_CHANGED) { - _update_value_input_stylebox(); - } +void EditorSpinSlider::_draw_spin_slider() { + updown_offset = -1; - if (p_what == NOTIFICATION_DRAW) { - updown_offset = -1; + RID ci = get_canvas_item(); + bool rtl = is_layout_rtl(); + Vector2 size = get_size(); - RID ci = get_canvas_item(); - bool rtl = is_layout_rtl(); - Vector2 size = get_size(); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); + if (!flat) { + draw_style_box(sb, Rect2(Vector2(), size)); + } + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("LineEdit")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("LineEdit")); + int sep_base = 4 * EDSCALE; + int sep = sep_base + sb->get_offset().x; //make it have the same margin on both sides, looks better - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); - if (!flat) { - draw_style_box(sb, Rect2(Vector2(), size)); - } - Ref<Font> font = get_theme_font(SNAME("font"), SNAME("LineEdit")); - int font_size = get_theme_font_size(SNAME("font_size"), SNAME("LineEdit")); - int sep_base = 4 * EDSCALE; - int sep = sep_base + sb->get_offset().x; //make it have the same margin on both sides, looks better + int label_width = font->get_string_size(label, font_size).width; + int number_width = size.width - sb->get_minimum_size().width - label_width - sep; - 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(SNAME("updown"), SNAME("SpinBox")); + if (get_step() == 1) { + number_width -= updown->get_width(); + } - if (get_step() == 1) { - number_width -= updown->get_width(); - } + String numstr = get_text_value(); - String numstr = get_text_value(); + int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size); - 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 lc; + if (use_custom_label_color) { + lc = custom_label_color; + } else { + lc = fc; + } - Color fc = get_theme_color(SNAME("font_color"), SNAME("LineEdit")); - Color lc; - if (use_custom_label_color) { - lc = custom_label_color; + if (flat && label != String()) { + Color label_bg_color = get_theme_color(SNAME("dark_color_3"), SNAME("Editor")); + if (rtl) { + draw_rect(Rect2(Vector2(size.width - (sb->get_offset().x * 2 + label_width), 0), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color); } else { - lc = fc; + draw_rect(Rect2(Vector2(), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color); } + } - if (flat && label != String()) { - Color label_bg_color = get_theme_color(SNAME("dark_color_3"), SNAME("Editor")); - if (rtl) { - draw_rect(Rect2(Vector2(size.width - (sb->get_offset().x * 2 + label_width), 0), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color); - } else { - draw_rect(Rect2(Vector2(), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color); - } - } + if (has_focus()) { + Ref<StyleBox> focus = get_theme_stylebox(SNAME("focus"), SNAME("LineEdit")); + draw_style_box(focus, Rect2(Vector2(), size)); + } + + if (rtl) { + draw_string(font, Vector2(Math::round(size.width - sb->get_offset().x - label_width), vofs), label, HALIGN_RIGHT, -1, font_size, lc * Color(1, 1, 1, 0.5)); + } else { + draw_string(font, Vector2(Math::round(sb->get_offset().x), vofs), label, HALIGN_LEFT, -1, font_size, lc * Color(1, 1, 1, 0.5)); + } - if (has_focus()) { - Ref<StyleBox> focus = get_theme_stylebox(SNAME("focus"), SNAME("LineEdit")); - draw_style_box(focus, Rect2(Vector2(), size)); + int suffix_start = numstr.length(); + RID num_rid = TS->create_shaped_text(); + TS->shaped_text_add_string(num_rid, numstr + U"\u2009" + suffix, font->get_rids(), font_size); + + float text_start = rtl ? Math::round(sb->get_offset().x) : Math::round(sb->get_offset().x + label_width + sep); + Vector2 text_ofs = rtl ? Vector2(text_start + (number_width - TS->shaped_text_get_width(num_rid)), vofs) : Vector2(text_start, vofs); + const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(num_rid); + int v_size = visual.size(); + const TextServer::Glyph *glyphs = visual.ptr(); + for (int i = 0; i < v_size; i++) { + for (int j = 0; j < glyphs[i].repeat; j++) { + if (text_ofs.x >= text_start && (text_ofs.x + glyphs[i].advance) <= (text_start + number_width)) { + Color color = fc; + if (glyphs[i].start >= suffix_start) { + color.a *= 0.4; + } + if (glyphs[i].font_rid != RID()) { + TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color); + } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + TS->draw_hex_code_box(ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color); + } + } + text_ofs.x += glyphs[i].advance; } + } + TS->free(num_rid); + if (get_step() == 1) { + Ref<Texture2D> updown2 = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); + int updown_vofs = (size.height - updown2->get_height()) / 2; if (rtl) { - draw_string(font, Vector2(Math::round(size.width - sb->get_offset().x - label_width), vofs), label, HALIGN_RIGHT, -1, font_size, lc * Color(1, 1, 1, 0.5)); + updown_offset = sb->get_margin(SIDE_LEFT); } else { - draw_string(font, Vector2(Math::round(sb->get_offset().x), vofs), label, HALIGN_LEFT, -1, font_size, lc * Color(1, 1, 1, 0.5)); + updown_offset = size.width - sb->get_margin(SIDE_RIGHT) - updown2->get_width(); } - - int suffix_start = numstr.length(); - RID num_rid = TS->create_shaped_text(); - TS->shaped_text_add_string(num_rid, numstr + U"\u2009" + suffix, font->get_rids(), font_size); - - float text_start = rtl ? Math::round(sb->get_offset().x) : Math::round(sb->get_offset().x + label_width + sep); - Vector2 text_ofs = rtl ? Vector2(text_start + (number_width - TS->shaped_text_get_width(num_rid)), vofs) : Vector2(text_start, vofs); - const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(num_rid); - int v_size = visual.size(); - const TextServer::Glyph *glyphs = visual.ptr(); - for (int i = 0; i < v_size; i++) { - for (int j = 0; j < glyphs[i].repeat; j++) { - if (text_ofs.x >= text_start && (text_ofs.x + glyphs[i].advance) <= (text_start + number_width)) { - Color color = fc; - if (glyphs[i].start >= suffix_start) { - color.a *= 0.4; - } - if (glyphs[i].font_rid != RID()) { - TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color); - } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - TS->draw_hex_code_box(ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color); - } - } - text_ofs.x += glyphs[i].advance; - } + Color c(1, 1, 1); + if (hover_updown) { + c *= Color(1.2, 1.2, 1.2); } - TS->free(num_rid); - - if (get_step() == 1) { - Ref<Texture2D> updown2 = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); - int updown_vofs = (size.height - updown2->get_height()) / 2; - if (rtl) { - updown_offset = sb->get_margin(SIDE_LEFT); + draw_texture(updown2, Vector2(updown_offset, updown_vofs), c); + if (grabber->is_visible()) { + grabber->hide(); + } + } else if (!hide_slider) { + int grabber_w = 4 * EDSCALE; + int width = size.width - sb->get_minimum_size().width - grabber_w; + int ofs = sb->get_offset().x; + int svofs = (size.height + vofs) / 2 - 1; + Color c = fc; + c.a = 0.2; + + draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c); + int gofs = get_as_ratio() * width; + c.a = 0.9; + Rect2 grabber_rect = Rect2(ofs + gofs, svofs + 1, grabber_w, 2 * EDSCALE); + draw_rect(grabber_rect, c); + + grabbing_spinner_mouse_pos = get_global_position() + grabber_rect.position + grabber_rect.size * 0.5; + + bool display_grabber = (mouse_over_spin || mouse_over_grabber) && !grabbing_spinner && !(value_input_popup && value_input_popup->is_visible()); + if (grabber->is_visible() != display_grabber) { + if (display_grabber) { + grabber->show(); } else { - updown_offset = size.width - sb->get_margin(SIDE_RIGHT) - updown2->get_width(); - } - Color c(1, 1, 1); - if (hover_updown) { - c *= Color(1.2, 1.2, 1.2); - } - draw_texture(updown2, Vector2(updown_offset, updown_vofs), c); - if (grabber->is_visible()) { grabber->hide(); } - } else if (!hide_slider) { - int grabber_w = 4 * EDSCALE; - int width = size.width - sb->get_minimum_size().width - grabber_w; - int ofs = sb->get_offset().x; - int svofs = (size.height + vofs) / 2 - 1; - Color c = fc; - c.a = 0.2; - - draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c); - int gofs = get_as_ratio() * width; - c.a = 0.9; - Rect2 grabber_rect = Rect2(ofs + gofs, svofs + 1, grabber_w, 2 * EDSCALE); - draw_rect(grabber_rect, c); - - grabbing_spinner_mouse_pos = get_global_position() + grabber_rect.position + grabber_rect.size * 0.5; - - bool display_grabber = (mouse_over_spin || mouse_over_grabber) && !grabbing_spinner && !(value_input_popup && value_input_popup->is_visible()); - if (grabber->is_visible() != display_grabber) { - if (display_grabber) { - grabber->show(); - } else { - grabber->hide(); - } - } - - if (display_grabber) { - Ref<Texture2D> grabber_tex; - if (mouse_over_grabber) { - grabber_tex = get_theme_icon(SNAME("grabber_highlight"), SNAME("HSlider")); - } else { - grabber_tex = get_theme_icon(SNAME("grabber"), SNAME("HSlider")); - } + } - if (grabber->get_texture() != grabber_tex) { - grabber->set_texture(grabber_tex); - } + if (display_grabber) { + Ref<Texture2D> grabber_tex; + if (mouse_over_grabber) { + grabber_tex = get_theme_icon(SNAME("grabber_highlight"), SNAME("HSlider")); + } else { + grabber_tex = get_theme_icon(SNAME("grabber"), SNAME("HSlider")); + } - Vector2 scale = get_global_transform_with_canvas().get_scale(); - grabber->set_scale(scale); - grabber->set_size(Size2(0, 0)); - grabber->set_position(get_global_position() + (grabber_rect.position + grabber_rect.size * 0.5 - grabber->get_size() * 0.5) * scale); + if (grabber->get_texture() != grabber_tex) { + grabber->set_texture(grabber_tex); + } - if (mousewheel_over_grabber) { - Input::get_singleton()->warp_mouse_position(grabber->get_position() + grabber_rect.size); - } + Vector2 scale = get_global_transform_with_canvas().get_scale(); + grabber->set_scale(scale); + grabber->set_size(Size2(0, 0)); + grabber->set_position(get_global_position() + (grabber_rect.position + grabber_rect.size * 0.5 - grabber->get_size() * 0.5) * scale); - grabber_range = width; + if (mousewheel_over_grabber) { + Input::get_singleton()->warp_mouse_position(grabber->get_position() + grabber_rect.size); } + + grabber_range = width; } } +} - if (p_what == NOTIFICATION_MOUSE_ENTER) { - mouse_over_spin = true; - update(); - } - if (p_what == NOTIFICATION_MOUSE_EXIT) { - mouse_over_spin = false; - update(); - } - if (p_what == NOTIFICATION_FOCUS_ENTER) { - if ((Input::get_singleton()->is_action_pressed("ui_focus_next") || Input::get_singleton()->is_action_pressed("ui_focus_prev")) && !value_input_just_closed) { - _focus_entered(); - } - value_input_just_closed = false; +void EditorSpinSlider::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + _update_value_input_stylebox(); + break; + + case NOTIFICATION_DRAW: + _draw_spin_slider(); + break; + + case NOTIFICATION_WM_WINDOW_FOCUS_IN: + case NOTIFICATION_WM_WINDOW_FOCUS_OUT: + case NOTIFICATION_EXIT_TREE: + if (grabbing_spinner) { + grabber->hide(); + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + grabbing_spinner = false; + grabbing_spinner_attempt = false; + } + break; + + case NOTIFICATION_MOUSE_ENTER: + mouse_over_spin = true; + update(); + break; + case NOTIFICATION_MOUSE_EXIT: + mouse_over_spin = false; + update(); + break; + case NOTIFICATION_FOCUS_ENTER: + if ((Input::get_singleton()->is_action_pressed("ui_focus_next") || Input::get_singleton()->is_action_pressed("ui_focus_prev")) && !value_input_just_closed) { + _focus_entered(); + } + value_input_just_closed = false; + break; } } @@ -555,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"); @@ -567,8 +574,10 @@ void EditorSpinSlider::_ensure_input_popup() { if (value_input_popup) { return; } + value_input_popup = memnew(Popup); add_child(value_input_popup); + value_input = memnew(LineEdit); value_input_popup->add_child(value_input); value_input_popup->set_wrap_controls(true); @@ -576,6 +585,7 @@ void EditorSpinSlider::_ensure_input_popup() { value_input_popup->connect("popup_hide", callable_mp(this, &EditorSpinSlider::_value_input_closed)); value_input->connect("text_submitted", callable_mp(this, &EditorSpinSlider::_value_input_submitted)); value_input->connect("focus_exited", callable_mp(this, &EditorSpinSlider::_value_focus_exited)); + if (is_inside_tree()) { _update_value_input_stylebox(); } diff --git a/editor/editor_spin_slider.h b/editor/editor_spin_slider.h index 5b99f88505..1bf8e8eef9 100644 --- a/editor/editor_spin_slider.h +++ b/editor/editor_spin_slider.h @@ -81,10 +81,11 @@ class EditorSpinSlider : public Range { void _update_value_input_stylebox(); void _ensure_input_popup(); + void _draw_spin_slider(); 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 2c83fb2df6..8c348731d6 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -219,6 +219,7 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme = exceptions.insert("DefaultProjectIcon"); exceptions.insert("GuiChecked"); exceptions.insert("GuiRadioChecked"); + exceptions.insert("GuiIndeterminate"); exceptions.insert("GuiCloseCustomizable"); exceptions.insert("GuiGraphNodePort"); exceptions.insert("GuiResizer"); @@ -595,6 +596,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_stylebox("panel", "PanelContainer", style_menu); theme->set_stylebox("MenuPanel", "EditorStyles", style_menu); + // CanvasItem Editor + Ref<StyleBoxFlat> style_canvas_editor_info = make_flat_stylebox(Color(0.0, 0.0, 0.0, 0.2)); + style_canvas_editor_info->set_expand_margin_size_all(4 * EDSCALE); + theme->set_stylebox("CanvasItemInfoOverlay", "EditorStyles", style_canvas_editor_info); + // Script Editor theme->set_stylebox("ScriptEditorPanel", "EditorStyles", make_empty_stylebox(default_margin_size, 0, default_margin_size, default_margin_size)); theme->set_stylebox("ScriptEditor", "EditorStyles", make_empty_stylebox(0, 0, 0, 0)); @@ -812,6 +818,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // Tree theme->set_icon("checked", "Tree", theme->get_icon("GuiChecked", "EditorIcons")); + theme->set_icon("indeterminate", "Tree", theme->get_icon("GuiIndeterminate", "EditorIcons")); theme->set_icon("unchecked", "Tree", theme->get_icon("GuiUnchecked", "EditorIcons")); theme->set_icon("arrow", "Tree", theme->get_icon("GuiTreeArrowDown", "EditorIcons")); theme->set_icon("arrow_collapsed", "Tree", theme->get_icon("GuiTreeArrowRight", "EditorIcons")); @@ -1354,7 +1361,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")); @@ -1362,6 +1369,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)); @@ -1499,15 +1513,14 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { } Ref<Theme> create_custom_theme(const Ref<Theme> p_theme) { - Ref<Theme> theme; + Ref<Theme> theme = create_editor_theme(p_theme); - const String custom_theme = EditorSettings::get_singleton()->get("interface/theme/custom_theme"); - if (custom_theme != "") { - theme = ResourceLoader::load(custom_theme); - } - - if (!theme.is_valid()) { - theme = create_editor_theme(p_theme); + const String custom_theme_path = EditorSettings::get_singleton()->get("interface/theme/custom_theme"); + if (custom_theme_path != "") { + Ref<Theme> custom_theme = ResourceLoader::load(custom_theme_path); + if (custom_theme.is_valid()) { + theme->merge_with(custom_theme); + } } return theme; diff --git a/editor/editor_translation_parser.cpp b/editor/editor_translation_parser.cpp index 27d428e682..df47b2d988 100644 --- a/editor/editor_translation_parser.cpp +++ b/editor/editor_translation_parser.cpp @@ -38,15 +38,10 @@ EditorTranslationParser *EditorTranslationParser::singleton = nullptr; Error EditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) { - if (!get_script_instance()) { - return ERR_UNAVAILABLE; - } - - if (get_script_instance()->has_method("_parse_file")) { - Array ids; - Array ids_ctx_plural; - get_script_instance()->call("_parse_file", p_path, ids, ids_ctx_plural); + Array ids; + Array ids_ctx_plural; + if (GDVIRTUAL_CALL(_parse_file, p_path, ids, ids_ctx_plural)) { // Add user's extracted translatable messages. for (int i = 0; i < ids.size(); i++) { r_ids->append(ids[i]); @@ -71,12 +66,8 @@ Error EditorTranslationParserPlugin::parse_file(const String &p_path, Vector<Str } void EditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const { - if (!get_script_instance()) { - return; - } - - if (get_script_instance()->has_method("_get_recognized_extensions")) { - Array extensions = get_script_instance()->call("_get_recognized_extensions"); + Vector<String> extensions; + if (GDVIRTUAL_CALL(_get_recognized_extensions, extensions)) { for (int i = 0; i < extensions.size(); i++) { r_extensions->push_back(extensions[i]); } @@ -86,8 +77,8 @@ void EditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_ex } void EditorTranslationParserPlugin::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::NIL, "_parse_file", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::ARRAY, "msgids"), PropertyInfo(Variant::ARRAY, "msgids_context_plural"))); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_get_recognized_extensions")); + GDVIRTUAL_BIND(_parse_file, "path", "msgids", "msgids_context_plural"); + GDVIRTUAL_BIND(_get_recognized_extensions); } ///////////////////////// diff --git a/editor/editor_translation_parser.h b/editor/editor_translation_parser.h index 7013bbb8c4..242ba33b55 100644 --- a/editor/editor_translation_parser.h +++ b/editor/editor_translation_parser.h @@ -32,7 +32,9 @@ #define EDITOR_TRANSLATION_PARSER_H #include "core/error/error_list.h" +#include "core/object/gdvirtual.gen.inc" #include "core/object/ref_counted.h" +#include "core/object/script_language.h" class EditorTranslationParserPlugin : public RefCounted { GDCLASS(EditorTranslationParserPlugin, RefCounted); @@ -40,6 +42,9 @@ class EditorTranslationParserPlugin : public RefCounted { protected: static void _bind_methods(); + GDVIRTUAL3(_parse_file, String, Array, Array) + GDVIRTUAL0RC(Vector<String>, _get_recognized_extensions) + public: virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural); virtual void get_recognized_extensions(List<String> *r_extensions) const; diff --git a/editor/icons/GuiIndeterminate.svg b/editor/icons/GuiIndeterminate.svg new file mode 100644 index 0000000000..79e5d95c9a --- /dev/null +++ b/editor/icons/GuiIndeterminate.svg @@ -0,0 +1 @@ +<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m3.333 1c-1.288 0-2.333 1.045-2.333 2.333v9.334c0 1.288 1.045 2.333 2.333 2.333h9.334c1.288 0 2.333-1.045 2.333-2.333v-9.334c0-1.288-1.045-2.333-2.333-2.333z" fill="#699ce8" fill-rule="nonzero"/><path d="m3 7h10.058v2.12h-10.058z" fill="#fff" transform="matrix(.99428 0 0 .943295 .017161 .396936)"/></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/icons/VisualShaderNodeTexture2DUniformTriplanar.svg b/editor/icons/VisualShaderNodeTexture2DArrayUniform.svg index ed9e084fd3..ed9e084fd3 100644 --- a/editor/icons/VisualShaderNodeTexture2DUniformTriplanar.svg +++ b/editor/icons/VisualShaderNodeTexture2DArrayUniform.svg diff --git a/editor/icons/VisualShaderNodeTexture3DUniform.svg b/editor/icons/VisualShaderNodeTexture3DUniform.svg new file mode 100644 index 0000000000..ed9e084fd3 --- /dev/null +++ b/editor/icons/VisualShaderNodeTexture3DUniform.svg @@ -0,0 +1 @@ +<svg height="14" viewBox="0 0 14 14" width="14" xmlns="http://www.w3.org/2000/svg"><path d="m2 0c-1.1045695 0-2 .8954-2 2v10c0 1.1046.8954305 2 2 2h10c1.104569 0 2-.8954 2-2v-10c0-1.1046-.895431-2-2-2zm0 2h10v10h-10zm9 2-4 4-2-2-2 3h8z" fill="#eae068"/></svg> diff --git a/editor/icons/VisualShaderNodeTextureUniform.svg b/editor/icons/VisualShaderNodeTextureUniform.svg new file mode 100644 index 0000000000..ed9e084fd3 --- /dev/null +++ b/editor/icons/VisualShaderNodeTextureUniform.svg @@ -0,0 +1 @@ +<svg height="14" viewBox="0 0 14 14" width="14" xmlns="http://www.w3.org/2000/svg"><path d="m2 0c-1.1045695 0-2 .8954-2 2v10c0 1.1046.8954305 2 2 2h10c1.104569 0 2-.8954 2-2v-10c0-1.1046-.895431-2-2-2zm0 2h10v10h-10zm9 2-4 4-2-2-2 3h8z" fill="#eae068"/></svg> diff --git a/editor/icons/VisualShaderNodeTextureUniformTriplanar.svg b/editor/icons/VisualShaderNodeTextureUniformTriplanar.svg new file mode 100644 index 0000000000..ed9e084fd3 --- /dev/null +++ b/editor/icons/VisualShaderNodeTextureUniformTriplanar.svg @@ -0,0 +1 @@ +<svg height="14" viewBox="0 0 14 14" width="14" xmlns="http://www.w3.org/2000/svg"><path d="m2 0c-1.1045695 0-2 .8954-2 2v10c0 1.1046.8954305 2 2 2h10c1.104569 0 2-.8954 2-2v-10c0-1.1046-.895431-2-2-2zm0 2h10v10h-10zm9 2-4 4-2-2-2 3h8z" fill="#eae068"/></svg> diff --git a/editor/import/collada.cpp b/editor/import/collada.cpp index aa9700716d..71930e1e59 100644 --- a/editor/import/collada.cpp +++ b/editor/import/collada.cpp @@ -960,6 +960,7 @@ void Collada::_parse_mesh_geometry(XMLParser &parser, String p_id, String p_name } else if (section == "vertices") { MeshData::Vertices vert; String id = parser.get_attribute_value("id"); + int last_ref = 0; while (parser.read() == OK) { if (parser.get_node_type() == XMLParser::NODE_ELEMENT) { @@ -967,6 +968,10 @@ void Collada::_parse_mesh_geometry(XMLParser &parser, String p_id, String p_name String semantic = parser.get_attribute_value("semantic"); String source = _uri_to_id(parser.get_attribute_value("source")); + if (semantic == "TEXCOORD") { + semantic = "TEXCOORD" + itos(last_ref++); + } + vert.sources[semantic] = source; COLLADA_PRINT(section + " input semantic: " + semantic + " source: " + source); 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/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp index 54b93edcdd..7ab80ac3b4 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/editor_import_collada.cpp @@ -504,61 +504,121 @@ Error ColladaImport::_create_mesh_surfaces(bool p_optimize, Ref<EditorSceneImpor const Collada::MeshData::Source *normal_src = nullptr; int normal_ofs = 0; - if (p.sources.has("NORMAL")) { - String normal_source_id = p.sources["NORMAL"].source; - normal_ofs = p.sources["NORMAL"].offset; - ERR_FAIL_COND_V(!meshdata.sources.has(normal_source_id), ERR_INVALID_DATA); - normal_src = &meshdata.sources[normal_source_id]; + { + String normal_source_id = ""; + + if (p.sources.has("NORMAL")) { + normal_source_id = p.sources["NORMAL"].source; + normal_ofs = p.sources["NORMAL"].offset; + } else if (meshdata.vertices[vertex_src_id].sources.has("NORMAL")) { + normal_source_id = meshdata.vertices[vertex_src_id].sources["NORMAL"]; + normal_ofs = vertex_ofs; + } + + if (normal_source_id != "") { + ERR_FAIL_COND_V(!meshdata.sources.has(normal_source_id), ERR_INVALID_DATA); + normal_src = &meshdata.sources[normal_source_id]; + } } const Collada::MeshData::Source *binormal_src = nullptr; int binormal_ofs = 0; - if (p.sources.has("TEXBINORMAL")) { - String binormal_source_id = p.sources["TEXBINORMAL"].source; - binormal_ofs = p.sources["TEXBINORMAL"].offset; - ERR_FAIL_COND_V(!meshdata.sources.has(binormal_source_id), ERR_INVALID_DATA); - binormal_src = &meshdata.sources[binormal_source_id]; + { + String binormal_source_id = ""; + + if (p.sources.has("TEXBINORMAL")) { + binormal_source_id = p.sources["TEXBINORMAL"].source; + binormal_ofs = p.sources["TEXBINORMAL"].offset; + } else if (meshdata.vertices[vertex_src_id].sources.has("TEXBINORMAL")) { + binormal_source_id = meshdata.vertices[vertex_src_id].sources["TEXBINORMAL"]; + binormal_ofs = vertex_ofs; + } + + if (binormal_source_id != "") { + ERR_FAIL_COND_V(!meshdata.sources.has(binormal_source_id), ERR_INVALID_DATA); + binormal_src = &meshdata.sources[binormal_source_id]; + } } const Collada::MeshData::Source *tangent_src = nullptr; int tangent_ofs = 0; - if (p.sources.has("TEXTANGENT")) { - String tangent_source_id = p.sources["TEXTANGENT"].source; - tangent_ofs = p.sources["TEXTANGENT"].offset; - ERR_FAIL_COND_V(!meshdata.sources.has(tangent_source_id), ERR_INVALID_DATA); - tangent_src = &meshdata.sources[tangent_source_id]; + { + String tangent_source_id = ""; + + if (p.sources.has("TEXTANGENT")) { + tangent_source_id = p.sources["TEXTANGENT"].source; + tangent_ofs = p.sources["TEXTANGENT"].offset; + } else if (meshdata.vertices[vertex_src_id].sources.has("TEXTANGENT")) { + tangent_source_id = meshdata.vertices[vertex_src_id].sources["TEXTANGENT"]; + tangent_ofs = vertex_ofs; + } + + if (tangent_source_id != "") { + ERR_FAIL_COND_V(!meshdata.sources.has(tangent_source_id), ERR_INVALID_DATA); + tangent_src = &meshdata.sources[tangent_source_id]; + } } const Collada::MeshData::Source *uv_src = nullptr; int uv_ofs = 0; - if (p.sources.has("TEXCOORD0")) { - String uv_source_id = p.sources["TEXCOORD0"].source; - uv_ofs = p.sources["TEXCOORD0"].offset; - ERR_FAIL_COND_V(!meshdata.sources.has(uv_source_id), ERR_INVALID_DATA); - uv_src = &meshdata.sources[uv_source_id]; + { + String uv_source_id = ""; + + if (p.sources.has("TEXCOORD0")) { + uv_source_id = p.sources["TEXCOORD0"].source; + uv_ofs = p.sources["TEXCOORD0"].offset; + } else if (meshdata.vertices[vertex_src_id].sources.has("TEXCOORD0")) { + uv_source_id = meshdata.vertices[vertex_src_id].sources["TEXCOORD0"]; + uv_ofs = vertex_ofs; + } + + if (uv_source_id != "") { + ERR_FAIL_COND_V(!meshdata.sources.has(uv_source_id), ERR_INVALID_DATA); + uv_src = &meshdata.sources[uv_source_id]; + } } const Collada::MeshData::Source *uv2_src = nullptr; int uv2_ofs = 0; - if (p.sources.has("TEXCOORD1")) { - String uv2_source_id = p.sources["TEXCOORD1"].source; - uv2_ofs = p.sources["TEXCOORD1"].offset; - ERR_FAIL_COND_V(!meshdata.sources.has(uv2_source_id), ERR_INVALID_DATA); - uv2_src = &meshdata.sources[uv2_source_id]; + { + String uv2_source_id = ""; + + if (p.sources.has("TEXCOORD1")) { + uv2_source_id = p.sources["TEXCOORD1"].source; + uv2_ofs = p.sources["TEXCOORD1"].offset; + } else if (meshdata.vertices[vertex_src_id].sources.has("TEXCOORD1")) { + uv2_source_id = meshdata.vertices[vertex_src_id].sources["TEXCOORD1"]; + uv2_ofs = vertex_ofs; + } + + if (uv2_source_id != "") { + ERR_FAIL_COND_V(!meshdata.sources.has(uv2_source_id), ERR_INVALID_DATA); + uv2_src = &meshdata.sources[uv2_source_id]; + } } const Collada::MeshData::Source *color_src = nullptr; int color_ofs = 0; - if (p.sources.has("COLOR")) { - String color_source_id = p.sources["COLOR"].source; - color_ofs = p.sources["COLOR"].offset; - ERR_FAIL_COND_V(!meshdata.sources.has(color_source_id), ERR_INVALID_DATA); - color_src = &meshdata.sources[color_source_id]; + { + String color_source_id = ""; + + if (p.sources.has("COLOR")) { + color_source_id = p.sources["COLOR"].source; + color_ofs = p.sources["COLOR"].offset; + } else if (meshdata.vertices[vertex_src_id].sources.has("COLOR")) { + color_source_id = meshdata.vertices[vertex_src_id].sources["COLOR"]; + color_ofs = vertex_ofs; + } + + if (color_source_id != "") { + ERR_FAIL_COND_V(!meshdata.sources.has(color_source_id), ERR_INVALID_DATA); + color_src = &meshdata.sources[color_source_id]; + } } //find largest source.. diff --git a/editor/import/editor_import_plugin.cpp b/editor/import/editor_import_plugin.cpp index 8660289c40..d219f6e325 100644 --- a/editor/import/editor_import_plugin.cpp +++ b/editor/import/editor_import_plugin.cpp @@ -35,102 +35,131 @@ EditorImportPlugin::EditorImportPlugin() { } String EditorImportPlugin::get_importer_name() const { - ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("_get_importer_name")), ""); - return get_script_instance()->call("_get_importer_name"); + String ret; + if (GDVIRTUAL_CALL(_get_importer_name, ret)) { + return ret; + } + ERR_FAIL_V_MSG(String(), "Unimplemented _get_importer_name in add-on."); } String EditorImportPlugin::get_visible_name() const { - ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("_get_visible_name")), ""); - return get_script_instance()->call("_get_visible_name"); + String ret; + if (GDVIRTUAL_CALL(_get_visible_name, ret)) { + return ret; + } + ERR_FAIL_V_MSG(String(), "Unimplemented _get_visible_name in add-on."); } void EditorImportPlugin::get_recognized_extensions(List<String> *p_extensions) const { - ERR_FAIL_COND(!(get_script_instance() && get_script_instance()->has_method("_get_recognized_extensions"))); - Array extensions = get_script_instance()->call("_get_recognized_extensions"); - for (int i = 0; i < extensions.size(); i++) { - p_extensions->push_back(extensions[i]); + Vector<String> extensions; + + if (GDVIRTUAL_CALL(_get_recognized_extensions, extensions)) { + for (int i = 0; i < extensions.size(); i++) { + p_extensions->push_back(extensions[i]); + } } + ERR_FAIL_MSG("Unimplemented _get_recognized_extensions in add-on."); } String EditorImportPlugin::get_preset_name(int p_idx) const { - ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("_get_preset_name")), ""); - return get_script_instance()->call("_get_preset_name", p_idx); + String ret; + if (GDVIRTUAL_CALL(_get_preset_name, p_idx, ret)) { + return ret; + } + ERR_FAIL_V_MSG(String(), "Unimplemented _get_preset_name in add-on."); } int EditorImportPlugin::get_preset_count() const { - ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("_get_preset_count")), 0); - return get_script_instance()->call("_get_preset_count"); + int ret; + if (GDVIRTUAL_CALL(_get_preset_count, ret)) { + return ret; + } + ERR_FAIL_V_MSG(-1, "Unimplemented _get_preset_count in add-on."); } String EditorImportPlugin::get_save_extension() const { - ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("_get_save_extension")), ""); - return get_script_instance()->call("_get_save_extension"); + String ret; + if (GDVIRTUAL_CALL(_get_save_extension, ret)) { + return ret; + } + ERR_FAIL_V_MSG(String(), "Unimplemented _get_save_extension in add-on."); } String EditorImportPlugin::get_resource_type() const { - ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("_get_resource_type")), ""); - return get_script_instance()->call("_get_resource_type"); + String ret; + if (GDVIRTUAL_CALL(_get_resource_type, ret)) { + return ret; + } + ERR_FAIL_V_MSG(String(), "Unimplemented _get_resource_type in add-on."); } float EditorImportPlugin::get_priority() const { - if (!(get_script_instance() && get_script_instance()->has_method("_get_priority"))) { - return ResourceImporter::get_priority(); + float ret; + if (GDVIRTUAL_CALL(_get_priority, ret)) { + return ret; } - return get_script_instance()->call("_get_priority"); + ERR_FAIL_V_MSG(-1, "Unimplemented _get_priority in add-on."); } int EditorImportPlugin::get_import_order() const { - if (!(get_script_instance() && get_script_instance()->has_method("_get_import_order"))) { - return ResourceImporter::get_import_order(); + int ret; + if (GDVIRTUAL_CALL(_get_import_order, ret)) { + return ret; } - return get_script_instance()->call("_get_import_order"); + ERR_FAIL_V_MSG(-1, "Unimplemented _get_import_order in add-on."); } void EditorImportPlugin::get_import_options(List<ResourceImporter::ImportOption> *r_options, int p_preset) const { - ERR_FAIL_COND(!(get_script_instance() && get_script_instance()->has_method("_get_import_options"))); Array needed; needed.push_back("name"); needed.push_back("default_value"); - Array options = get_script_instance()->call("_get_import_options", p_preset); - for (int i = 0; i < options.size(); i++) { - Dictionary d = options[i]; - ERR_FAIL_COND(!d.has_all(needed)); - String name = d["name"]; - Variant default_value = d["default_value"]; - - PropertyHint hint = PROPERTY_HINT_NONE; - if (d.has("property_hint")) { - hint = (PropertyHint)d["property_hint"].operator int64_t(); - } - - String hint_string; - if (d.has("hint_string")) { - hint_string = d["hint_string"]; + Array options; + if (GDVIRTUAL_CALL(_get_import_options, p_preset, options)) { + for (int i = 0; i < options.size(); i++) { + Dictionary d = options[i]; + ERR_FAIL_COND(!d.has_all(needed)); + String name = d["name"]; + Variant default_value = d["default_value"]; + + PropertyHint hint = PROPERTY_HINT_NONE; + if (d.has("property_hint")) { + hint = (PropertyHint)d["property_hint"].operator int64_t(); + } + + String hint_string; + if (d.has("hint_string")) { + hint_string = d["hint_string"]; + } + + uint32_t usage = PROPERTY_USAGE_DEFAULT; + if (d.has("usage")) { + usage = d["usage"]; + } + + ImportOption option(PropertyInfo(default_value.get_type(), name, hint, hint_string, usage), default_value); + r_options->push_back(option); } - - uint32_t usage = PROPERTY_USAGE_DEFAULT; - if (d.has("usage")) { - usage = d["usage"]; - } - - ImportOption option(PropertyInfo(default_value.get_type(), name, hint, hint_string, usage), default_value); - r_options->push_back(option); } + + ERR_FAIL_MSG("Unimplemented _get_import_options in add-on."); } bool EditorImportPlugin::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { - ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("_get_option_visibility")), true); Dictionary d; Map<StringName, Variant>::Element *E = p_options.front(); while (E) { d[E->key()] = E->get(); E = E->next(); } - return get_script_instance()->call("_get_option_visibility", p_option, d); + bool visible; + if (GDVIRTUAL_CALL(_get_option_visibility, p_option, d, visible)) { + return visible; + } + + ERR_FAIL_V_MSG(false, "Unimplemented _get_option_visibility in add-on."); } Error EditorImportPlugin::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) { - ERR_FAIL_COND_V(!(get_script_instance() && get_script_instance()->has_method("_import")), ERR_UNAVAILABLE); Dictionary options; Array platform_variants, gen_files; @@ -139,28 +168,33 @@ Error EditorImportPlugin::import(const String &p_source_file, const String &p_sa options[E->key()] = E->get(); E = E->next(); } - Error err = (Error)get_script_instance()->call("_import", p_source_file, p_save_path, options, platform_variants, gen_files).operator int64_t(); - for (int i = 0; i < platform_variants.size(); i++) { - r_platform_variants->push_back(platform_variants[i]); - } - for (int i = 0; i < gen_files.size(); i++) { - r_gen_files->push_back(gen_files[i]); + int err; + if (GDVIRTUAL_CALL(_import, p_source_file, p_save_path, options, platform_variants, gen_files, err)) { + Error ret_err = Error(err); + + for (int i = 0; i < platform_variants.size(); i++) { + r_platform_variants->push_back(platform_variants[i]); + } + for (int i = 0; i < gen_files.size(); i++) { + r_gen_files->push_back(gen_files[i]); + } + return ret_err; } - return err; + ERR_FAIL_V_MSG(ERR_METHOD_NOT_FOUND, "Unimplemented _import in add-on."); } void EditorImportPlugin::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_importer_name")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_visible_name")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_preset_count")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_preset_name", PropertyInfo(Variant::INT, "preset"))); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_get_recognized_extensions")); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_get_import_options", PropertyInfo(Variant::INT, "preset"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_save_extension")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_resource_type")); - BIND_VMETHOD(MethodInfo(Variant::FLOAT, "_get_priority")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_import_order")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_get_option_visibility", PropertyInfo(Variant::STRING, "option"), PropertyInfo(Variant::DICTIONARY, "options"))); - BIND_VMETHOD(MethodInfo(Variant::INT, "_import", PropertyInfo(Variant::STRING, "source_file"), PropertyInfo(Variant::STRING, "save_path"), PropertyInfo(Variant::DICTIONARY, "options"), PropertyInfo(Variant::ARRAY, "platform_variants"), PropertyInfo(Variant::ARRAY, "gen_files"))); + GDVIRTUAL_BIND(_get_importer_name) + GDVIRTUAL_BIND(_get_visible_name) + GDVIRTUAL_BIND(_get_preset_count) + GDVIRTUAL_BIND(_get_preset_name, "preset_index") + GDVIRTUAL_BIND(_get_recognized_extensions) + GDVIRTUAL_BIND(_get_import_options, "preset_index") + GDVIRTUAL_BIND(_get_save_extension) + GDVIRTUAL_BIND(_get_resource_type) + GDVIRTUAL_BIND(_get_priority) + GDVIRTUAL_BIND(_get_import_order) + GDVIRTUAL_BIND(_get_option_visibility, "option_name", "options") + GDVIRTUAL_BIND(_import, "source_file", "save_path", "options", "platform_variants", "gen_files"); } diff --git a/editor/import/editor_import_plugin.h b/editor/import/editor_import_plugin.h index 345a40e96d..49c959ab44 100644 --- a/editor/import/editor_import_plugin.h +++ b/editor/import/editor_import_plugin.h @@ -39,6 +39,19 @@ class EditorImportPlugin : public ResourceImporter { protected: static void _bind_methods(); + GDVIRTUAL0RC(String, _get_importer_name) + GDVIRTUAL0RC(String, _get_visible_name) + GDVIRTUAL0RC(int, _get_preset_count) + GDVIRTUAL1RC(String, _get_preset_name, int) + GDVIRTUAL0RC(Vector<String>, _get_recognized_extensions) + GDVIRTUAL1RC(Array, _get_import_options, int) + GDVIRTUAL0RC(String, _get_save_extension) + GDVIRTUAL0RC(String, _get_resource_type) + GDVIRTUAL0RC(float, _get_priority) + GDVIRTUAL0RC(int, _get_import_order) + GDVIRTUAL2RC(bool, _get_option_visibility, StringName, Dictionary) + GDVIRTUAL5RC(int, _import, String, String, Dictionary, Array, Array) + public: EditorImportPlugin(); virtual String get_importer_name() const override; 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 492fa3f965..c2244befa1 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -46,21 +46,23 @@ #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" uint32_t EditorSceneImporter::get_import_flags() const { - if (get_script_instance()) { - return get_script_instance()->call("_get_import_flags"); + int ret; + if (GDVIRTUAL_CALL(_get_import_flags, ret)) { + return ret; } ERR_FAIL_V(0); } void EditorSceneImporter::get_extensions(List<String> *r_extensions) const { - if (get_script_instance()) { - Array arr = get_script_instance()->call("_get_extensions"); + Vector<String> arr; + if (GDVIRTUAL_CALL(_get_extensions, arr)) { for (int i = 0; i < arr.size(); i++) { r_extensions->push_back(arr[i]); } @@ -71,16 +73,18 @@ void EditorSceneImporter::get_extensions(List<String> *r_extensions) const { } Node *EditorSceneImporter::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { - if (get_script_instance()) { - return get_script_instance()->call("_import_scene", p_path, p_flags, p_bake_fps); + Object *ret; + if (GDVIRTUAL_CALL(_import_scene, p_path, p_flags, p_bake_fps, ret)) { + return Object::cast_to<Node>(ret); } ERR_FAIL_V(nullptr); } Ref<Animation> EditorSceneImporter::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) { - if (get_script_instance()) { - return get_script_instance()->call("_import_animation", p_path, p_flags); + Ref<Animation> ret; + if (GDVIRTUAL_CALL(_import_animation, p_path, p_flags, p_bake_fps, ret)) { + return ret; } ERR_FAIL_V(nullptr); @@ -101,15 +105,10 @@ void EditorSceneImporter::_bind_methods() { ClassDB::bind_method(D_METHOD("import_scene_from_other_importer", "path", "flags", "bake_fps"), &EditorSceneImporter::import_scene_from_other_importer); ClassDB::bind_method(D_METHOD("import_animation_from_other_importer", "path", "flags", "bake_fps"), &EditorSceneImporter::import_animation_from_other_importer); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_import_flags")); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_get_extensions")); - - MethodInfo mi = MethodInfo(Variant::OBJECT, "_import_scene", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "flags"), PropertyInfo(Variant::INT, "bake_fps")); - mi.return_val.class_name = "Node"; - BIND_VMETHOD(mi); - mi = MethodInfo(Variant::OBJECT, "_import_animation", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "flags"), PropertyInfo(Variant::INT, "bake_fps")); - mi.return_val.class_name = "Animation"; - BIND_VMETHOD(mi); + GDVIRTUAL_BIND(_get_import_flags); + GDVIRTUAL_BIND(_get_extensions); + GDVIRTUAL_BIND(_import_scene, "path", "flags", "bake_fps"); + GDVIRTUAL_BIND(_import_animation, "path", "flags", "bake_fps"); BIND_CONSTANT(IMPORT_SCENE); BIND_CONSTANT(IMPORT_ANIMATION); @@ -120,13 +119,14 @@ void EditorSceneImporter::_bind_methods() { ///////////////////////////////// void EditorScenePostImport::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_post_import", PropertyInfo(Variant::OBJECT, "scene"))); + GDVIRTUAL_BIND(_post_import, "scene") ClassDB::bind_method(D_METHOD("get_source_file"), &EditorScenePostImport::get_source_file); } Node *EditorScenePostImport::post_import(Node *p_scene) { - if (get_script_instance()) { - return get_script_instance()->call("_post_import", p_scene); + Object *ret; + if (GDVIRTUAL_CALL(_post_import, p_scene, ret)) { + return Object::cast_to<Node>(ret); } return p_scene; @@ -380,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_scene.h b/editor/import/resource_importer_scene.h index 781beff689..542959be02 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/resource_importer_scene.h @@ -51,6 +51,11 @@ protected: Node *import_scene_from_other_importer(const String &p_path, uint32_t p_flags, int p_bake_fps); Ref<Animation> import_animation_from_other_importer(const String &p_path, uint32_t p_flags, int p_bake_fps); + GDVIRTUAL0RC(int, _get_import_flags) + GDVIRTUAL0RC(Vector<String>, _get_extensions) + GDVIRTUAL3R(Object *, _import_scene, String, uint32_t, uint32_t) + GDVIRTUAL3R(Ref<Animation>, _import_animation, String, uint32_t, uint32_t) + public: enum ImportFlags { IMPORT_SCENE = 1, @@ -77,6 +82,8 @@ class EditorScenePostImport : public RefCounted { protected: static void _bind_methods(); + GDVIRTUAL1R(Object *, _post_import, Node *) + public: String get_source_file() const; virtual Node *post_import(Node *p_scene); diff --git a/editor/import/scene_importer_mesh.cpp b/editor/import/scene_importer_mesh.cpp index 0d14347225..06f373c54f 100644 --- a/editor/import/scene_importer_mesh.cpp +++ b/editor/import/scene_importer_mesh.cpp @@ -110,6 +110,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()); @@ -140,6 +146,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 +251,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()) { @@ -838,7 +845,10 @@ void EditorSceneImporterMesh::_bind_methods() { 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_mesh"), &EditorSceneImporterMesh::get_mesh); + 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", "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..e57e479d8e 100644 --- a/editor/import/scene_importer_mesh.h +++ b/editor/import/scene_importer_mesh.h @@ -88,6 +88,7 @@ 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; @@ -112,7 +113,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_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 69206daea8..030d90eeca 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -126,6 +126,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { graph->add_child(node); Ref<AnimationNode> agnode = blend_tree->get_node(E); + ERR_CONTINUE(!agnode.is_valid()); node->set_position_offset(blend_tree->get_node_position(E) * EDSCALE); 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 c273727dd3..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--) { @@ -3909,6 +3909,11 @@ void CanvasItemEditor::_notification(int p_what) { anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE); anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); + + info_overlay->get_theme()->set_stylebox("normal", "Label", get_theme_stylebox(SNAME("CanvasItemInfoOverlay"), SNAME("EditorStyles"))); + warning_child_of_container->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + warning_child_of_container->add_theme_font_override("font", get_theme_font(SNAME("main"), SNAME("EditorFonts"))); + warning_child_of_container->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts"))); } if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { @@ -4914,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); @@ -5281,21 +5286,13 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { info_overlay->add_theme_constant_override("separation", 10); viewport_scrollable->add_child(info_overlay); + // Make sure all labels inside of the container are styled the same. Theme *info_overlay_theme = memnew(Theme); - info_overlay_theme->copy_default_theme(); info_overlay->set_theme(info_overlay_theme); - StyleBoxFlat *info_overlay_label_stylebox = memnew(StyleBoxFlat); - info_overlay_label_stylebox->set_bg_color(Color(0.0, 0.0, 0.0, 0.2)); - info_overlay_label_stylebox->set_expand_margin_size_all(4); - info_overlay_theme->set_stylebox("normal", "Label", info_overlay_label_stylebox); - warning_child_of_container = memnew(Label); warning_child_of_container->hide(); warning_child_of_container->set_text(TTR("Warning: Children of a container get their position and size determined only by their parent.")); - warning_child_of_container->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); - warning_child_of_container->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts"))); - warning_child_of_container->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts"))); add_control_to_info_overlay(warning_child_of_container); h_scroll = memnew(HScrollBar); 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 486f947e43..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) { @@ -50,12 +51,7 @@ Variant CollisionShape2DEditor::get_handle_value(int idx) const { switch (shape_type) { case CAPSULE_SHAPE: { Ref<CapsuleShape2D> capsule = node->get_shape(); - - if (idx == 0) { - return capsule->get_radius(); - } else if (idx == 1) { - return capsule->get_height(); - } + return Vector2(capsule->get_radius(), capsule->get_height()); } break; @@ -85,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(); @@ -157,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(); @@ -209,17 +223,17 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) { case CAPSULE_SHAPE: { Ref<CapsuleShape2D> capsule = node->get_shape(); + Vector2 values = p_org; + if (idx == 0) { undo_redo->add_do_method(capsule.ptr(), "set_radius", capsule->get_radius()); - undo_redo->add_do_method(canvas_item_editor, "update_viewport"); - undo_redo->add_undo_method(capsule.ptr(), "set_radius", p_org); - undo_redo->add_do_method(canvas_item_editor, "update_viewport"); } else if (idx == 1) { undo_redo->add_do_method(capsule.ptr(), "set_height", capsule->get_height()); - undo_redo->add_do_method(canvas_item_editor, "update_viewport"); - undo_redo->add_undo_method(capsule.ptr(), "set_height", p_org); - undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); } + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_method(capsule.ptr(), "set_radius", values[0]); + undo_redo->add_undo_method(capsule.ptr(), "set_height", values[1]); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); } break; @@ -258,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(); @@ -399,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)) { @@ -476,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/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index b93e12d7fa..d04e88e915 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" @@ -105,9 +106,7 @@ void EditorNode3DGizmo::clear() { } void EditorNode3DGizmo::redraw() { - if (get_script_instance() && get_script_instance()->has_method("_redraw")) { - get_script_instance()->call("_redraw"); - } else { + if (!GDVIRTUAL_CALL(_redraw)) { ERR_FAIL_COND(!gizmo_plugin); gizmo_plugin->redraw(this); } @@ -118,8 +117,9 @@ void EditorNode3DGizmo::redraw() { } String EditorNode3DGizmo::get_handle_name(int p_id) const { - if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) { - return get_script_instance()->call("_get_handle_name", p_id); + String ret; + if (GDVIRTUAL_CALL(_get_handle_name, p_id, ret)) { + return ret; } ERR_FAIL_COND_V(!gizmo_plugin, ""); @@ -127,8 +127,9 @@ String EditorNode3DGizmo::get_handle_name(int p_id) const { } bool EditorNode3DGizmo::is_handle_highlighted(int p_id) const { - if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) { - return get_script_instance()->call("_is_handle_highlighted", p_id); + bool success; + if (GDVIRTUAL_CALL(_is_handle_highlighted, p_id, success)) { + return success; } ERR_FAIL_COND_V(!gizmo_plugin, false); @@ -136,8 +137,9 @@ bool EditorNode3DGizmo::is_handle_highlighted(int p_id) const { } Variant EditorNode3DGizmo::get_handle_value(int p_id) const { - if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) { - return get_script_instance()->call("_get_handle_value", p_id); + Variant value; + if (GDVIRTUAL_CALL(_get_handle_value, p_id, value)) { + return value; } ERR_FAIL_COND_V(!gizmo_plugin, Variant()); @@ -145,8 +147,7 @@ Variant EditorNode3DGizmo::get_handle_value(int p_id) const { } void EditorNode3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) { - if (get_script_instance() && get_script_instance()->has_method("_set_handle")) { - get_script_instance()->call("_set_handle", p_id, p_camera, p_point); + if (GDVIRTUAL_CALL(_set_handle, p_id, p_camera, p_point)) { return; } @@ -155,8 +156,7 @@ void EditorNode3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p } void EditorNode3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cancel) { - if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) { - get_script_instance()->call("_commit_handle", p_id, p_restore, p_cancel); + if (GDVIRTUAL_CALL(_commit_handle, p_id, p_restore, p_cancel)) { return; } @@ -165,8 +165,9 @@ void EditorNode3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p } int EditorNode3DGizmo::subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const { - if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_ray")) { - return get_script_instance()->call("_subgizmos_intersect_ray", p_camera, p_point); + int id; + if (GDVIRTUAL_CALL(_subgizmos_intersect_ray, p_camera, p_point, id)) { + return id; } ERR_FAIL_COND_V(!gizmo_plugin, -1); @@ -174,12 +175,14 @@ int EditorNode3DGizmo::subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 } Vector<int> EditorNode3DGizmo::subgizmos_intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) const { - if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_frustum")) { - Array frustum; - for (int i = 0; i < p_frustum.size(); i++) { - frustum[i] = p_frustum[i]; - } - return get_script_instance()->call("_subgizmos_intersect_frustum", p_camera, frustum); + TypedArray<Plane> frustum; + frustum.resize(p_frustum.size()); + for (int i = 0; i < p_frustum.size(); i++) { + frustum[i] = p_frustum[i]; + } + Vector<int> ret; + if (GDVIRTUAL_CALL(_subgizmos_intersect_frustum, p_camera, frustum, ret)) { + return ret; } ERR_FAIL_COND_V(!gizmo_plugin, Vector<int>()); @@ -187,8 +190,9 @@ Vector<int> EditorNode3DGizmo::subgizmos_intersect_frustum(const Camera3D *p_cam } Transform3D EditorNode3DGizmo::get_subgizmo_transform(int p_id) const { - if (get_script_instance() && get_script_instance()->has_method("_get_subgizmo_transform")) { - return get_script_instance()->call("_get_subgizmo_transform", p_id); + Transform3D ret; + if (GDVIRTUAL_CALL(_get_subgizmo_transform, p_id, ret)) { + return ret; } ERR_FAIL_COND_V(!gizmo_plugin, Transform3D()); @@ -196,8 +200,7 @@ Transform3D EditorNode3DGizmo::get_subgizmo_transform(int p_id) const { } void EditorNode3DGizmo::set_subgizmo_transform(int p_id, Transform3D p_transform) { - if (get_script_instance() && get_script_instance()->has_method("_set_subgizmo_transform")) { - get_script_instance()->call("_set_subgizmo_transform", p_id, p_transform); + if (GDVIRTUAL_CALL(_set_subgizmo_transform, p_id, p_transform)) { return; } @@ -206,18 +209,13 @@ void EditorNode3DGizmo::set_subgizmo_transform(int p_id, Transform3D p_transform } void EditorNode3DGizmo::commit_subgizmos(const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { - if (get_script_instance() && get_script_instance()->has_method("_commit_subgizmos")) { - Array ids; - for (int i = 0; i < p_ids.size(); i++) { - ids[i] = p_ids[i]; - } - - Array restore; - for (int i = 0; i < p_restore.size(); i++) { - restore[i] = p_restore[i]; - } + TypedArray<Transform3D> restore; + restore.resize(p_restore.size()); + for (int i = 0; i < p_restore.size(); i++) { + restore[i] = p_restore[i]; + } - get_script_instance()->call("_commit_subgizmos", ids, restore, p_cancel); + if (GDVIRTUAL_CALL(_commit_subgizmos, p_ids, restore, p_cancel)) { return; } @@ -837,26 +835,19 @@ void EditorNode3DGizmo::_bind_methods() { ClassDB::bind_method(D_METHOD("is_subgizmo_selected"), &EditorNode3DGizmo::is_subgizmo_selected); ClassDB::bind_method(D_METHOD("get_subgizmo_selection"), &EditorNode3DGizmo::get_subgizmo_selection); - BIND_VMETHOD(MethodInfo("_redraw")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", PropertyInfo(Variant::INT, "id"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", PropertyInfo(Variant::INT, "id"))); + GDVIRTUAL_BIND(_redraw); + GDVIRTUAL_BIND(_get_handle_name, "id"); + GDVIRTUAL_BIND(_is_handle_highlighted, "id"); - MethodInfo hvget(Variant::NIL, "_get_handle_value", PropertyInfo(Variant::INT, "id")); - hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(hvget); + GDVIRTUAL_BIND(_get_handle_value, "id"); + GDVIRTUAL_BIND(_set_handle, "id", "camera", "point"); + GDVIRTUAL_BIND(_commit_handle, "id", "restore", "cancel"); - BIND_VMETHOD(MethodInfo("_set_handle", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point"))); - MethodInfo cm = MethodInfo("_commit_handle", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel")); - cm.default_arguments.push_back(false); - BIND_VMETHOD(cm); - - BIND_VMETHOD(MethodInfo(Variant::INT, "_subgizmos_intersect_ray", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point"))); - BIND_VMETHOD(MethodInfo(Variant::PACKED_INT32_ARRAY, "_subgizmos_intersect_frustum", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::ARRAY, "frustum"))); - BIND_VMETHOD(MethodInfo(Variant::TRANSFORM3D, "_get_subgizmo_transform", PropertyInfo(Variant::INT, "id"))); - BIND_VMETHOD(MethodInfo("_set_subgizmo_transform", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::TRANSFORM3D, "transform"))); - MethodInfo cs = MethodInfo("_commit_subgizmos", PropertyInfo(Variant::PACKED_INT32_ARRAY, "ids"), PropertyInfo(Variant::ARRAY, "restore"), PropertyInfo(Variant::BOOL, "cancel")); - cs.default_arguments.push_back(false); - BIND_VMETHOD(cs); + GDVIRTUAL_BIND(_subgizmos_intersect_ray, "camera", "point"); + GDVIRTUAL_BIND(_subgizmos_intersect_frustum, "camera", "frustum"); + GDVIRTUAL_BIND(_set_subgizmo_transform, "id", "transform"); + GDVIRTUAL_BIND(_get_subgizmo_transform, "id"); + GDVIRTUAL_BIND(_commit_subgizmos, "ids", "restores", "cancel"); } EditorNode3DGizmo::EditorNode3DGizmo() { @@ -1042,11 +1033,6 @@ Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) { } void EditorNode3DGizmoPlugin::_bind_methods() { -#define GIZMO_REF PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "EditorNode3DGizmo") - - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"))); - BIND_VMETHOD(MethodInfo(GIZMO_REF, "_create_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"))); - ClassDB::bind_method(D_METHOD("create_material", "name", "color", "billboard", "on_top", "use_vertex_color"), &EditorNode3DGizmoPlugin::create_material, DEFVAL(false), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("create_icon_material", "name", "texture", "on_top", "color"), &EditorNode3DGizmoPlugin::create_icon_material, DEFVAL(false), DEFVAL(Color(1, 1, 1, 1))); ClassDB::bind_method(D_METHOD("create_handle_material", "name", "billboard", "texture"), &EditorNode3DGizmoPlugin::create_handle_material, DEFVAL(false), DEFVAL(Variant())); @@ -1054,45 +1040,42 @@ void EditorNode3DGizmoPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("get_material", "name", "gizmo"), &EditorNode3DGizmoPlugin::get_material, DEFVAL(Ref<EditorNode3DGizmo>())); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_gizmo_name")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_priority")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_be_hidden")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_selectable_when_hidden")); - - BIND_VMETHOD(MethodInfo("_redraw", GIZMO_REF)); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", GIZMO_REF, PropertyInfo(Variant::INT, "id"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", GIZMO_REF, PropertyInfo(Variant::INT, "id"))); + GDVIRTUAL_BIND(_has_gizmo, "for_node_3d"); + GDVIRTUAL_BIND(_create_gizmo, "for_node_3d"); - MethodInfo hvget(Variant::NIL, "_get_handle_value", GIZMO_REF, PropertyInfo(Variant::INT, "id")); - hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(hvget); + GDVIRTUAL_BIND(_get_gizmo_name); + GDVIRTUAL_BIND(_get_priority); + GDVIRTUAL_BIND(_can_be_hidden); + GDVIRTUAL_BIND(_is_selectable_when_hidden); - BIND_VMETHOD(MethodInfo("_set_handle", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point"))); - MethodInfo cm = MethodInfo("_commit_handle", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel")); - cm.default_arguments.push_back(false); - BIND_VMETHOD(cm); + GDVIRTUAL_BIND(_redraw, "gizmo"); + GDVIRTUAL_BIND(_get_handle_name, "gizmo", "handle_id"); + GDVIRTUAL_BIND(_is_handle_highlighted, "gizmo", "handle_id"); + GDVIRTUAL_BIND(_get_handle_value, "gizmo", "handle_id"); - BIND_VMETHOD(MethodInfo(Variant::INT, "_subgizmos_intersect_ray", GIZMO_REF, PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point"))); - BIND_VMETHOD(MethodInfo(Variant::PACKED_INT32_ARRAY, "_subgizmos_intersect_frustum", GIZMO_REF, PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::ARRAY, "frustum"))); - BIND_VMETHOD(MethodInfo(Variant::TRANSFORM3D, "_get_subgizmo_transform", GIZMO_REF, PropertyInfo(Variant::INT, "id"))); - BIND_VMETHOD(MethodInfo("_set_subgizmo_transform", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::TRANSFORM3D, "transform"))); - MethodInfo cs = MethodInfo("_commit_subgizmos", GIZMO_REF, PropertyInfo(Variant::PACKED_INT32_ARRAY, "ids"), PropertyInfo(Variant::ARRAY, "restore"), PropertyInfo(Variant::BOOL, "cancel")); - cs.default_arguments.push_back(false); - BIND_VMETHOD(cs); + GDVIRTUAL_BIND(_set_handle, "gizmo", "handle_id", "camera", "screen_pos"); + GDVIRTUAL_BIND(_commit_handle, "gizmo", "handle_id", "restore", "cancel"); -#undef GIZMO_REF + GDVIRTUAL_BIND(_subgizmos_intersect_ray, "gizmo", "camera", "screen_pos"); + GDVIRTUAL_BIND(_subgizmos_intersect_frustum, "gizmo", "camera", "frustum_planes"); + GDVIRTUAL_BIND(_get_subgizmo_transform, "gizmo", "subgizmo_id"); + GDVIRTUAL_BIND(_set_subgizmo_transform, "gizmo", "subgizmo_id", "transform"); + GDVIRTUAL_BIND(_commit_subgizmos, "gizmo", "ids", "restores", "cancel"); + ; } bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - if (get_script_instance() && get_script_instance()->has_method("_has_gizmo")) { - return get_script_instance()->call("_has_gizmo", p_spatial); + bool success; + if (GDVIRTUAL_CALL(_has_gizmo, p_spatial, success)) { + return success; } return false; } Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) { - if (get_script_instance() && get_script_instance()->has_method("_create_gizmo")) { - return get_script_instance()->call("_create_gizmo", p_spatial); + Ref<EditorNode3DGizmo> ret; + if (GDVIRTUAL_CALL(_create_gizmo, p_spatial, ret)) { + return ret; } Ref<EditorNode3DGizmo> ref; @@ -1103,106 +1086,100 @@ Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) } bool EditorNode3DGizmoPlugin::can_be_hidden() const { - if (get_script_instance() && get_script_instance()->has_method("_can_be_hidden")) { - return get_script_instance()->call("_can_be_hidden"); + bool ret; + if (GDVIRTUAL_CALL(_can_be_hidden, ret)) { + return ret; } return true; } bool EditorNode3DGizmoPlugin::is_selectable_when_hidden() const { - if (get_script_instance() && get_script_instance()->has_method("_is_selectable_when_hidden")) { - return get_script_instance()->call("_is_selectable_when_hidden"); + bool ret; + if (GDVIRTUAL_CALL(_is_selectable_when_hidden, ret)) { + return ret; } return false; } void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - if (get_script_instance() && get_script_instance()->has_method("_redraw")) { - Ref<EditorNode3DGizmo> ref(p_gizmo); - get_script_instance()->call("_redraw", ref); - } + GDVIRTUAL_CALL(_redraw, p_gizmo); } bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const { - if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) { - return get_script_instance()->call("_is_handle_highlighted", p_gizmo, p_id); + bool ret; + if (GDVIRTUAL_CALL(_is_handle_highlighted, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret)) { + return ret; } return false; } String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { - if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) { - return get_script_instance()->call("_get_handle_name", p_gizmo, p_id); + String ret; + if (GDVIRTUAL_CALL(_get_handle_name, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret)) { + return ret; } return ""; } Variant EditorNode3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) { - return get_script_instance()->call("_get_handle_value", p_gizmo, p_id); + Variant ret; + if (GDVIRTUAL_CALL(_get_handle_value, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret)) { + return ret; } return Variant(); } void EditorNode3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - if (get_script_instance() && get_script_instance()->has_method("_set_handle")) { - get_script_instance()->call("_set_handle", p_gizmo, p_id, p_camera, p_point); - } + GDVIRTUAL_CALL(_set_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_camera, p_point); } void EditorNode3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) { - get_script_instance()->call("_commit_handle", p_gizmo, p_id, p_restore, p_cancel); - } + GDVIRTUAL_CALL(_commit_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_restore, p_cancel); } int EditorNode3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { - if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_ray")) { - return get_script_instance()->call("_subgizmos_intersect_ray", p_camera, p_point); + int ret; + if (GDVIRTUAL_CALL(_subgizmos_intersect_ray, Ref<EditorNode3DGizmo>(p_gizmo), p_camera, p_point, ret)) { + return ret; } return -1; } Vector<int> EditorNode3DGizmoPlugin::subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const { - if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_frustum")) { - Array frustum; - for (int i = 0; i < p_frustum.size(); i++) { - frustum[i] = p_frustum[i]; - } - return get_script_instance()->call("_subgizmos_intersect_frustum", p_camera, frustum); + TypedArray<Transform3D> frustum; + frustum.resize(p_frustum.size()); + for (int i = 0; i < p_frustum.size(); i++) { + frustum[i] = p_frustum[i]; + } + Vector<int> ret; + if (GDVIRTUAL_CALL(_subgizmos_intersect_frustum, Ref<EditorNode3DGizmo>(p_gizmo), p_camera, frustum, ret)) { + return ret; } return Vector<int>(); } Transform3D EditorNode3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { - if (get_script_instance() && get_script_instance()->has_method("_get_subgizmo_transform")) { - return get_script_instance()->call("_get_subgizmo_transform", p_id); + Transform3D ret; + if (GDVIRTUAL_CALL(_get_subgizmo_transform, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret)) { + return ret; } return Transform3D(); } void EditorNode3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { - if (get_script_instance() && get_script_instance()->has_method("_set_subgizmo_transform")) { - get_script_instance()->call("_set_subgizmo_transform", p_id, p_transform); - } + GDVIRTUAL_CALL(_set_subgizmo_transform, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_transform); } void EditorNode3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { - if (get_script_instance() && get_script_instance()->has_method("_commit_subgizmos")) { - Array ids; - for (int i = 0; i < p_ids.size(); i++) { - ids[i] = p_ids[i]; - } - - Array restore; - for (int i = 0; i < p_restore.size(); i++) { - restore[i] = p_restore[i]; - } - - get_script_instance()->call("_commit_subgizmos", ids, restore, p_cancel); + TypedArray<Transform3D> restore; + restore.resize(p_restore.size()); + for (int i = 0; i < p_restore.size(); i++) { + restore[i] = p_restore[i]; } + + GDVIRTUAL_CALL(_commit_subgizmos, Ref<EditorNode3DGizmo>(p_gizmo), p_ids, restore, p_cancel); } void EditorNode3DGizmoPlugin::set_state(int p_state) { @@ -2104,7 +2081,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { grests.resize(skel->get_bone_count()); LocalVector<int> bones; - LocalVector<real_t> weights; + LocalVector<float> weights; bones.resize(4); weights.resize(4); @@ -4091,6 +4068,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 ""; } @@ -4114,7 +4095,7 @@ Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p if (Object::cast_to<CapsuleShape3D>(*s)) { Ref<CapsuleShape3D> cs2 = s; - return p_id == 0 ? cs2->get_radius() : cs2->get_height(); + return Vector2(cs2->get_radius(), cs2->get_height()); } if (Object::cast_to<CylinderShape3D>(*s)) { @@ -4122,6 +4103,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(); } @@ -4157,6 +4143,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; @@ -4261,12 +4263,11 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo if (Object::cast_to<CapsuleShape3D>(*s)) { Ref<CapsuleShape3D> ss = s; + Vector2 values = p_restore; + if (p_cancel) { - if (p_id == 0) { - ss->set_radius(p_restore); - } else { - ss->set_height(p_restore); - } + ss->set_radius(values[0]); + ss->set_height(values[1]); return; } @@ -4274,12 +4275,12 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo if (p_id == 0) { ur->create_action(TTR("Change Capsule Shape Radius")); ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius()); - ur->add_undo_method(ss.ptr(), "set_radius", p_restore); } else { ur->create_action(TTR("Change Capsule Shape Height")); ur->add_do_method(ss.ptr(), "set_height", ss->get_height()); - ur->add_undo_method(ss.ptr(), "set_height", p_restore); } + ur->add_undo_method(ss.ptr(), "set_radius", values[0]); + ur->add_undo_method(ss.ptr(), "set_height", values[1]); ur->commit_action(); } @@ -4312,6 +4313,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) { @@ -4582,6 +4597,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_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index 2cc0951557..415ed5da5c 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -33,10 +33,10 @@ #include "core/templates/local_vector.h" #include "core/templates/ordered_hash_map.h" +#include "scene/3d/camera_3d.h" #include "scene/3d/node_3d.h" #include "scene/3d/skeleton_3d.h" -class Camera3D; class Timer; class EditorNode3DGizmoPlugin; @@ -79,6 +79,19 @@ protected: EditorNode3DGizmoPlugin *gizmo_plugin; + GDVIRTUAL0(_redraw) + GDVIRTUAL1RC(String, _get_handle_name, int) + GDVIRTUAL1RC(bool, _is_handle_highlighted, int) + + GDVIRTUAL1RC(Variant, _get_handle_value, int) + GDVIRTUAL3(_set_handle, int, const Camera3D *, Vector2) + GDVIRTUAL3(_commit_handle, int, Variant, bool) + + GDVIRTUAL2RC(int, _subgizmos_intersect_ray, const Camera3D *, Vector2) + GDVIRTUAL2RC(Vector<int>, _subgizmos_intersect_frustum, const Camera3D *, TypedArray<Plane>) + GDVIRTUAL1RC(Transform3D, _get_subgizmo_transform, int) + GDVIRTUAL2(_set_subgizmo_transform, int, Transform3D) + GDVIRTUAL3(_commit_subgizmos, Vector<int>, TypedArray<Transform3D>, bool) public: void add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1)); void add_vertices(const Vector<Vector3> &p_vertices, const Ref<Material> &p_material, Mesh::PrimitiveType p_primitive_type, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1)); @@ -145,6 +158,28 @@ protected: virtual bool has_gizmo(Node3D *p_spatial); virtual Ref<EditorNode3DGizmo> create_gizmo(Node3D *p_spatial); + GDVIRTUAL1RC(bool, _has_gizmo, Node3D *) + GDVIRTUAL1RC(Ref<EditorNode3DGizmo>, _create_gizmo, Node3D *) + + GDVIRTUAL0RC(String, _get_gizmo_name) + GDVIRTUAL0RC(int, _get_priority) + GDVIRTUAL0RC(bool, _can_be_hidden) + GDVIRTUAL0RC(bool, _is_selectable_when_hidden) + + GDVIRTUAL1(_redraw, Ref<EditorNode3DGizmo>) + GDVIRTUAL2RC(String, _get_handle_name, Ref<EditorNode3DGizmo>, int) + GDVIRTUAL2RC(bool, _is_handle_highlighted, Ref<EditorNode3DGizmo>, int) + GDVIRTUAL2RC(Variant, _get_handle_value, Ref<EditorNode3DGizmo>, int) + + GDVIRTUAL4(_set_handle, Ref<EditorNode3DGizmo>, int, const Camera3D *, Vector2) + GDVIRTUAL4(_commit_handle, Ref<EditorNode3DGizmo>, int, Variant, bool) + + GDVIRTUAL3RC(int, _subgizmos_intersect_ray, Ref<EditorNode3DGizmo>, const Camera3D *, Vector2) + GDVIRTUAL3RC(Vector<int>, _subgizmos_intersect_frustum, Ref<EditorNode3DGizmo>, const Camera3D *, TypedArray<Plane>) + GDVIRTUAL2RC(Transform3D, _get_subgizmo_transform, Ref<EditorNode3DGizmo>, int) + GDVIRTUAL3(_set_subgizmo_transform, Ref<EditorNode3DGizmo>, int, Transform3D) + GDVIRTUAL4(_commit_subgizmos, Ref<EditorNode3DGizmo>, Vector<int>, TypedArray<Transform3D>, bool) + public: void create_material(const String &p_name, const Color &p_color, bool p_billboard = false, bool p_on_top = false, bool p_use_vertex_color = false); void create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top = false, const Color &p_albedo = Color(1, 1, 1, 1)); 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 d29c1efabb..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); @@ -532,7 +530,7 @@ private: bool grid_enable[3]; //should be always visible if true bool grid_enabled; bool grid_init_draw = false; - Camera3D::Projection grid_camera_last_update_perspective; + Camera3D::Projection grid_camera_last_update_perspective = Camera3D::PROJECTION_PERSPECTIVE; Vector3 grid_camera_last_update_position = Vector3(); Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3]; @@ -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/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 226a54b966..3d2b3f4478 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -54,17 +54,17 @@ /*** SYNTAX HIGHLIGHTER ****/ String EditorSyntaxHighlighter::_get_name() const { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_get_name")) { - return si->call("_get_name"); + String ret; + if (GDVIRTUAL_CALL(_get_name, ret)) { + return ret; } return "Unnamed"; } Array EditorSyntaxHighlighter::_get_supported_languages() const { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_get_supported_languages")) { - return si->call("_get_supported_languages"); + Array ret; + if (GDVIRTUAL_CALL(_get_supported_languages, ret)) { + return ret; } return Array(); } @@ -81,9 +81,8 @@ Ref<EditorSyntaxHighlighter> EditorSyntaxHighlighter::_create() const { void EditorSyntaxHighlighter::_bind_methods() { ClassDB::bind_method(D_METHOD("_get_edited_resource"), &EditorSyntaxHighlighter::_get_edited_resource); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_name")); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_get_supported_languages")); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_get_supported_extentions")); + GDVIRTUAL_BIND(_get_name) + GDVIRTUAL_BIND(_get_supported_languages) } //// @@ -104,11 +103,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() { List<StringName> types; ClassDB::get_class_list(&types); for (const StringName &E : types) { - String n = E; - if (n.begins_with("_")) { - n = n.substr(1, n.length()); - } - highlighter->add_keyword_color(n, type_color); + highlighter->add_keyword_color(E, type_color); } /* User types. */ @@ -227,8 +222,6 @@ void ScriptEditorBase::_bind_methods() { // TODO: This signal is no use for VisualScript. ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text"))); ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text"))); - - BIND_VMETHOD(MethodInfo("_add_syntax_highlighter", PropertyInfo(Variant::OBJECT, "highlighter"))); } static bool _is_built_in_script(Script *p_script) { @@ -323,7 +316,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(); } } @@ -942,7 +935,10 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { } _update_script_names(); + _trigger_live_script_reload(); +} +void ScriptEditor::_trigger_live_script_reload() { if (!pending_auto_reload && auto_reload_running_scripts) { call_deferred(SNAME("_live_auto_reload_running_scripts")); pending_auto_reload = true; @@ -2732,7 +2728,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()) { @@ -3276,7 +3272,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); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 72a649ffbf..a57aeea5c0 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -56,6 +56,9 @@ private: protected: static void _bind_methods(); + GDVIRTUAL0RC(String, _get_name) + GDVIRTUAL0RC(Array, _get_supported_languages) + public: virtual String _get_name() const; virtual Array _get_supported_languages() const; @@ -74,7 +77,7 @@ private: public: virtual void _update_cache() override; - virtual Dictionary _get_line_syntax_highlighting(int p_line) override { return highlighter->get_line_syntax_highlighting(p_line); } + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override { return highlighter->get_line_syntax_highlighting(p_line); } virtual String _get_name() const override { return TTR("Standard"); } @@ -344,6 +347,7 @@ class ScriptEditor : public PanelContainer { bool pending_auto_reload; bool auto_reload_running_scripts; + void _trigger_live_script_reload(); void _live_auto_reload_running_scripts(); void _update_selected_editor_menu(); @@ -407,7 +411,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 4491c13b4c..5f48106afc 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -666,6 +666,8 @@ void ScriptEditor::_update_modified_scripts_for_external_editor(Ref<Script> p_fo script->set_source_code(rel_script->get_source_code()); script->set_last_modified_time(rel_script->get_last_modified_time()); script->update_exports(); + + _trigger_live_script_reload(); } } } @@ -756,8 +758,6 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c } else if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK) { _goto_line(p_row); - result.class_name = result.class_name.trim_prefix("_"); - switch (result.type) { case ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION: { if (result.script.is_valid()) { @@ -1628,6 +1628,13 @@ void ScriptTextEditor::_color_changed(const Color &p_color) { code_editor->get_text_editor()->update(); } +void ScriptTextEditor::_prepare_edit_menu() { + const CodeEdit *tx = code_editor->get_text_editor(); + PopupMenu *popup = edit_menu->get_popup(); + popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo()); + popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo()); +} + void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition, Vector2 p_pos) { context_menu->clear(); context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); @@ -1667,6 +1674,10 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p } } + const CodeEdit *tx = code_editor->get_text_editor(); + context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo()); + context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo()); + context_menu->set_position(get_global_transform().xform(p_pos)); context_menu->set_size(Vector2(1, 1)); context_menu->popup(); @@ -1752,6 +1763,7 @@ void ScriptTextEditor::_enable_code_editor() { search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); edit_hb->add_child(edit_menu); + edit_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_prepare_edit_menu)); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); edit_menu->get_popup()->add_separator(); diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index e4a13951e4..1ca6f56ea1 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -178,6 +178,7 @@ protected: void _make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition, Vector2 p_pos); void _text_edit_gui_input(const Ref<InputEvent> &ev); void _color_changed(const Color &p_color); + void _prepare_edit_menu(); void _goto_line(int p_line) { goto_line(p_line); } void _lookup_symbol(const String &p_symbol, int p_row, int p_column); 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.cpp b/editor/plugins/text_editor.cpp index 481eb84081..32bcc1a4e6 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -471,6 +471,13 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { } } +void TextEditor::_prepare_edit_menu() { + const CodeEdit *tx = code_editor->get_text_editor(); + PopupMenu *popup = edit_menu->get_popup(); + popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo()); + popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo()); +} + void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position) { context_menu->clear(); if (p_selection) { @@ -497,6 +504,10 @@ void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE); } + const CodeEdit *tx = code_editor->get_text_editor(); + context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo()); + context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo()); + context_menu->set_position(get_global_transform().xform(p_position)); context_menu->set_size(Vector2(1, 1)); context_menu->popup(); @@ -542,6 +553,7 @@ TextEditor::TextEditor() { edit_hb->add_child(edit_menu); edit_menu->set_text(TTR("Edit")); edit_menu->set_switch_on_hover(true); + edit_menu->connect("about_to_popup", callable_mp(this, &TextEditor::_prepare_edit_menu)); edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextEditor::_edit_option)); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index 86a4910ac0..839e1c5f7a 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -92,6 +92,7 @@ protected: void _edit_option(int p_op); void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position); void _text_edit_gui_input(const Ref<InputEvent> &ev); + void _prepare_edit_menu(); Map<String, Ref<EditorSyntaxHighlighter>> highlighters; void _change_syntax_highlighter(int p_idx); 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..d406c2514c 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; diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 2dd8270ee3..5b1da11f12 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -70,14 +70,15 @@ const int MAX_FLOAT_CONST_DEFS = sizeof(float_constant_defs) / sizeof(FloatConst /////////////////// Control *VisualShaderNodePlugin::create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node) { - if (get_script_instance()) { - return get_script_instance()->call("_create_editor", p_parent_resource, p_node); + Object *ret; + if (GDVIRTUAL_CALL(_create_editor, p_parent_resource, p_node, ret)) { + return Object::cast_to<Control>(ret); } return nullptr; } void VisualShaderNodePlugin::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_create_editor", PropertyInfo(Variant::OBJECT, "parent_resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), PropertyInfo(Variant::OBJECT, "for_node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode"))); + GDVIRTUAL_BIND(_create_editor, "parent_resource", "visual_shader_node"); } /////////////////// @@ -1219,8 +1220,88 @@ void VisualShaderEditor::_update_options_menu() { Vector<AddOption> custom_options; Vector<AddOption> embedded_options; + static Vector<String> type_filter_exceptions; + if (type_filter_exceptions.is_empty()) { + type_filter_exceptions.append("VisualShaderNodeExpression"); + } + for (int i = 0; i < add_options.size(); i++) { if (!use_filter || add_options[i].name.findn(filter) != -1) { + // port type filtering + if (members_output_port_type != VisualShaderNode::PORT_TYPE_MAX || members_input_port_type != VisualShaderNode::PORT_TYPE_MAX) { + Ref<VisualShaderNode> vsn; + int check_result = 0; + + if (!add_options[i].is_custom) { + vsn = Ref<VisualShaderNode>(Object::cast_to<VisualShaderNode>(ClassDB::instantiate(add_options[i].type))); + if (!vsn.is_valid()) { + continue; + } + + if (type_filter_exceptions.has(add_options[i].type)) { + check_result = 1; + } + + Ref<VisualShaderNodeInput> input = Object::cast_to<VisualShaderNodeInput>(vsn.ptr()); + if (input.is_valid()) { + input->set_shader_mode(visual_shader->get_mode()); + input->set_shader_type(visual_shader->get_shader_type()); + input->set_input_name(add_options[i].sub_func_str); + } + + Ref<VisualShaderNodeExpression> expression = Object::cast_to<VisualShaderNodeExpression>(vsn.ptr()); + if (expression.is_valid()) { + if (members_input_port_type == VisualShaderNode::PORT_TYPE_SAMPLER) { + check_result = -1; // expressions creates a port with required type automatically (except for sampler output) + } + } + + Ref<VisualShaderNodeUniformRef> uniform_ref = Object::cast_to<VisualShaderNodeUniformRef>(vsn.ptr()); + if (uniform_ref.is_valid()) { + check_result = -1; + + if (members_input_port_type != VisualShaderNode::PORT_TYPE_MAX) { + for (int j = 0; j < uniform_ref->get_uniforms_count(); j++) { + if (visual_shader->is_port_types_compatible(uniform_ref->get_port_type_by_index(j), members_input_port_type)) { + check_result = 1; + break; + } + } + } + } + } else { + check_result = 1; + } + + if (members_output_port_type != VisualShaderNode::PORT_TYPE_MAX) { + if (check_result == 0) { + for (int j = 0; j < vsn->get_input_port_count(); j++) { + if (visual_shader->is_port_types_compatible(vsn->get_input_port_type(j), members_output_port_type)) { + check_result = 1; + break; + } + } + } + + if (check_result != 1) { + continue; + } + } + if (members_input_port_type != VisualShaderNode::PORT_TYPE_MAX) { + if (check_result == 0) { + for (int j = 0; j < vsn->get_output_port_count(); j++) { + if (visual_shader->is_port_types_compatible(vsn->get_output_port_type(j), members_input_port_type)) { + check_result = 1; + break; + } + } + } + + if (check_result != 1) { + continue; + } + } + } if ((add_options[i].func != current_func && add_options[i].func != -1) || !_is_available(add_options[i].mode)) { continue; } @@ -2588,13 +2669,25 @@ void VisualShaderEditor::_disconnection_request(const String &p_from, int p_from void VisualShaderEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) { from_node = p_from.to_int(); from_slot = p_from_slot; - _show_members_dialog(true); + VisualShaderNode::PortType input_port_type = VisualShaderNode::PORT_TYPE_MAX; + VisualShaderNode::PortType output_port_type = VisualShaderNode::PORT_TYPE_MAX; + Ref<VisualShaderNode> node = visual_shader->get_node(get_current_shader_type(), from_node); + if (node.is_valid()) { + output_port_type = node->get_output_port_type(from_slot); + } + _show_members_dialog(true, input_port_type, output_port_type); } void VisualShaderEditor::_connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position) { to_node = p_to.to_int(); to_slot = p_to_slot; - _show_members_dialog(true); + VisualShaderNode::PortType input_port_type = VisualShaderNode::PORT_TYPE_MAX; + VisualShaderNode::PortType output_port_type = VisualShaderNode::PORT_TYPE_MAX; + Ref<VisualShaderNode> node = visual_shader->get_node(get_current_shader_type(), to_node); + if (node.is_valid()) { + input_port_type = node->get_input_port_type(to_slot); + } + _show_members_dialog(true, input_port_type, output_port_type); } void VisualShaderEditor::_delete_nodes(int p_type, const List<int> &p_nodes) { @@ -3036,7 +3129,13 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { } } -void VisualShaderEditor::_show_members_dialog(bool at_mouse_pos) { +void VisualShaderEditor::_show_members_dialog(bool at_mouse_pos, VisualShaderNode::PortType p_input_port_type, VisualShaderNode::PortType p_output_port_type) { + if (members_input_port_type != p_input_port_type || members_output_port_type != p_output_port_type) { + members_input_port_type = p_input_port_type; + members_output_port_type = p_output_port_type; + _update_options_menu(); + } + if (at_mouse_pos) { saved_node_pos_dirty = true; saved_node_pos = graph->get_local_mouse_position(); @@ -3072,7 +3171,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(); } } @@ -3114,7 +3213,7 @@ void VisualShaderEditor::_notification(int p_what) { { Color background_color = EDITOR_GET("text_editor/theme/highlighting/background_color"); Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color"); - Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); + Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); Color symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color"); diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 87bab16a45..9f24c5af72 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -47,6 +47,8 @@ class VisualShaderNodePlugin : public RefCounted { protected: static void _bind_methods(); + GDVIRTUAL2RC(Object *, _create_editor, RES, Ref<VisualShaderNode>) + public: virtual Control *create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node); }; @@ -160,6 +162,8 @@ class VisualShaderEditor : public VBoxContainer { bool saved_node_pos_dirty; ConfirmationDialog *members_dialog; + VisualShaderNode::PortType members_input_port_type = VisualShaderNode::PORT_TYPE_MAX; + VisualShaderNode::PortType members_output_port_type = VisualShaderNode::PORT_TYPE_MAX; PopupMenu *popup_menu; PopupMenu *constants_submenu = nullptr; MenuButton *tools; @@ -227,7 +231,7 @@ class VisualShaderEditor : public VBoxContainer { Label *highend_label; void _tools_menu_option(int p_idx); - void _show_members_dialog(bool at_mouse_pos); + void _show_members_dialog(bool at_mouse_pos, VisualShaderNode::PortType p_input_port_type = VisualShaderNode::PORT_TYPE_MAX, VisualShaderNode::PortType p_output_port_type = VisualShaderNode::PORT_TYPE_MAX); void _update_graph(); 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/property_editor.cpp b/editor/property_editor.cpp index 7338588d56..6ea9b9dfae 100644 --- a/editor/property_editor.cpp +++ b/editor/property_editor.cpp @@ -59,42 +59,33 @@ #include "scene/scene_string_names.h" void EditorResourceConversionPlugin::_bind_methods() { - MethodInfo mi; - mi.name = "_convert"; - mi.return_val.type = Variant::OBJECT; - mi.return_val.class_name = "Resource"; - mi.return_val.hint = PROPERTY_HINT_RESOURCE_TYPE; - mi.return_val.hint_string = "Resource"; - mi.arguments.push_back(mi.return_val); - mi.arguments[0].name = "resource"; - - BIND_VMETHOD(mi) - - mi.name = "_handles"; - mi.return_val = PropertyInfo(Variant::BOOL, ""); - - BIND_VMETHOD(MethodInfo(Variant::STRING, "_converts_to")); + GDVIRTUAL_BIND(_converts_to); + GDVIRTUAL_BIND(_handles, "resource"); + GDVIRTUAL_BIND(_convert, "resource"); } String EditorResourceConversionPlugin::converts_to() const { - if (get_script_instance()) { - return get_script_instance()->call("_converts_to"); + String ret; + if (GDVIRTUAL_CALL(_converts_to, ret)) { + return ret; } return ""; } bool EditorResourceConversionPlugin::handles(const Ref<Resource> &p_resource) const { - if (get_script_instance()) { - return get_script_instance()->call("_handles", p_resource); + bool ret; + if (GDVIRTUAL_CALL(_handles, p_resource, ret)) { + return ret; } return false; } Ref<Resource> EditorResourceConversionPlugin::convert(const Ref<Resource> &p_resource) const { - if (get_script_instance()) { - return get_script_instance()->call("_convert", p_resource); + RES ret; + if (GDVIRTUAL_CALL(_convert, p_resource, ret)) { + return ret; } return Ref<Resource>(); diff --git a/editor/property_editor.h b/editor/property_editor.h index 8a587b50b0..23771b7494 100644 --- a/editor/property_editor.h +++ b/editor/property_editor.h @@ -56,6 +56,10 @@ class EditorResourceConversionPlugin : public RefCounted { protected: static void _bind_methods(); + GDVIRTUAL0RC(String, _converts_to) + GDVIRTUAL1RC(bool, _handles, RES) + GDVIRTUAL1RC(RES, _convert, RES) + public: virtual String converts_to() const; virtual bool handles(const Ref<Resource> &p_resource) const; 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..2ec4a088a2 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()) { @@ -3163,8 +3163,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..387a35acbb 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(); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 83b0203f32..e7ba80677d 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -1206,11 +1206,10 @@ SceneTreeEditor::SceneTreeEditor(bool p_label, bool p_can_rename, bool p_can_ope } tree->connect("cell_selected", callable_mp(this, &SceneTreeEditor::_selected_changed)); - tree->connect("item_edited", callable_mp(this, &SceneTreeEditor::_renamed), varray(), CONNECT_DEFERRED); + tree->connect("item_edited", callable_mp(this, &SceneTreeEditor::_renamed)); tree->connect("multi_selected", callable_mp(this, &SceneTreeEditor::_cell_multi_selected)); tree->connect("button_pressed", callable_mp(this, &SceneTreeEditor::_cell_button_pressed)); tree->connect("nothing_selected", callable_mp(this, &SceneTreeEditor::_deselect_items)); - //tree->connect("item_edited", this,"_renamed",Vector<Variant>(),true); error = memnew(AcceptDialog); add_child(error); diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index ee100ca938..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; @@ -250,6 +250,8 @@ void EditorSettingsDialog::_update_shortcuts() { sections["Common"] = common_section; common_section->set_text(0, TTR("Common")); + common_section->set_selectable(0, false); + common_section->set_selectable(1, false); if (collapsed.has("Common")) { common_section->set_collapsed(collapsed["Common"]); } @@ -343,14 +345,16 @@ void EditorSettingsDialog::_update_shortcuts() { String item_name = section_name.capitalize(); section->set_text(0, item_name); + section->set_selectable(0, false); + section->set_selectable(1, false); + section->set_custom_bg_color(0, shortcuts->get_theme_color(SNAME("prop_subsection"), SNAME("Editor"))); + section->set_custom_bg_color(1, shortcuts->get_theme_color(SNAME("prop_subsection"), SNAME("Editor"))); if (collapsed.has(item_name)) { section->set_collapsed(collapsed[item_name]); } sections[section_name] = section; - section->set_custom_bg_color(0, shortcuts->get_theme_color(SNAME("prop_subsection"), SNAME("Editor"))); - section->set_custom_bg_color(1, shortcuts->get_theme_color(SNAME("prop_subsection"), SNAME("Editor"))); } // Don't match unassigned shortcuts when searching for assigned keys in search results. @@ -538,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 6764332f16..a90dc121f3 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(); @@ -2203,7 +2201,7 @@ bool Main::start() { //standard helpers that can be changed from main config String stretch_mode = GLOBAL_DEF_BASIC("display/window/stretch/mode", "disabled"); - String stretch_aspect = GLOBAL_DEF_BASIC("display/window/stretch/aspect", "ignore"); + String stretch_aspect = GLOBAL_DEF_BASIC("display/window/stretch/aspect", "keep"); Size2i stretch_size = Size2i(GLOBAL_DEF_BASIC("display/window/size/width", 0), GLOBAL_DEF_BASIC("display/window/size/height", 0)); @@ -2242,6 +2240,10 @@ bool Main::start() { DisplayServer::get_singleton()->window_set_title(appname); #endif + // Define a very small minimum window size to prevent bugs such as GH-37242. + // It can still be overridden by the user in a script. + DisplayServer::get_singleton()->window_set_min_size(Size2i(64, 64)); + bool snap_controls = GLOBAL_DEF("gui/common/snap_controls_to_pixels", true); sml->get_root()->set_snap_controls_to_pixels(snap_controls); @@ -2262,7 +2264,7 @@ bool Main::start() { "display/window/stretch/mode", PROPERTY_HINT_ENUM, "disabled,canvas_items,viewport")); - GLOBAL_DEF_BASIC("display/window/stretch/aspect", "ignore"); + GLOBAL_DEF_BASIC("display/window/stretch/aspect", "keep"); ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/aspect", PropertyInfo(Variant::STRING, "display/window/stretch/aspect", diff --git a/methods.py b/methods.py index 970bd10aa3..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 @@ -934,9 +935,12 @@ def show_progress(env): def progress_finish(target, source, env): nonlocal node_count, progressor - with open(node_count_fname, "w") as f: - f.write("%d\n" % node_count) - progressor.delete(progressor.file_list()) + try: + with open(node_count_fname, "w") as f: + f.write("%d\n" % node_count) + progressor.delete(progressor.file_list()) + except Exception: + pass try: with open(node_count_fname) as f: 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.cpp b/modules/bullet/space_bullet.cpp index c6d835742d..a9a811c445 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -945,6 +945,11 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p G_TO_B(p_from, body_transform); UNSCALE_BT_BASIS(body_transform); + if (!p_body->get_kinematic_utilities()) { + p_body->init_kinematic_utilities(); + p_body->reload_kinematic_shapes(); + } + btVector3 initial_recover_motion(0, 0, 0); { /// Phase one - multi shapes depenetration using margin for (int t(RECOVERING_MOVEMENT_CYCLES); 0 < t; --t) { @@ -958,6 +963,9 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p btVector3 motion; G_TO_B(p_motion, motion); + real_t total_length = motion.length(); + real_t unsafe_fraction = 1.0; + real_t safe_fraction = 1.0; { // Phase two - sweep test, from a secure position without margin @@ -1007,6 +1015,15 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p dynamicsWorld->convexSweepTest(convex_shape_test, shape_world_from, shape_world_to, btResult, dynamicsWorld->getDispatchInfo().m_allowedCcdPenetration); if (btResult.hasHit()) { + if (total_length > CMP_EPSILON) { + real_t hit_fraction = btResult.m_closestHitFraction * motion.length() / total_length; + if (hit_fraction < unsafe_fraction) { + unsafe_fraction = hit_fraction; + real_t margin = p_body->get_kinematic_utilities()->safe_margin; + safe_fraction = MAX(hit_fraction - (1 - ((total_length - margin) / total_length)), 0); + } + } + /// Since for each sweep test I fix the motion of new shapes in base the recover result, /// if another shape will hit something it means that has a deepest penetration respect the previous shape motion *= btResult.m_closestHitFraction; @@ -1043,6 +1060,9 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p r_result->collider_id = collisionObject->get_instance_id(); r_result->collider_shape = r_recover_result.other_compound_shape_index; r_result->collision_local_shape = r_recover_result.local_shape_most_recovered; + r_result->collision_depth = Math::abs(r_recover_result.penetration_distance); + r_result->collision_safe_fraction = safe_fraction; + r_result->collision_unsafe_fraction = unsafe_fraction; #if debug_test_motion Vector3 sup_line2; @@ -1067,6 +1087,11 @@ int SpaceBullet::test_ray_separation(RigidBodyBullet *p_body, const Transform3D G_TO_B(p_transform, body_transform); UNSCALE_BT_BASIS(body_transform); + if (!p_body->get_kinematic_utilities()) { + p_body->init_kinematic_utilities(); + p_body->reload_kinematic_shapes(); + } + btVector3 recover_motion(0, 0, 0); int rays_found = 0; 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/fbx/data/fbx_material.cpp b/modules/fbx/data/fbx_material.cpp index fb6c67f7b9..86baec4244 100644 --- a/modules/fbx/data/fbx_material.cpp +++ b/modules/fbx/data/fbx_material.cpp @@ -31,7 +31,7 @@ #include "fbx_material.h" // FIXME: Shouldn't depend on core_bind.h! Use DirAccessRef like the rest of -// the engine instead of _Directory. +// the engine instead of core_bind::Directory. #include "core/core_bind.h" #include "scene/resources/material.h" #include "scene/resources/texture.h" @@ -55,7 +55,7 @@ void FBXMaterial::add_search_string(String p_filename, String p_current_director } String find_file(const String &p_base, const String &p_file_to_find) { - _Directory dir; + core_bind::Directory dir; dir.open(p_base); dir.list_dir_begin(); @@ -84,7 +84,7 @@ String find_file(const String &p_base, const String &p_file_to_find) { // fbx will not give us good path information and let's not regex them to fix them // no relative paths are in fbx generally they have a rel field but it's populated incorrectly by the SDK. String FBXMaterial::find_texture_path_by_filename(const String p_filename, const String p_current_directory) { - _Directory dir; + core_bind::Directory dir; Vector<String> paths; add_search_string(p_filename, p_current_directory, "", paths); add_search_string(p_filename, p_current_directory, "texture", paths); 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/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/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/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp index df0f29277e..2d9b45cb07 100644 --- a/modules/gdnative/nativescript/api_generator.cpp +++ b/modules/gdnative/nativescript/api_generator.cpp @@ -242,13 +242,9 @@ List<ClassAPI> generate_c_api_classes() { class_api.class_name = class_name; class_api.super_class_name = ClassDB::get_parent_class(class_name); { - String name = class_name; - if (name.begins_with("_")) { - name.remove(0); - } - class_api.is_singleton = Engine::get_singleton()->has_singleton(name); + class_api.is_singleton = Engine::get_singleton()->has_singleton(class_name); if (class_api.is_singleton) { - class_api.singleton_name = name; + class_api.singleton_name = class_name; } } class_api.is_instantiable = !class_api.is_singleton && ClassDB::can_instantiate(class_name); 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..195c32c3f8 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_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 3441233811..8b9bd2659d 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -45,7 +45,7 @@ static bool _is_bin_symbol(char32_t c) { return (c == '0' || c == '1'); } -Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) { +Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) { Dictionary color_map; Type next_type = NONE; @@ -459,11 +459,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { List<StringName> types; ClassDB::get_class_list(&types); for (const StringName &E : types) { - String n = E; - if (n.begins_with("_")) { - n = n.substr(1, n.length()); - } - keywords[n] = types_color; + keywords[E] = types_color; } /* User types. */ diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index d07c182aa6..fabd64dab8 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -80,7 +80,7 @@ private: public: virtual void _update_cache() override; - virtual Dictionary _get_line_syntax_highlighting(int p_line) override; + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override; virtual String _get_name() const override; virtual Array _get_supported_languages() const override; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 8957b00a1b..4589cf1a36 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1644,16 +1644,11 @@ void GDScriptLanguage::init() { List<StringName> class_list; ClassDB::get_class_list(&class_list); for (const StringName &n : class_list) { - String s = String(n); - if (s.begins_with("_")) { - s = s.substr(1, s.length()); - } - - if (globals.has(s)) { + if (globals.has(n)) { continue; } Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n)); - _add_global(s, nc); + _add_global(n, nc); } //populate singletons diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index ab37e54cf1..6b7403d854 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -30,6 +30,7 @@ #include "gdscript_analyzer.h" +#include "core/config/engine.h" #include "core/config/project_settings.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" @@ -112,11 +113,10 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native type.is_meta_type = true; List<StringName> enum_values; - StringName real_native_name = GDScriptParser::get_real_class_name(p_native_class); - ClassDB::get_enum_constants(real_native_name, p_enum_name, &enum_values); + ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values); for (const StringName &E : enum_values) { - type.enum_values[E] = ClassDB::get_integer_constant(real_native_name, E); + type.enum_values[E] = ClassDB::get_integer_constant(p_native_class, E); } return type; @@ -229,7 +229,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } - } else if (class_exists(name) && ClassDB::can_instantiate(GDScriptParser::get_real_class_name(name))) { + } else if (class_exists(name) && ClassDB::can_instantiate(name)) { base.kind = GDScriptParser::DataType::NATIVE; base.native_type = name; } else { @@ -406,7 +406,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return GDScriptParser::DataType(); } result = ref->get_parser()->head->get_datatype(); - } else if (ClassDB::has_enum(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), first)) { + } else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) { // Native enum in current class. result = make_native_enum_type(parser->current_class->base_type.native_type, first); } else { @@ -433,8 +433,28 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type case GDScriptParser::ClassNode::Member::CONSTANT: if (member.constant->get_datatype().is_meta_type) { result = member.constant->get_datatype(); + result.is_meta_type = false; found = true; break; + } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) { + Ref<GDScript> gdscript = member.constant->initializer->reduced_value; + if (gdscript.is_valid()) { + Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_path()); + if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { + push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_path()), p_type); + return GDScriptParser::DataType(); + } + result = ref->get_parser()->head->get_datatype(); + result.is_meta_type = false; + } else { + Ref<GDScript> script = member.constant->initializer->reduced_value; + result.kind = GDScriptParser::DataType::SCRIPT; + result.builtin_type = Variant::OBJECT; + result.script_type = script; + result.script_path = script->get_path(); + result.native_type = script->get_instance_base_type(); + } + break; } [[fallthrough]]; default: @@ -469,7 +489,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } else if (result.kind == GDScriptParser::DataType::NATIVE) { // Only enums allowed for native. - if (ClassDB::has_enum(GDScriptParser::get_real_class_name(result.native_type), p_type->type_chain[1]->name)) { + if (ClassDB::has_enum(result.native_type, p_type->type_chain[1]->name)) { if (p_type->type_chain.size() > 2) { push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]); } else { @@ -2130,6 +2150,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee); + } else if (!is_self && base_type.is_meta_type && !is_static) { + base_type.is_meta_type = false; // For `to_string()`. + push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call->callee); } else if (is_self && !is_static && !lambda_stack.is_empty()) { push_error(vformat(R"*(Cannot call non-static function "%s()" from a lambda function.)*", p_call->function_name), p_call->callee); } @@ -2252,7 +2275,7 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) result.native_type = "Node"; result.builtin_type = Variant::OBJECT; - if (!ClassDB::is_parent_class(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) { + if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, result.native_type)) { push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node); } else if (!lambda_stack.is_empty()) { push_error(R"*(Cannot use shorthand "get_node()" notation ("$") inside a lambda. Use a captured variable instead.)*", p_get_node); @@ -2393,6 +2416,11 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod resolve_function_signature(member.function); p_identifier->set_datatype(make_callable_type(member.function->info)); break; + case GDScriptParser::ClassNode::Member::CLASS: + // For out-of-order resolution: + resolve_class_interface(member.m_class); + p_identifier->set_datatype(member.m_class->get_datatype()); + break; default: break; // Type already set. } @@ -2421,7 +2449,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } // Check native members. - const StringName &native = GDScriptParser::get_real_class_name(base.native_type); + const StringName &native = base.native_type; if (class_exists(native)) { PropertyInfo prop_info; @@ -3202,7 +3230,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo return result; } -bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { +bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { r_static = false; r_vararg = false; r_default_arg_count = 0; @@ -3221,14 +3249,16 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD for (const MethodInfo &E : methods) { if (E.name == p_function) { - return function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + r_static = Variant::is_builtin_method_static(p_base_type.builtin_type, function_name); + return true; } } return false; } - bool is_constructor = p_base_type.is_meta_type && p_function == "new"; + bool is_constructor = (p_base_type.is_meta_type || (p_source->callee && p_source->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_function == StaticCString::create("new"); if (is_constructor) { function_name = "_init"; r_static = true; @@ -3258,6 +3288,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD } } r_return_type = found_function->get_datatype(); + r_return_type.is_meta_type = false; r_return_type.is_coroutine = found_function->is_coroutine; return true; @@ -3303,11 +3334,13 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD return true; } - StringName real_native = GDScriptParser::get_real_class_name(base_native); - MethodInfo info; - if (ClassDB::get_method_info(real_native, function_name, &info)) { - return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + if (ClassDB::get_method_info(base_native, function_name, &info)) { + bool valid = function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + if (valid && Engine::get_singleton()->has_singleton(base_native)) { + r_static = true; + } + return valid; } return false; @@ -3398,24 +3431,23 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con StringName parent = base_native; while (parent != StringName()) { - StringName real_class_name = GDScriptParser::get_real_class_name(parent); - if (ClassDB::has_method(real_class_name, name, true)) { + if (ClassDB::has_method(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent); return true; - } else if (ClassDB::has_signal(real_class_name, name, true)) { + } else if (ClassDB::has_signal(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "signal", parent); return true; - } else if (ClassDB::has_property(real_class_name, name, true)) { + } else if (ClassDB::has_property(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "property", parent); return true; - } else if (ClassDB::has_integer_constant(real_class_name, name, true)) { + } else if (ClassDB::has_integer_constant(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "constant", parent); return true; - } else if (ClassDB::has_enum(real_class_name, name, true)) { + } else if (ClassDB::has_enum(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "enum", parent); return true; } - parent = ClassDB::get_parent_class(real_class_name); + parent = ClassDB::get_parent_class(parent); } return false; @@ -3560,16 +3592,12 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ break; // Already solved before. } - // Get underscore-prefixed version for some classes. - src_native = GDScriptParser::get_real_class_name(src_native); - switch (p_target.kind) { case GDScriptParser::DataType::NATIVE: { if (p_target.is_meta_type) { return ClassDB::is_parent_class(src_native, GDScriptNativeClass::get_class_static()); } - StringName tgt_native = GDScriptParser::get_real_class_name(p_target.native_type); - return ClassDB::is_parent_class(src_native, tgt_native); + return ClassDB::is_parent_class(src_native, p_target.native_type); } case GDScriptParser::DataType::SCRIPT: if (p_target.is_meta_type) { @@ -3618,8 +3646,7 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { } bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { - StringName real_name = GDScriptParser::get_real_class_name(p_class); - return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name); + return ClassDB::class_exists(p_class) && ClassDB::is_class_exposed(p_class); } Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 8cd3fcf837..32bf049fa1 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -99,7 +99,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const; GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const; GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); - bool get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool get_function_signature(GDScriptParser::CallNode *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 1a844bf241..07f50d14dc 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -200,7 +200,9 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro if (singleton->full_gdscript_cache.has(p_path)) { return singleton->full_gdscript_cache[p_path]; } + Ref<GDScript> script = get_shallow_script(p_path); + ERR_FAIL_COND_V(script.is_null(), Ref<GDScript>()); r_error = script->load_source_code(p_path); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index fe827a5b72..ddf4f281b4 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -126,8 +126,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D names.pop_back(); } result.kind = GDScriptDataType::GDSCRIPT; - result.script_type_ref = script; - result.script_type = result.script_type_ref.ptr(); + result.script_type = script.ptr(); result.native_type = script->get_instance_base_type(); } else { result.kind = GDScriptDataType::GDSCRIPT; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 73661855b4..372a726d71 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -912,7 +912,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } } break; case GDScriptParser::DataType::NATIVE: { - StringName type = GDScriptParser::get_real_class_name(base_type.native_type); + StringName type = base_type.native_type; if (!ClassDB::class_exists(type)) { return; } @@ -1326,10 +1326,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, native_type.kind = GDScriptParser::DataType::NATIVE; native_type.native_type = native_type.script_type->get_instance_base_type(); if (!ClassDB::class_exists(native_type.native_type)) { - native_type.native_type = String("_") + native_type.native_type; - if (!ClassDB::class_exists(native_type.native_type)) { - native_type.kind = GDScriptParser::DataType::UNRESOLVED; - } + native_type.kind = GDScriptParser::DataType::UNRESOLVED; } } } @@ -1765,9 +1762,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, base_type = GDScriptParser::DataType(); break; } - StringName real_native = GDScriptParser::get_real_class_name(base_type.native_type); MethodInfo info; - if (ClassDB::get_method_info(real_native, p_context.current_function->identifier->name, &info)) { + if (ClassDB::get_method_info(base_type.native_type, p_context.current_function->identifier->name, &info)) { for (const PropertyInfo &E : info.arguments) { if (E.name == p_identifier) { r_type = _type_from_property(E); @@ -1836,13 +1832,12 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } // Check ClassDB. - StringName class_name = GDScriptParser::get_real_class_name(p_identifier); - if (ClassDB::class_exists(class_name) && ClassDB::is_class_exposed(class_name)) { + if (ClassDB::class_exists(p_identifier) && ClassDB::is_class_exposed(p_identifier)) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::NATIVE; r_type.type.native_type = p_identifier; r_type.type.is_constant = true; - r_type.type.is_meta_type = !Engine::get_singleton()->has_singleton(class_name); + r_type.type.is_meta_type = !Engine::get_singleton()->has_singleton(p_identifier); r_type.value = Variant(); } @@ -1951,7 +1946,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); + StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { return false; } @@ -2113,11 +2108,10 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex } } break; case GDScriptParser::DataType::NATIVE: { - StringName native = GDScriptParser::get_real_class_name(base_type.native_type); - if (!ClassDB::class_exists(native)) { + if (!ClassDB::class_exists(base_type.native_type)) { return false; } - MethodBind *mb = ClassDB::get_method(native, p_method); + MethodBind *mb = ClassDB::get_method(base_type.native_type, p_method); if (mb) { r_type = _type_from_property(mb->get_return_info()); return true; @@ -2209,7 +2203,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c base_type = base_type.class_type->base_type; } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); + StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; @@ -2592,7 +2586,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c break; } - StringName class_name = GDScriptParser::get_real_class_name(native_type.native_type); + StringName class_name = native_type.native_type; if (!ClassDB::class_exists(class_name)) { break; } @@ -2821,7 +2815,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); + StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; @@ -2873,11 +2867,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co StringName parent = ClassDB::get_parent_class(class_name); if (parent != StringName()) { - if (String(parent).begins_with("_")) { - base_type.native_type = String(parent).substr(1); - } else { - base_type.native_type = parent; - } + base_type.native_type = parent; } else { base_type.kind = GDScriptParser::DataType::UNRESOLVED; } @@ -2931,18 +2921,11 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } ::Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { - //before parsing, try the usual stuff + // Before parsing, try the usual stuff if (ClassDB::class_exists(p_symbol)) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; r_result.class_name = p_symbol; return OK; - } else { - String under_prefix = "_" + p_symbol; - if (ClassDB::class_exists(under_prefix)) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; - r_result.class_name = p_symbol; - return OK; - } } for (int i = 0; i < Variant::VARIANT_MAX; i++) { diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 9e5ef0f632..87d8c03494 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -111,11 +111,7 @@ public: } if (!ClassDB::is_parent_class(obj->get_class_name(), native_type)) { - // Try with underscore prefix - StringName underscore_native_type = "_" + native_type; - if (!ClassDB::is_parent_class(obj->get_class_name(), underscore_native_type)) { - return false; - } + return false; } return true; } break; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 8c3bb1ea57..029e30080b 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -94,43 +94,8 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { return Variant::VARIANT_MAX; } -// TODO: Move this to a central location (maybe core?). -static HashMap<StringName, StringName> underscore_map; -static const char *underscore_classes[] = { - "ClassDB", - "Directory", - "Engine", - "File", - "Geometry", - "GodotSharp", - "JSON", - "Marshalls", - "Mutex", - "OS", - "ResourceLoader", - "ResourceSaver", - "Semaphore", - "Thread", - "VisualScriptEditor", - nullptr, -}; -StringName GDScriptParser::get_real_class_name(const StringName &p_source) { - if (underscore_map.is_empty()) { - const char **class_name = underscore_classes; - while (*class_name != nullptr) { - underscore_map[*class_name] = String("_") + *class_name; - class_name++; - } - } - if (underscore_map.has(p_source)) { - return underscore_map[p_source]; - } - return p_source; -} - void GDScriptParser::cleanup() { builtin_types.clear(); - underscore_map.clear(); } void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { @@ -1152,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); @@ -2401,6 +2365,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) { @@ -2986,7 +2953,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; @@ -3363,10 +3330,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); break; case GDScriptParser::DataType::NATIVE: - if (ClassDB::is_parent_class(get_real_class_name(export_type.native_type), "Resource")) { + if (ClassDB::is_parent_class(export_type.native_type, "Resource")) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = get_real_class_name(export_type.native_type); + variable->export_info.hint_string = export_type.native_type; } else { push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); return false; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 6a227a55e5..0bce8d7ddd 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1399,7 +1399,6 @@ public: ClassNode *get_tree() const { return head; } bool is_tool() const { return _is_tool; } static Variant::Type get_builtin_type(const StringName &p_type); - static StringName get_real_class_name(const StringName &p_source); CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 8a261a88e3..882256b7e3 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -174,7 +174,7 @@ void (*type_init_function_table[])(Variant *) = { &VariantInitializer<StringName>::init, // STRING_NAME. &VariantInitializer<NodePath>::init, // NODE_PATH. &VariantInitializer<RID>::init, // RID. - &VariantTypeAdjust<Object *>::adjust, // OBJECT. + &VariantInitializer<Object *>::init, // OBJECT. &VariantInitializer<Callable>::init, // CALLABLE. &VariantInitializer<Signal>::init, // SIGNAL. &VariantInitializer<Dictionary>::init, // DICTIONARY. 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/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.gd b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.gd new file mode 100644 index 0000000000..ac66b78220 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.gd @@ -0,0 +1,3 @@ +func test(): + print(Color.html_is_valid("00ffff")) + print("OK") 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 new file mode 100644 index 0000000000..ad6beeb646 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out @@ -0,0 +1,3 @@ +GDTEST_OK +true +OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd new file mode 100644 index 0000000000..fb0ace6a90 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd @@ -0,0 +1,5 @@ +func test(): + pass + +func something(): + return "OK" diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.gd b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.gd new file mode 100644 index 0000000000..4f4b7a4897 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.gd @@ -0,0 +1,11 @@ +class InnerClass: + var val := "OK" + static func create_instance() -> InnerClass: + return new() + +func create_inner_instance() -> InnerClass: + return InnerClass.create_instance() + +func test(): + var instance = create_inner_instance() + print(instance.val) diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.out b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.out new file mode 100644 index 0000000000..1ccb591560 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.out @@ -0,0 +1,2 @@ +GDTEST_OK +OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd new file mode 100644 index 0000000000..5f73064cc0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd @@ -0,0 +1,5 @@ +const preloaded : GDScript = preload("gdscript_to_preload.gd") + +func test(): + var preloaded_instance: preloaded = preloaded.new() + print(preloaded_instance.something()) diff --git a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.out b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.out new file mode 100644 index 0000000000..1ccb591560 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.out @@ -0,0 +1,2 @@ +GDTEST_OK +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/features/str_preserves_case.gd b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd new file mode 100644 index 0000000000..ad1694de81 --- /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/gltf_document.h b/modules/gltf/gltf_document.h index 3e8ea19045..7a826897a9 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -346,8 +346,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/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index f101c43e89..2331a12d0f 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -790,7 +790,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/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index 2cc974322d..fb741f6266 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() { diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h index ce001fc418..5dd88779f8 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: diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp index 590b95ab79..12bb0e5b44 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_ 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/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 632f7d61cc..7fdef8ff45 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -3130,8 +3130,18 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; case Variant::CALLABLE: + ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_Callable, false, + "Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_Callable) + "'."); + ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false, + "Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value."); + r_iarg.default_argument = "default"; + break; case Variant::SIGNAL: - CRASH_NOW_MSG("Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value."); + ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_Signal, false, + "Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_Signal) + "'."); + ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false, + "Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value."); + r_iarg.default_argument = "default"; break; default: CRASH_NOW_MSG("Unexpected Variant type: " + itos(p_val.get_type())); diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index a649181b20..8a85a1acbd 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -536,6 +536,8 @@ class BindingsGenerator { StringName type_Object = StaticCString::create("Object"); StringName type_RefCounted = StaticCString::create("RefCounted"); StringName type_RID = StaticCString::create("RID"); + StringName type_Callable = StaticCString::create("Callable"); + StringName type_Signal = StaticCString::create("Signal"); StringName type_String = StaticCString::create("String"); StringName type_StringName = StaticCString::create("StringName"); StringName type_NodePath = StaticCString::create("NodePath"); diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 8164f459ca..6692a6efec 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -241,7 +241,7 @@ MonoBoolean godot_icall_Internal_IsAssembliesReloadingNeeded() { void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) { #ifdef GD_MONO_HOT_RELOAD - _GodotSharp::get_singleton()->call_deferred(SNAME("_reload_assemblies"), (bool)p_soft_reload); + mono_bind::GodotSharp::get_singleton()->call_deferred(SNAME("_reload_assemblies"), (bool)p_soft_reload); #endif } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index d9665cbf2b..e619ad74e9 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -64,7 +64,7 @@ namespace Godot /// <summary> /// If the string is a path to a file, return the path to the file without the extension. /// </summary> - public static string BaseName(this string instance) + public static string GetBaseName(this string instance) { int index = instance.LastIndexOf('.'); @@ -339,7 +339,7 @@ namespace Godot /// <summary> /// If the string is a path to a file, return the extension. /// </summary> - public static string Extension(this string instance) + public static string GetExtension(this string instance) { int pos = instance.FindLast("."); @@ -575,7 +575,7 @@ namespace Godot /// <summary> /// If the string is a path to a file or directory, return <see langword="true"/> if the path is absolute. /// </summary> - public static bool IsAbsPath(this string instance) + public static bool IsAbsolutePath(this string instance) { if (string.IsNullOrEmpty(instance)) return false; @@ -590,7 +590,7 @@ namespace Godot /// </summary> public static bool IsRelPath(this string instance) { - return !IsAbsPath(instance); + return !IsAbsolutePath(instance); } /// <summary> @@ -1103,6 +1103,17 @@ 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> 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.cpp b/modules/mono/mono_gd/gd_mono.cpp index 299344bb93..1b1349a3a3 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -73,7 +73,7 @@ #endif // TODO: -// This has turn into a gigantic mess. There's too much going on here. Too much #ifdef as well. +// This has turned into a gigantic mess. There's too much going on here. Too much #ifdef as well. // It's just painful to read... It needs to be re-structured. Please, clean this up, future me. GDMono *GDMono::singleton = nullptr; @@ -1335,23 +1335,25 @@ GDMono::~GDMono() { singleton = nullptr; } -_GodotSharp *_GodotSharp::singleton = nullptr; +namespace mono_bind { -void _GodotSharp::attach_thread() { +GodotSharp *GodotSharp::singleton = nullptr; + +void GodotSharp::attach_thread() { GDMonoUtils::attach_current_thread(); } -void _GodotSharp::detach_thread() { +void GodotSharp::detach_thread() { GDMonoUtils::detach_current_thread(); } -int32_t _GodotSharp::get_domain_id() { +int32_t GodotSharp::get_domain_id() { MonoDomain *domain = mono_domain_get(); ERR_FAIL_NULL_V(domain, -1); return mono_domain_get_id(domain); } -int32_t _GodotSharp::get_scripts_domain_id() { +int32_t GodotSharp::get_scripts_domain_id() { ERR_FAIL_NULL_V_MSG(GDMono::get_singleton(), -1, "The Mono runtime is not initialized"); MonoDomain *domain = GDMono::get_singleton()->get_scripts_domain(); @@ -1359,21 +1361,21 @@ int32_t _GodotSharp::get_scripts_domain_id() { return mono_domain_get_id(domain); } -bool _GodotSharp::is_scripts_domain_loaded() { +bool GodotSharp::is_scripts_domain_loaded() { return GDMono::get_singleton() != nullptr && GDMono::get_singleton()->is_runtime_initialized() && GDMono::get_singleton()->get_scripts_domain() != nullptr; } -bool _GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) { +bool GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) { return is_domain_finalizing_for_unload(p_domain_id); } -bool _GodotSharp::is_domain_finalizing_for_unload(int32_t p_domain_id) { +bool GodotSharp::is_domain_finalizing_for_unload(int32_t p_domain_id) { return is_domain_finalizing_for_unload(mono_domain_get_by_id(p_domain_id)); } -bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) { +bool GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) { GDMono *gd_mono = GDMono::get_singleton(); ERR_FAIL_COND_V_MSG(!gd_mono || !gd_mono->is_runtime_initialized(), @@ -1388,15 +1390,15 @@ bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) { return mono_domain_is_unloading(p_domain); } -bool _GodotSharp::is_runtime_shutting_down() { +bool GodotSharp::is_runtime_shutting_down() { return mono_runtime_is_shutting_down(); } -bool _GodotSharp::is_runtime_initialized() { +bool GodotSharp::is_runtime_initialized() { return GDMono::get_singleton() != nullptr && GDMono::get_singleton()->is_runtime_initialized(); } -void _GodotSharp::_reload_assemblies(bool p_soft_reload) { +void GodotSharp::_reload_assemblies(bool p_soft_reload) { #ifdef GD_MONO_HOT_RELOAD CRASH_COND(CSharpLanguage::get_singleton() == nullptr); // This method may be called more than once with `call_deferred`, so we need to check @@ -1407,24 +1409,26 @@ void _GodotSharp::_reload_assemblies(bool p_soft_reload) { #endif } -void _GodotSharp::_bind_methods() { - ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread); - ClassDB::bind_method(D_METHOD("detach_thread"), &_GodotSharp::detach_thread); +void GodotSharp::_bind_methods() { + ClassDB::bind_method(D_METHOD("attach_thread"), &GodotSharp::attach_thread); + ClassDB::bind_method(D_METHOD("detach_thread"), &GodotSharp::detach_thread); - ClassDB::bind_method(D_METHOD("get_domain_id"), &_GodotSharp::get_domain_id); - ClassDB::bind_method(D_METHOD("get_scripts_domain_id"), &_GodotSharp::get_scripts_domain_id); - ClassDB::bind_method(D_METHOD("is_scripts_domain_loaded"), &_GodotSharp::is_scripts_domain_loaded); - ClassDB::bind_method(D_METHOD("is_domain_finalizing_for_unload", "domain_id"), &_GodotSharp::_is_domain_finalizing_for_unload); + ClassDB::bind_method(D_METHOD("get_domain_id"), &GodotSharp::get_domain_id); + ClassDB::bind_method(D_METHOD("get_scripts_domain_id"), &GodotSharp::get_scripts_domain_id); + ClassDB::bind_method(D_METHOD("is_scripts_domain_loaded"), &GodotSharp::is_scripts_domain_loaded); + ClassDB::bind_method(D_METHOD("is_domain_finalizing_for_unload", "domain_id"), &GodotSharp::_is_domain_finalizing_for_unload); - ClassDB::bind_method(D_METHOD("is_runtime_shutting_down"), &_GodotSharp::is_runtime_shutting_down); - ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &_GodotSharp::is_runtime_initialized); - ClassDB::bind_method(D_METHOD("_reload_assemblies"), &_GodotSharp::_reload_assemblies); + ClassDB::bind_method(D_METHOD("is_runtime_shutting_down"), &GodotSharp::is_runtime_shutting_down); + ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &GodotSharp::is_runtime_initialized); + ClassDB::bind_method(D_METHOD("_reload_assemblies"), &GodotSharp::_reload_assemblies); } -_GodotSharp::_GodotSharp() { +GodotSharp::GodotSharp() { singleton = this; } -_GodotSharp::~_GodotSharp() { +GodotSharp::~GodotSharp() { singleton = nullptr; } + +} // namespace mono_bind diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 5accc21f8e..4170e5053f 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -293,8 +293,10 @@ public: gdmono::ScopeExitDomainUnload __gdmono__scope__exit__domain__unload__(m_mono_domain); \ (void)__gdmono__scope__exit__domain__unload__; -class _GodotSharp : public Object { - GDCLASS(_GodotSharp, Object); +namespace mono_bind { + +class GodotSharp : public Object { + GDCLASS(GodotSharp, Object); friend class GDMono; @@ -303,11 +305,11 @@ class _GodotSharp : public Object { void _reload_assemblies(bool p_soft_reload); protected: - static _GodotSharp *singleton; + static GodotSharp *singleton; static void _bind_methods(); public: - static _GodotSharp *get_singleton() { return singleton; } + static GodotSharp *get_singleton() { return singleton; } void attach_thread(); void detach_thread(); @@ -323,8 +325,10 @@ public: bool is_runtime_shutting_down(); bool is_runtime_initialized(); - _GodotSharp(); - ~_GodotSharp(); + GodotSharp(); + ~GodotSharp(); }; +} // namespace mono_bind + #endif // GD_MONO_H 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/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index 9f2977bfa2..c9789bf270 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -1686,7 +1686,9 @@ Callable managed_to_callable(const M_Callable &p_managed_callable) { Object *target = p_managed_callable.target ? unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_managed_callable.target)) : nullptr; - StringName *method_ptr = unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_callable.method_string_name)); + StringName *method_ptr = p_managed_callable.method_string_name ? + unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_callable.method_string_name)) : + nullptr; StringName method = method_ptr ? *method_ptr : StringName(); return Callable(target, method); } @@ -1732,7 +1734,9 @@ Signal managed_to_signal_info(const M_SignalInfo &p_managed_signal) { Object *owner = p_managed_signal.owner ? unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_managed_signal.owner)) : nullptr; - StringName *name_ptr = unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_signal.name_string_name)); + StringName *name_ptr = p_managed_signal.name_string_name ? + unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_signal.name_string_name)) : + nullptr; StringName name = name_ptr ? *name_ptr : StringName(); return Signal(owner, name); } diff --git a/modules/mono/register_types.cpp b/modules/mono/register_types.cpp index 2ba89eac55..98e83335e9 100644 --- a/modules/mono/register_types.cpp +++ b/modules/mono/register_types.cpp @@ -38,15 +38,15 @@ CSharpLanguage *script_language_cs = nullptr; Ref<ResourceFormatLoaderCSharpScript> resource_loader_cs; Ref<ResourceFormatSaverCSharpScript> resource_saver_cs; -_GodotSharp *_godotsharp = nullptr; +mono_bind::GodotSharp *_godotsharp = nullptr; void register_mono_types() { GDREGISTER_CLASS(CSharpScript); - _godotsharp = memnew(_GodotSharp); + _godotsharp = memnew(mono_bind::GodotSharp); - GDREGISTER_CLASS(_GodotSharp); - Engine::get_singleton()->add_singleton(Engine::Singleton("GodotSharp", _GodotSharp::get_singleton())); + GDREGISTER_CLASS(mono_bind::GodotSharp); + Engine::get_singleton()->add_singleton(Engine::Singleton("GodotSharp", mono_bind::GodotSharp::get_singleton())); script_language_cs = memnew(CSharpLanguage); script_language_cs->set_language_index(ScriptServer::get_language_count()); 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/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp index 768b419348..3a938200e9 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp @@ -32,13 +32,15 @@ #include "core/io/file_access.h" -void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) { - ERR_FAIL_COND(!active); +int AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) { + ERR_FAIL_COND_V(!active, 0); int todo = p_frames; int start_buffer = 0; + int frames_mixed_this_step = p_frames; + while (todo && active) { float *buffer = (float *)p_buffer; if (start_buffer > 0) { @@ -64,6 +66,7 @@ void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_fra // 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); } @@ -72,6 +75,7 @@ void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_fra } } } + return frames_mixed_this_step; } float AudioStreamPlaybackOGGVorbis::get_stream_sampling_rate() { diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.h b/modules/stb_vorbis/audio_stream_ogg_vorbis.h index 2bd70a2722..756c241d1f 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.h +++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.h @@ -52,7 +52,7 @@ class AudioStreamPlaybackOGGVorbis : public AudioStreamPlaybackResampled { Ref<AudioStreamOGGVorbis> vorbis_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: 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..78a87be971 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; } @@ -497,7 +695,7 @@ static FeatureInfo feature_set[] = { { 0, String() }, }; -int32_t TextServerAdvanced::name_to_tag(const String &p_name) { +int32_t TextServerAdvanced::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; @@ -508,7 +706,7 @@ int32_t TextServerAdvanced::name_to_tag(const String &p_name) { return hb_tag_from_string(p_name.replace("custom_", "").ascii().get_data(), -1); } -String TextServerAdvanced::tag_to_name(int32_t p_tag) { +String TextServerAdvanced::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; @@ -523,426 +721,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); + + MutexLock lock(fd->mutex); + return fd->oversampling; +} + +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_height(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_ascent(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_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]->descent * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->descent; + } } -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_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_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]->underline_position * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->underline_position; + } } -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_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_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_thickness * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->underline_thickness; + } } -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_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; +} + +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); - 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]->scale * (real_t)p_size / (real_t)fd->msdf_source_size; + } else { + return fd->cache[size]->scale / fd->cache[size]->oversampling; + } } -int TextServerAdvanced::font_get_spacing_space(RID p_font) const { - _THREAD_SAFE_METHOD_ - const FontDataAdvanced *fd = font_owner.getornull(p_font); +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); + + 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(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_space(); + + 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_space(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_space(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(); } -int TextServerAdvanced::font_get_spacing_glyph(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_glyph(); +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); + + 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); } -void TextServerAdvanced::font_set_spacing_glyph(RID p_font, int p_value) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +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); - fd->set_spacing_glyph(p_value); + 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 TextServerAdvanced::font_set_antialiased(RID p_font, bool p_antialiased) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +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; +} + +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); - 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)); + 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; } -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(); +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; } -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(); +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; } -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_clear_glyphs(RID p_font_rid, const Vector2i &p_size) { + 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.clear(); } -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); +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); - 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.erase(p_glyph); } -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); +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_distance_field_hint(RID p_font, bool p_distance_field) { - _THREAD_SAFE_METHOD_ - FontDataAdvanced *fd = font_owner.getornull(p_font); +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 +2895,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 +2910,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 +2924,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 +2944,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 +2958,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 +2970,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 +2992,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 +3072,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 +3110,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 +3135,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 +3205,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 +3242,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 +3299,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 +3326,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 +3398,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 +3446,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 +3486,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 +3501,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 +3541,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 +3554,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 +3571,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 +3579,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 +3591,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 +3622,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 +3718,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 +3913,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 +4024,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 +4050,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 +4070,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 +4102,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 +4183,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 +4233,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 +4253,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 +4423,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 +4491,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 +4510,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 +4536,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 +4549,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 +4561,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 +4575,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 +4620,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); } @@ -2762,7 +4651,6 @@ static num_system_data num_systems[]{ }; 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; String res = p_string; @@ -2789,7 +4677,6 @@ 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; String res = p_string; @@ -2819,7 +4706,6 @@ 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; for (int i = 0; num_systems[i].lang != String(); i++) { @@ -2849,6 +4735,9 @@ TextServerAdvanced::TextServerAdvanced() { 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/VisualScriptCustomNode.xml b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml index 8aa34f8cae..b574576856 100644 --- a/modules/visual_script/doc_classes/VisualScriptCustomNode.xml +++ b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml @@ -9,118 +9,118 @@ <tutorials> </tutorials> <methods> - <method name="_get_caption" qualifiers="virtual"> + <method name="_get_caption" qualifiers="virtual const"> <return type="String" /> <description> Return the node's title. </description> </method> - <method name="_get_category" qualifiers="virtual"> + <method name="_get_category" qualifiers="virtual const"> <return type="String" /> <description> Return the node's category. </description> </method> - <method name="_get_input_value_port_count" qualifiers="virtual"> + <method name="_get_input_value_port_count" qualifiers="virtual const"> <return type="int" /> <description> Return the count of input value ports. </description> </method> - <method name="_get_input_value_port_hint" qualifiers="virtual"> + <method name="_get_input_value_port_hint" qualifiers="virtual const"> <return type="int" /> - <argument index="0" name="idx" type="int" /> + <argument index="0" name="input_idx" type="int" /> <description> Return the specified input port's hint. See the [enum @GlobalScope.PropertyHint] hints. </description> </method> - <method name="_get_input_value_port_hint_string" qualifiers="virtual"> + <method name="_get_input_value_port_hint_string" qualifiers="virtual const"> <return type="String" /> - <argument index="0" name="idx" type="int" /> + <argument index="0" name="input_idx" type="int" /> <description> Return the specified input port's hint string. </description> </method> - <method name="_get_input_value_port_name" qualifiers="virtual"> + <method name="_get_input_value_port_name" qualifiers="virtual const"> <return type="String" /> - <argument index="0" name="idx" type="int" /> + <argument index="0" name="input_idx" type="int" /> <description> Return the specified input port's name. </description> </method> - <method name="_get_input_value_port_type" qualifiers="virtual"> + <method name="_get_input_value_port_type" qualifiers="virtual const"> <return type="int" /> - <argument index="0" name="idx" type="int" /> + <argument index="0" name="input_idx" type="int" /> <description> Return the specified input port's type. See the [enum Variant.Type] values. </description> </method> - <method name="_get_output_sequence_port_count" qualifiers="virtual"> + <method name="_get_output_sequence_port_count" qualifiers="virtual const"> <return type="int" /> <description> Return the amount of output [b]sequence[/b] ports. </description> </method> - <method name="_get_output_sequence_port_text" qualifiers="virtual"> + <method name="_get_output_sequence_port_text" qualifiers="virtual const"> <return type="String" /> - <argument index="0" name="idx" type="int" /> + <argument index="0" name="seq_idx" type="int" /> <description> Return the specified [b]sequence[/b] output's name. </description> </method> - <method name="_get_output_value_port_count" qualifiers="virtual"> + <method name="_get_output_value_port_count" qualifiers="virtual const"> <return type="int" /> <description> Return the amount of output value ports. </description> </method> - <method name="_get_output_value_port_hint" qualifiers="virtual"> + <method name="_get_output_value_port_hint" qualifiers="virtual const"> <return type="int" /> - <argument index="0" name="idx" type="int" /> + <argument index="0" name="output_idx" type="int" /> <description> Return the specified output port's hint. See the [enum @GlobalScope.PropertyHint] hints. </description> </method> - <method name="_get_output_value_port_hint_string" qualifiers="virtual"> + <method name="_get_output_value_port_hint_string" qualifiers="virtual const"> <return type="String" /> - <argument index="0" name="idx" type="int" /> + <argument index="0" name="output_idx" type="int" /> <description> Return the specified output port's hint string. </description> </method> - <method name="_get_output_value_port_name" qualifiers="virtual"> + <method name="_get_output_value_port_name" qualifiers="virtual const"> <return type="String" /> - <argument index="0" name="idx" type="int" /> + <argument index="0" name="output_idx" type="int" /> <description> Return the specified output port's name. </description> </method> - <method name="_get_output_value_port_type" qualifiers="virtual"> + <method name="_get_output_value_port_type" qualifiers="virtual const"> <return type="int" /> - <argument index="0" name="idx" type="int" /> + <argument index="0" name="output_idx" type="int" /> <description> Return the specified output port's type. See the [enum Variant.Type] values. </description> </method> - <method name="_get_text" qualifiers="virtual"> + <method name="_get_text" qualifiers="virtual const"> <return type="String" /> <description> Return the custom node's text, which is shown right next to the input [b]sequence[/b] port (if there is none, on the place that is usually taken by it). </description> </method> - <method name="_get_working_memory_size" qualifiers="virtual"> + <method name="_get_working_memory_size" qualifiers="virtual const"> <return type="int" /> <description> Return the size of the custom node's working memory. See [method _step] for more details. </description> </method> - <method name="_has_input_sequence_port" qualifiers="virtual"> + <method name="_has_input_sequence_port" qualifiers="virtual const"> <return type="bool" /> <description> Return whether the custom node has an input [b]sequence[/b] port. </description> </method> - <method name="_step" qualifiers="virtual"> + <method name="_step" qualifiers="virtual const"> <return type="Variant" /> <argument index="0" name="inputs" type="Array" /> <argument index="1" name="outputs" type="Array" /> diff --git a/modules/visual_script/doc_classes/VisualScriptSubCall.xml b/modules/visual_script/doc_classes/VisualScriptSubCall.xml index 374e7d0f35..f54887b09c 100644 --- a/modules/visual_script/doc_classes/VisualScriptSubCall.xml +++ b/modules/visual_script/doc_classes/VisualScriptSubCall.xml @@ -9,13 +9,6 @@ <tutorials> </tutorials> <methods> - <method name="_subcall" qualifiers="virtual"> - <return type="Variant" /> - <argument index="0" name="arguments" type="Variant" /> - <description> - Called by this node. - </description> - </method> </methods> <constants> </constants> diff --git a/modules/visual_script/register_types.cpp b/modules/visual_script/register_types.cpp index f20ef046a3..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 _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(_VisualScriptEditor); + GDREGISTER_CLASS(VisualScriptCustomNodes); ClassDB::set_current_api(ClassDB::API_CORE); - vs_editor_singleton = memnew(_VisualScriptEditor); - Engine::get_singleton()->add_singleton(Engine::Singleton("VisualScriptEditor", _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..142419f90a 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -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_editor.cpp b/modules/visual_script/visual_script_editor.cpp index f3b6d74b7d..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); @@ -4521,44 +4519,47 @@ void VisualScriptEditor::register_editor() { EditorNode::add_plugin_init_callback(register_editor_callback); } -Ref<VisualScriptNode> _VisualScriptEditor::create_node_custom(const String &p_name) { +void VisualScriptEditor::validate() { +} + +// VisualScriptCustomNodes + +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")); } -void VisualScriptEditor::validate() { -} #endif diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 962ea380aa..7dfb4fa270 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -43,13 +43,14 @@ class VisualScriptEditorVariableEdit; #ifdef TOOLS_ENABLED +// 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 - }; enum { @@ -71,7 +72,6 @@ class VisualScriptEditor : public ScriptEditorBase { enum MemberAction { MEMBER_EDIT, MEMBER_REMOVE - }; enum MemberType { @@ -236,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); @@ -333,27 +333,28 @@ public: }; // 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(); }; + #endif #endif // VISUALSCRIPT_EDITOR_H diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index c517d89aa5..480136736a 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -2825,36 +2825,41 @@ VisualScriptSelf::VisualScriptSelf() { ////////////////////////////////////////// int VisualScriptCustomNode::get_output_sequence_port_count() const { - if (get_script_instance() && get_script_instance()->has_method("_get_output_sequence_port_count")) { - return get_script_instance()->call("_get_output_sequence_port_count"); + int ret; + if (GDVIRTUAL_CALL(_get_output_sequence_port_count, ret)) { + return ret; } return 0; } bool VisualScriptCustomNode::has_input_sequence_port() const { - if (get_script_instance() && get_script_instance()->has_method("_has_input_sequence_port")) { - return get_script_instance()->call("_has_input_sequence_port"); + bool ret; + if (GDVIRTUAL_CALL(_has_input_sequence_port, ret)) { + return ret; } return false; } int VisualScriptCustomNode::get_input_value_port_count() const { - if (get_script_instance() && get_script_instance()->has_method("_get_input_value_port_count")) { - return get_script_instance()->call("_get_input_value_port_count"); + int ret; + if (GDVIRTUAL_CALL(_get_input_value_port_count, ret)) { + return ret; } return 0; } int VisualScriptCustomNode::get_output_value_port_count() const { - if (get_script_instance() && get_script_instance()->has_method("_get_output_value_port_count")) { - return get_script_instance()->call("_get_output_value_port_count"); + int ret; + if (GDVIRTUAL_CALL(_get_output_value_port_count, ret)) { + return ret; } return 0; } String VisualScriptCustomNode::get_output_sequence_port_text(int p_port) const { - if (get_script_instance() && get_script_instance()->has_method("_get_output_sequence_port_text")) { - return get_script_instance()->call("_get_output_sequence_port_text", p_port); + String ret; + if (GDVIRTUAL_CALL(_get_output_sequence_port_text, p_port, ret)) { + return ret; } return String(); @@ -2862,34 +2867,61 @@ String VisualScriptCustomNode::get_output_sequence_port_text(int p_port) const { PropertyInfo VisualScriptCustomNode::get_input_value_port_info(int p_idx) const { PropertyInfo info; - if (get_script_instance() && get_script_instance()->has_method("_get_input_value_port_type")) { - info.type = Variant::Type(int(get_script_instance()->call("_get_input_value_port_type", p_idx))); + { + int type; + if (GDVIRTUAL_CALL(_get_input_value_port_type, p_idx, type)) { + info.type = Variant::Type(type); + } } - if (get_script_instance() && get_script_instance()->has_method("_get_input_value_port_name")) { - info.name = get_script_instance()->call("_get_input_value_port_name", p_idx); + { + String name; + if (GDVIRTUAL_CALL(_get_input_value_port_name, p_idx, name)) { + info.name = name; + } } - if (get_script_instance() && get_script_instance()->has_method("_get_input_value_port_hint")) { - info.hint = PropertyHint(int(get_script_instance()->call("_get_input_value_port_hint", p_idx))); + { + int hint; + if (GDVIRTUAL_CALL(_get_input_value_port_hint, p_idx, hint)) { + info.hint = PropertyHint(hint); + } } - if (get_script_instance() && get_script_instance()->has_method("_get_input_value_port_hint_string")) { - info.hint_string = get_script_instance()->call("_get_input_value_port_hint_string", p_idx); + + { + String hint_string; + if (GDVIRTUAL_CALL(_get_input_value_port_hint_string, p_idx, hint_string)) { + info.hint_string = hint_string; + } } + return info; } PropertyInfo VisualScriptCustomNode::get_output_value_port_info(int p_idx) const { PropertyInfo info; - if (get_script_instance() && get_script_instance()->has_method("_get_output_value_port_type")) { - info.type = Variant::Type(int(get_script_instance()->call("_get_output_value_port_type", p_idx))); + { + int type; + if (GDVIRTUAL_CALL(_get_output_value_port_type, p_idx, type)) { + info.type = Variant::Type(type); + } } - if (get_script_instance() && get_script_instance()->has_method("_get_output_value_port_name")) { - info.name = get_script_instance()->call("_get_output_value_port_name", p_idx); + { + String name; + if (GDVIRTUAL_CALL(_get_output_value_port_name, p_idx, name)) { + info.name = name; + } } - if (get_script_instance() && get_script_instance()->has_method("_get_output_value_port_hint")) { - info.hint = PropertyHint(int(get_script_instance()->call("_get_output_value_port_hint", p_idx))); + { + int hint; + if (GDVIRTUAL_CALL(_get_output_value_port_hint, p_idx, hint)) { + info.hint = PropertyHint(hint); + } } - if (get_script_instance() && get_script_instance()->has_method("_get_output_value_port_hint_string")) { - info.hint_string = get_script_instance()->call("_get_output_value_port_hint_string", p_idx); + + { + String hint_string; + if (GDVIRTUAL_CALL(_get_output_value_port_hint_string, p_idx, hint_string)) { + info.hint_string = hint_string; + } } return info; } @@ -2911,22 +2943,25 @@ VisualScriptCustomNode::TypeGuess VisualScriptCustomNode::guess_output_type(Type } String VisualScriptCustomNode::get_caption() const { - if (get_script_instance() && get_script_instance()->has_method("_get_caption")) { - return get_script_instance()->call("_get_caption"); + String ret; + if (GDVIRTUAL_CALL(_get_caption, ret)) { + return ret; } return "CustomNode"; } String VisualScriptCustomNode::get_text() const { - if (get_script_instance() && get_script_instance()->has_method("_get_text")) { - return get_script_instance()->call("_get_text"); + String ret; + if (GDVIRTUAL_CALL(_get_text, ret)) { + return ret; } return ""; } String VisualScriptCustomNode::get_category() const { - if (get_script_instance() && get_script_instance()->has_method("_get_category")) { - return get_script_instance()->call("_get_category"); + String ret; + if (GDVIRTUAL_CALL(_get_category, ret)) { + return ret; } return "Custom"; } @@ -2941,14 +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 (node->get_script_instance()) { -#ifdef DEBUG_ENABLED - if (!node->get_script_instance()->has_method(VisualScriptLanguage::singleton->_step)) { - r_error_str = RTR("Custom node has no _step() method, can't process graph."); - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return 0; - } -#endif + if (GDVIRTUAL_IS_OVERRIDDEN_PTR(node, _step)) { Array in_values; Array out_values; Array work_mem; @@ -2969,7 +2997,8 @@ public: int ret_out; - Variant ret = node->get_script_instance()->call(VisualScriptLanguage::singleton->_step, in_values, out_values, p_start_mode, work_mem); + Variant ret; + GDVIRTUAL_CALL_PTR(node, _step, in_values, out_values, p_start_mode, work_mem, ret); if (ret.get_type() == Variant::STRING) { r_error_str = ret; r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; @@ -2995,6 +3024,9 @@ public: } return ret_out; + } else { + r_error_str = RTR("Custom node has no _step() method, can't process graph."); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; } return 0; @@ -3008,11 +3040,8 @@ VisualScriptNodeInstance *VisualScriptCustomNode::instantiate(VisualScriptInstan instance->in_count = get_input_value_port_count(); instance->out_count = get_output_value_port_count(); - if (get_script_instance() && get_script_instance()->has_method("_get_working_memory_size")) { - instance->work_mem_size = get_script_instance()->call("_get_working_memory_size"); - } else { - instance->work_mem_size = 0; - } + instance->work_mem_size = 0; + GDVIRTUAL_CALL(_get_working_memory_size, instance->work_mem_size); return instance; } @@ -3022,32 +3051,29 @@ void VisualScriptCustomNode::_script_changed() { } void VisualScriptCustomNode::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_sequence_port_count")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_input_sequence_port")); - - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_output_sequence_port_text", PropertyInfo(Variant::INT, "idx"))); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_input_value_port_count")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_value_port_count")); + GDVIRTUAL_BIND(_get_output_sequence_port_count); + GDVIRTUAL_BIND(_has_input_sequence_port); + GDVIRTUAL_BIND(_get_output_sequence_port_text, "seq_idx"); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_input_value_port_type", PropertyInfo(Variant::INT, "idx"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_input_value_port_name", PropertyInfo(Variant::INT, "idx"))); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_input_value_port_hint", PropertyInfo(Variant::INT, "idx"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_input_value_port_hint_string", PropertyInfo(Variant::INT, "idx"))); + GDVIRTUAL_BIND(_get_input_value_port_count); + GDVIRTUAL_BIND(_get_input_value_port_type, "input_idx"); + GDVIRTUAL_BIND(_get_input_value_port_name, "input_idx"); + GDVIRTUAL_BIND(_get_input_value_port_hint, "input_idx"); + GDVIRTUAL_BIND(_get_input_value_port_hint_string, "input_idx"); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_value_port_type", PropertyInfo(Variant::INT, "idx"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_output_value_port_name", PropertyInfo(Variant::INT, "idx"))); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_value_port_hint", PropertyInfo(Variant::INT, "idx"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_output_value_port_hint_string", PropertyInfo(Variant::INT, "idx"))); + GDVIRTUAL_BIND(_get_output_value_port_count); + GDVIRTUAL_BIND(_get_output_value_port_type, "output_idx"); + GDVIRTUAL_BIND(_get_output_value_port_name, "output_idx"); + GDVIRTUAL_BIND(_get_output_value_port_hint, "output_idx"); + GDVIRTUAL_BIND(_get_output_value_port_hint_string, "output_idx"); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_caption")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_text")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_category")); + GDVIRTUAL_BIND(_get_caption); + GDVIRTUAL_BIND(_get_text); + GDVIRTUAL_BIND(_get_category); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_working_memory_size")); + GDVIRTUAL_BIND(_get_working_memory_size); - MethodInfo stepmi(Variant::NIL, "_step", PropertyInfo(Variant::ARRAY, "inputs"), PropertyInfo(Variant::ARRAY, "outputs"), PropertyInfo(Variant::INT, "start_mode"), PropertyInfo(Variant::ARRAY, "working_mem")); - stepmi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(stepmi); + GDVIRTUAL_BIND(_step, "inputs", "outputs", "start_mode", "working_mem"); BIND_ENUM_CONSTANT(START_MODE_BEGIN_SEQUENCE); BIND_ENUM_CONSTANT(START_MODE_CONTINUE_SEQUENCE); @@ -3170,9 +3196,7 @@ VisualScriptNodeInstance *VisualScriptSubCall::instantiate(VisualScriptInstance } void VisualScriptSubCall::_bind_methods() { - MethodInfo scmi(Variant::NIL, "_subcall", PropertyInfo(Variant::NIL, "arguments")); - scmi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(scmi); + // Since this is script only, registering virtual function is no longer valid. Will have to go in docs. } VisualScriptSubCall::VisualScriptSubCall() { diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h index 2390e5c7bc..35d9b0b4fe 100644 --- a/modules/visual_script/visual_script_nodes.h +++ b/modules/visual_script/visual_script_nodes.h @@ -31,6 +31,8 @@ #ifndef VISUAL_SCRIPT_NODES_H #define VISUAL_SCRIPT_NODES_H +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" #include "visual_script.h" class VisualScriptFunction : public VisualScriptNode { @@ -757,6 +759,30 @@ class VisualScriptCustomNode : public VisualScriptNode { protected: static void _bind_methods(); + friend class VisualScriptNodeInstanceCustomNode; + GDVIRTUAL0RC(int, _get_output_sequence_port_count) + GDVIRTUAL0RC(bool, _has_input_sequence_port) + GDVIRTUAL1RC(String, _get_output_sequence_port_text, int) + + GDVIRTUAL0RC(int, _get_input_value_port_count) + GDVIRTUAL1RC(int, _get_input_value_port_type, int) + GDVIRTUAL1RC(String, _get_input_value_port_name, int) + GDVIRTUAL1RC(int, _get_input_value_port_hint, int) + GDVIRTUAL1RC(String, _get_input_value_port_hint_string, int) + + GDVIRTUAL0RC(int, _get_output_value_port_count) + GDVIRTUAL1RC(int, _get_output_value_port_type, int) + GDVIRTUAL1RC(String, _get_output_value_port_name, int) + GDVIRTUAL1RC(int, _get_output_value_port_hint, int) + GDVIRTUAL1RC(String, _get_output_value_port_hint_string, int) + + GDVIRTUAL0RC(String, _get_caption) + GDVIRTUAL0RC(String, _get_text) + GDVIRTUAL0RC(String, _get_category) + + GDVIRTUAL0RC(int, _get_working_memory_size) + + GDVIRTUAL4RC(Variant, _step, Array, Array, int, Array) public: enum StartMode { //replicated for step 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/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/android_input_handler.cpp b/platform/android/android_input_handler.cpp index b9004c4989..e03375e8d9 100644 --- a/platform/android/android_input_handler.cpp +++ b/platform/android/android_input_handler.cpp @@ -377,7 +377,7 @@ MouseButton AndroidInputHandler::_android_button_mask_to_godot_button_mask(int a if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) { godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON1; } - if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { + if (android_button_mask & AMOTION_EVENT_BUTTON_FORWARD) { godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON2; } 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/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 17ee173855..bdf12b6c50 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -717,6 +717,10 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared return OK; } +bool EditorExportPlatformAndroid::_has_storage_permission(const Vector<String> &p_permissions) { + return p_permissions.find("android.permission.READ_EXTERNAL_STORAGE") != -1 || p_permissions.find("android.permission.WRITE_EXTERNAL_STORAGE") != -1; +} + void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) { const char **aperms = android_perms; while (*aperms) { @@ -763,12 +767,17 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres Vector<String> perms; _get_permissions(p_preset, p_give_internet, perms); for (int i = 0; i < perms.size(); i++) { - manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i)); + String permission = perms.get(i); + if (permission == "android.permission.WRITE_EXTERNAL_STORAGE" || permission == "android.permission.READ_EXTERNAL_STORAGE") { + manifest_text += vformat(" <uses-permission android:name=\"%s\" android:maxSdkVersion=\"29\" />\n", permission); + } else { + manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", permission); + } } manifest_text += _get_xr_features_tag(p_preset); manifest_text += _get_instrumentation_tag(p_preset); - manifest_text += _get_application_tag(p_preset); + manifest_text += _get_application_tag(p_preset, _has_storage_permission(perms)); manifest_text += "</manifest>\n"; String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); @@ -824,6 +833,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p Vector<String> perms; // Write permissions into the perms variable. _get_permissions(p_preset, p_give_internet, perms); + bool has_storage_permission = _has_storage_permission(perms); while (ofs < (uint32_t)p_manifest.size()) { uint32_t chunk = decode_uint32(&p_manifest[ofs]); @@ -913,6 +923,10 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p } } + if (tname == "application" && attrname == "requestLegacyExternalStorage") { + encode_uint32(has_storage_permission ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); + } + if (tname == "application" && attrname == "allowBackup") { encode_uint32(backup_allowed, &p_manifest.write[iofs + 16]); } @@ -1632,8 +1646,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")); @@ -1780,7 +1792,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"); diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 909428c2fe..b061ee4e04 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -134,6 +134,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { static Error copy_gradle_so(void *p_userdata, const SharedObject &p_so); + bool _has_storage_permission(const Vector<String> &p_permissions); + void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions); void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug); diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 76512226bf..b9e28a7937 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -235,18 +235,20 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { return manifest_activity_text; } -String _get_application_tag(const Ref<EditorExportPreset> &p_preset) { +String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission) { String manifest_application_text = vformat( " <application android:label=\"@string/godot_project_name_string\"\n" " android:allowBackup=\"%s\"\n" " android:icon=\"@mipmap/icon\"\n" " android:isGame=\"%s\"\n" " android:hasFragileUserData=\"%s\"\n" - " tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData\"\n" + " android:requestLegacyExternalStorage=\"%s\"\n" + " tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData,android:requestLegacyExternalStorage\"\n" " tools:ignore=\"GoogleAppIndexingWarning\">\n\n", bool_to_string(p_preset->get("user_data_backup/allow")), bool_to_string(p_preset->get("package/classify_as_game")), - bool_to_string(p_preset->get("package/retain_data_on_uninstall"))); + bool_to_string(p_preset->get("package/retain_data_on_uninstall")), + bool_to_string(p_has_storage_permission)); manifest_application_text += _get_activity_tag(p_preset); manifest_application_text += " </application>\n"; diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 44e9a1727d..8a93c25d79 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -81,6 +81,6 @@ String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset); String _get_activity_tag(const Ref<EditorExportPreset> &p_preset); -String _get_application_tag(const Ref<EditorExportPreset> &p_preset); +String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission); #endif //GODOT_GRADLE_EXPORT_UTIL_H diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 467a0dc3c0..00e01884cf 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -22,6 +22,7 @@ android:icon="@mipmap/icon" android:isGame="true" android:hasFragileUserData="false" + android:requestLegacyExternalStorage="false" tools:ignore="GoogleAppIndexingWarning" > <!-- Records the version of the Godot editor used for building --> diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 81fc87b7ef..fad64c675f 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,8 +1,8 @@ ext.versions = [ - androidGradlePlugin: '4.2.1', - compileSdk : 29, - minSdk : 18, - targetSdk : 29, + androidGradlePlugin: '4.2.2', + compileSdk : 30, + minSdk : 19, + targetSdk : 30, buildTools : '30.0.3', supportCoreUtils : '1.0.0', kotlinVersion : '1.5.10', 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/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/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java index ad7048cbf3..3600706c7c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java +++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java @@ -30,6 +30,7 @@ package org.godotengine.godot; +import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import android.util.Log; @@ -86,6 +87,26 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God } @Override + public final void onGodotRestartRequested(Godot instance) { + if (instance == godotFragment) { + // HACK: + // + // Currently it's very hard to properly deinitialize Godot on Android to restart the game + // from scratch. Therefore, we need to kill the whole app process and relaunch it. + // + // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including + // releasing and reloading native libs or resetting their state somehow and clearing statics). + // + // Using instrumentation is a way of making the whole app process restart, because Android + // will kill any process of the same package which was already running. + // + Bundle args = new Bundle(); + args.putParcelable("intent", getIntent()); + startInstrumentation(new ComponentName(this, GodotInstrumentation.class), null, args); + } + } + + @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); if (godotFragment != null) { 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 76751a886c..896b169953 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -49,7 +49,6 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; @@ -68,15 +67,12 @@ import android.os.Environment; import android.os.Messenger; import android.os.VibrationEffect; import android.os.Vibrator; -import android.provider.Settings.Secure; import android.view.Display; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; -import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.widget.Button; @@ -321,7 +317,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC @SuppressLint("MissingPermission") @Keep private void vibrate(int durationMs) { - if (requestPermission("VIBRATE")) { + if (durationMs > 0 && requestPermission("VIBRATE")) { Vibrator v = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE); if (v != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -335,22 +331,8 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC } public void restart() { - // HACK: - // - // Currently it's very hard to properly deinitialize Godot on Android to restart the game - // from scratch. Therefore, we need to kill the whole app process and relaunch it. - // - // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including - // releasing and reloading native libs or resetting their state somehow and clearing statics). - // - // Using instrumentation is a way of making the whole app process restart, because Android - // will kill any process of the same package which was already running. - // - final Activity activity = getActivity(); - if (activity != null) { - Bundle args = new Bundle(); - args.putParcelable("intent", mCurrentIntent); - activity.startInstrumentation(new ComponentName(activity, GodotInstrumentation.class), null, args); + if (godotHost != null) { + godotHost.onGodotRestartRequested(this); } } @@ -471,7 +453,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC final Activity activity = getActivity(); io = new GodotIO(activity); - io.unique_id = Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID); GodotLib.io = io; netUtils = new GodotNetUtils(activity); mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java index 58e982c569..7b22895994 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java @@ -58,4 +58,10 @@ public interface GodotHost { * Invoked on the UI thread as the last step of the Godot instance clean up phase. */ default void onGodotForceQuit(Godot instance) {} + + /** + * Invoked on the GL thread when the Godot instance wants to be restarted. It's up to the host + * to perform the appropriate action(s). + */ + default void onGodotRestartRequested(Godot instance) {} } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index 66882e8e72..d85d88ec6c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -30,15 +30,19 @@ package org.godotengine.godot; -import org.godotengine.godot.input.*; +import org.godotengine.godot.input.GodotEditText; import android.app.Activity; -import android.content.*; +import android.content.ActivityNotFoundException; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.AssetManager; import android.graphics.Point; import android.net.Uri; -import android.os.*; +import android.os.Build; +import android.os.Environment; +import android.provider.Settings; +import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; @@ -47,14 +51,16 @@ import android.view.DisplayCutout; import android.view.WindowInsets; import java.io.IOException; -import java.io.InputStream; import java.util.Locale; // Wrapper for native library public class GodotIO { - AssetManager am; - final Activity activity; + private static final String TAG = GodotIO.class.getSimpleName(); + + private final AssetManager am; + private final Activity activity; + private final String uniqueId; GodotEditText edit; final int SCREEN_LANDSCAPE = 0; @@ -66,167 +72,6 @@ public class GodotIO { final int SCREEN_SENSOR = 6; ///////////////////////// - /// FILES - ///////////////////////// - - public int last_file_id = 1; - - static class AssetData { - public boolean eof = false; - public String path; - public InputStream is; - public int len; - public int pos; - } - - SparseArray<AssetData> streams; - - public int file_open(String path, boolean write) { - //System.out.printf("file_open: Attempt to Open %s\n",path); - - //Log.v("MyApp", "TRYING TO OPEN FILE: " + path); - if (write) - return -1; - - AssetData ad = new AssetData(); - - try { - ad.is = am.open(path); - - } catch (Exception e) { - //System.out.printf("Exception on file_open: %s\n",path); - return -1; - } - - try { - ad.len = ad.is.available(); - } catch (Exception e) { - System.out.printf("Exception availabling on file_open: %s\n", path); - return -1; - } - - ad.path = path; - ad.pos = 0; - ++last_file_id; - streams.put(last_file_id, ad); - - return last_file_id; - } - public int file_get_size(int id) { - if (streams.get(id) == null) { - System.out.printf("file_get_size: Invalid file id: %d\n", id); - return -1; - } - - return streams.get(id).len; - } - public void file_seek(int id, int bytes) { - if (streams.get(id) == null) { - System.out.printf("file_get_size: Invalid file id: %d\n", id); - return; - } - //seek sucks - AssetData ad = streams.get(id); - if (bytes > ad.len) - bytes = ad.len; - if (bytes < 0) - bytes = 0; - - try { - if (bytes > (int)ad.pos) { - int todo = bytes - (int)ad.pos; - while (todo > 0) { - todo -= ad.is.skip(todo); - } - ad.pos = bytes; - } else if (bytes < (int)ad.pos) { - ad.is = am.open(ad.path); - - ad.pos = bytes; - int todo = bytes; - while (todo > 0) { - todo -= ad.is.skip(todo); - } - } - - ad.eof = false; - } catch (IOException e) { - System.out.printf("Exception on file_seek: %s\n", e); - return; - } - } - - public int file_tell(int id) { - if (streams.get(id) == null) { - System.out.printf("file_read: Can't tell eof for invalid file id: %d\n", id); - return 0; - } - - AssetData ad = streams.get(id); - return ad.pos; - } - public boolean file_eof(int id) { - if (streams.get(id) == null) { - System.out.printf("file_read: Can't check eof for invalid file id: %d\n", id); - return false; - } - - AssetData ad = streams.get(id); - return ad.eof; - } - - public byte[] file_read(int id, int bytes) { - if (streams.get(id) == null) { - System.out.printf("file_read: Can't read invalid file id: %d\n", id); - return new byte[0]; - } - - AssetData ad = streams.get(id); - - if (ad.pos + bytes > ad.len) { - bytes = ad.len - ad.pos; - ad.eof = true; - } - - if (bytes == 0) { - return new byte[0]; - } - - byte[] buf1 = new byte[bytes]; - int r = 0; - try { - r = ad.is.read(buf1); - } catch (IOException e) { - System.out.printf("Exception on file_read: %s\n", e); - return new byte[bytes]; - } - - if (r == 0) { - return new byte[0]; - } - - ad.pos += r; - - if (r < bytes) { - byte[] buf2 = new byte[r]; - for (int i = 0; i < r; i++) - buf2[i] = buf1[i]; - return buf2; - } else { - return buf1; - } - } - - public void file_close(int id) { - if (streams.get(id) == null) { - System.out.printf("file_close: Can't close invalid file id: %d\n", id); - return; - } - - streams.remove(id); - } - - ///////////////////////// /// DIRECTORIES ///////////////////////// @@ -236,9 +81,9 @@ public class GodotIO { public String path; } - public int last_dir_id = 1; + private int last_dir_id = 1; - SparseArray<AssetDir> dirs; + private final SparseArray<AssetDir> dirs; public int dir_open(String path) { AssetDir ad = new AssetDir(); @@ -257,7 +102,6 @@ public class GodotIO { return -1; } - //System.out.printf("Opened dir: %s\n",path); ++last_dir_id; dirs.put(last_dir_id, ad); @@ -320,9 +164,14 @@ public class GodotIO { GodotIO(Activity p_activity) { am = p_activity.getAssets(); activity = p_activity; - //streams = new HashMap<Integer, AssetData>(); - streams = new SparseArray<>(); dirs = new SparseArray<>(); + String androidId = Settings.Secure.getString(activity.getContentResolver(), + Settings.Secure.ANDROID_ID); + if (androidId == null) { + androidId = ""; + } + + uniqueId = androidId; } ///////////////////////// @@ -331,7 +180,6 @@ public class GodotIO { public int openURI(String p_uri) { try { - Log.v("MyApp", "TRYING TO OPEN URI: " + p_uri); String path = p_uri; String type = ""; if (path.startsWith("/")) { @@ -357,12 +205,12 @@ public class GodotIO { } } - public String getDataDir() { - return activity.getFilesDir().getAbsolutePath(); + public String getCacheDir() { + return activity.getCacheDir().getAbsolutePath(); } - public String getExternalDataDir() { - return activity.getExternalFilesDir(null).getAbsolutePath(); + public String getDataDir() { + return activity.getFilesDir().getAbsolutePath(); } public String getLocale() { @@ -456,51 +304,58 @@ public class GodotIO { public static final int SYSTEM_DIR_PICTURES = 6; public static final int SYSTEM_DIR_RINGTONES = 7; - public String getSystemDir(int idx) { - String what = ""; + public String getSystemDir(int idx, boolean shared_storage) { + String what; switch (idx) { - case SYSTEM_DIR_DESKTOP: { - //what=Environment.DIRECTORY_DOCUMENTS; - what = Environment.DIRECTORY_DOWNLOADS; + case SYSTEM_DIR_DESKTOP: + default: { + what = null; // This leads to the app specific external root directory. } break; + case SYSTEM_DIR_DCIM: { what = Environment.DIRECTORY_DCIM; - } break; + case SYSTEM_DIR_DOCUMENTS: { - what = Environment.DIRECTORY_DOWNLOADS; - //what=Environment.DIRECTORY_DOCUMENTS; + what = Environment.DIRECTORY_DOCUMENTS; } break; + case SYSTEM_DIR_DOWNLOADS: { what = Environment.DIRECTORY_DOWNLOADS; - } break; + case SYSTEM_DIR_MOVIES: { what = Environment.DIRECTORY_MOVIES; - } break; + case SYSTEM_DIR_MUSIC: { what = Environment.DIRECTORY_MUSIC; } break; + case SYSTEM_DIR_PICTURES: { what = Environment.DIRECTORY_PICTURES; } break; + case SYSTEM_DIR_RINGTONES: { what = Environment.DIRECTORY_RINGTONES; - } break; } - if (what.equals("")) - return ""; - return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath(); + if (shared_storage) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Log.w(TAG, "Shared storage access is limited on Android 10 and higher."); + } + if (TextUtils.isEmpty(what)) { + return Environment.getExternalStorageDirectory().getAbsolutePath(); + } else { + return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath(); + } + } else { + return activity.getExternalFilesDir(what).getAbsolutePath(); + } } - protected static final String PREFS_FILE = "device_id.xml"; - protected static final String PREFS_DEVICE_ID = "device_id"; - - public static String unique_id = ""; public String getUniqueID() { - return unique_id; + return uniqueId; } } diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 5e99135498..5cd2c382d2 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -48,8 +48,8 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc } _open_URI = p_env->GetMethodID(cls, "openURI", "(Ljava/lang/String;)I"); + _get_cache_dir = p_env->GetMethodID(cls, "getCacheDir", "()Ljava/lang/String;"); _get_data_dir = p_env->GetMethodID(cls, "getDataDir", "()Ljava/lang/String;"); - _get_external_data_dir = p_env->GetMethodID(cls, "getExternalDataDir", "()Ljava/lang/String;"); _get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;"); _get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;"); _get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I"); @@ -59,7 +59,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc _hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V"); _set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V"); _get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I"); - _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;"); + _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;"); } } @@ -82,22 +82,22 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) { } } -String GodotIOJavaWrapper::get_user_data_dir() { - if (_get_data_dir) { +String GodotIOJavaWrapper::get_cache_dir() { + if (_get_cache_dir) { JNIEnv *env = get_jni_env(); ERR_FAIL_COND_V(env == nullptr, String()); - jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_cache_dir); return jstring_to_string(s, env); } else { return String(); } } -String GodotIOJavaWrapper::get_external_data_dir() { - if (_get_external_data_dir) { +String GodotIOJavaWrapper::get_user_data_dir() { + if (_get_data_dir) { JNIEnv *env = get_jni_env(); ERR_FAIL_COND_V(env == nullptr, String()); - jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_external_data_dir); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir); return jstring_to_string(s, env); } else { return String(); @@ -200,11 +200,11 @@ int GodotIOJavaWrapper::get_screen_orientation() { } } -String GodotIOJavaWrapper::get_system_dir(int p_dir) { +String GodotIOJavaWrapper::get_system_dir(int p_dir, bool p_shared_storage) { if (_get_system_dir) { JNIEnv *env = get_jni_env(); ERR_FAIL_COND_V(env == nullptr, String(".")); - jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir, p_shared_storage); return jstring_to_string(s, env); } else { return String("."); diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h index e4c0a4b2c7..8f6d7f813f 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -46,8 +46,8 @@ private: jclass cls; jmethodID _open_URI = 0; + jmethodID _get_cache_dir = 0; jmethodID _get_data_dir = 0; - jmethodID _get_external_data_dir = 0; jmethodID _get_locale = 0; jmethodID _get_model = 0; jmethodID _get_screen_DPI = 0; @@ -66,8 +66,8 @@ public: jobject get_instance(); Error open_uri(const String &p_uri); + String get_cache_dir(); String get_user_data_dir(); - String get_external_data_dir(); String get_locale(); String get_model(); int get_screen_dpi(); @@ -80,7 +80,7 @@ public: void set_vk_height(int p_height); void set_screen_orientation(int p_orient); int get_screen_orientation(); - String get_system_dir(int p_dir); + String get_system_dir(int p_dir, bool p_shared_storage); }; #endif /* !JAVA_GODOT_IO_WRAPPER_H */ diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index c2e12442b3..21fb31d991 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -213,6 +213,10 @@ String OS_Android::get_model_name() const { return OS_Unix::get_model_name(); } +String OS_Android::get_data_path() const { + return get_user_data_dir(); +} + String OS_Android::get_user_data_dir() const { if (data_dir_cache != String()) return data_dir_cache; @@ -225,11 +229,11 @@ String OS_Android::get_user_data_dir() const { return "."; } -String OS_Android::get_external_data_dir() const { - String data_dir = godot_io_java->get_external_data_dir(); - if (data_dir != "") { - data_dir = _remove_symlink(data_dir); - return data_dir; +String OS_Android::get_cache_path() const { + String cache_dir = godot_io_java->get_cache_dir(); + if (cache_dir != "") { + cache_dir = _remove_symlink(cache_dir); + return cache_dir; } return "."; } @@ -242,8 +246,8 @@ String OS_Android::get_unique_id() const { return OS::get_unique_id(); } -String OS_Android::get_system_dir(SystemDir p_dir) const { - return godot_io_java->get_system_dir(p_dir); +String OS_Android::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { + return godot_io_java->get_system_dir(p_dir, p_shared_storage); } void OS_Android::set_display_size(const Size2i &p_size) { diff --git a/platform/android/os_android.h b/platform/android/os_android.h index a5b995a775..c938297821 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -110,14 +110,15 @@ public: virtual Error shell_open(String p_uri) override; virtual String get_user_data_dir() const override; - virtual String get_external_data_dir() const override; + virtual String get_data_path() const override; + virtual String get_cache_path() const override; virtual String get_resource_dir() const override; virtual String get_locale() const override; virtual String get_model_name() const override; virtual String get_unique_id() const override; - virtual String get_system_dir(SystemDir p_dir) const override; + virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; void vibrate_handheld(int p_duration_ms) override; diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py index ab98838e20..4dad2d5204 100644 --- a/platform/javascript/emscripten_helpers.py +++ b/platform/javascript/emscripten_helpers.py @@ -24,7 +24,10 @@ def get_build_version(): v = "%d.%d" % (version.major, version.minor) if version.patch > 0: v += ".%d" % version.patch - v += ".%s.%s" % (version.status, name) + status = version.status + if os.getenv("GODOT_VERSION_STATUS") != None: + status = str(os.getenv("GODOT_VERSION_STATUS")) + v += ".%s.%s" % (status, name) return v diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 5aa750757c..99e7ee8b5f 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -106,7 +106,7 @@ autoAddDeps(GodotConfig, '$GodotConfig'); mergeInto(LibraryManager.library, GodotConfig); const GodotFS = { - $GodotFS__deps: ['$FS', '$IDBFS', '$GodotRuntime'], + $GodotFS__deps: ['$ERRNO_CODES', '$FS', '$IDBFS', '$GodotRuntime'], $GodotFS__postset: [ 'Module["initFS"] = GodotFS.init;', 'Module["copyToFS"] = GodotFS.copy_to_fs;', 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/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 08630be8b0..2c9801f512 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -273,7 +273,7 @@ String OS_LinuxBSD::get_cache_path() const { } } -String OS_LinuxBSD::get_system_dir(SystemDir p_dir) const { +String OS_LinuxBSD::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { String xdgparam; switch (p_dir) { diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 1e06587322..35c80e3f9b 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -84,7 +84,7 @@ public: virtual String get_data_path() const override; virtual String get_cache_path() const override; - virtual String get_system_dir(SystemDir p_dir) const override; + virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; virtual Error shell_open(String p_uri) override; 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/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/os_osx.h b/platform/osx/os_osx.h index 37d30add78..df41ccd892 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -84,7 +84,7 @@ public: virtual String get_bundle_resource_dir() const override; virtual String get_godot_dir_name() const override; - virtual String get_system_dir(SystemDir p_dir) const override; + virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; Error shell_open(String p_uri) override; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index c458a0264a..c6e35fee83 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -395,7 +395,7 @@ String OS_OSX::get_godot_dir_name() const { return String(VERSION_SHORT_NAME).capitalize(); } -String OS_OSX::get_system_dir(SystemDir p_dir) const { +String OS_OSX::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { NSSearchPathDirectory id; bool found = true; 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..cf51caa6d5 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -854,12 +854,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. return Size2(r.right - r.left, r.bottom - r.top); } return Size2(); @@ -1900,7 +1901,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } 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; @@ -2563,10 +2566,13 @@ 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 +2583,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,7 +2641,6 @@ 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: { diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 2c8afaf7de..2a0a509d43 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -681,7 +681,7 @@ String OS_Windows::get_godot_dir_name() const { return String(VERSION_SHORT_NAME).capitalize(); } -String OS_Windows::get_system_dir(SystemDir p_dir) const { +String OS_Windows::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { KNOWNFOLDERID id; switch (p_dir) { diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index ea0c263b78..c4a2eda8f4 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -150,7 +150,7 @@ public: virtual String get_cache_path() const override; virtual String get_godot_dir_name() const override; - virtual String get_system_dir(SystemDir p_dir) const override; + virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; virtual String get_user_data_dir() const override; virtual String get_unique_id() const override; diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index e0b994f27d..ea491e8b0e 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -33,125 +33,17 @@ #include "scene/2d/area_2d.h" #include "scene/main/window.h" -void AudioStreamPlayer2D::_mix_audio() { - if (!stream_playback.is_valid() || !active.is_set() || - (stream_paused && !stream_paused_fade_out)) { - return; - } - - if (setseek.get() >= 0.0) { - stream_playback->start(setseek.get()); - setseek.set(-1.0); //reset seek - } - - //get data - AudioFrame *buffer = mix_buffer.ptrw(); - int buffer_size = mix_buffer.size(); - - if (stream_paused_fade_out) { - // Short fadeout ramp - buffer_size = MIN(buffer_size, 128); - } - - stream_playback->mix(buffer, pitch_scale, buffer_size); - - //write all outputs - int oc = output_count.get(); - for (int i = 0; i < oc; i++) { - Output current = outputs[i]; - - //see if current output exists, to keep volume ramp - bool found = false; - for (int j = i; j < prev_output_count; j++) { - if (prev_outputs[j].viewport == current.viewport) { - if (j != i) { - SWAP(prev_outputs[j], prev_outputs[i]); - } - found = true; - break; - } - } - - if (!found) { - //create new if was not used before - if (prev_output_count < MAX_OUTPUTS) { - prev_outputs[prev_output_count] = prev_outputs[i]; //may be owned by another viewport - prev_output_count++; - } - prev_outputs[i] = current; - } - - //mix! - AudioFrame target_volume = stream_paused_fade_out ? AudioFrame(0.f, 0.f) : current.vol; - AudioFrame vol_prev = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : prev_outputs[i].vol; - AudioFrame vol_inc = (target_volume - vol_prev) / float(buffer_size); - AudioFrame vol = vol_prev; - - int cc = AudioServer::get_singleton()->get_channel_count(); - - if (cc == 1) { - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, 0)) { - continue; //may have been removed - } - - AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, 0); - - for (int j = 0; j < buffer_size; j++) { - target[j] += buffer[j] * vol; - vol += vol_inc; - } - - } else { - AudioFrame *targets[4]; - bool valid = true; - - for (int k = 0; k < cc; k++) { - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, k)) { - valid = false; //may have been removed - break; - } - - targets[k] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, k); - } - - if (!valid) { - continue; - } - - for (int j = 0; j < buffer_size; j++) { - AudioFrame frame = buffer[j] * vol; - for (int k = 0; k < cc; k++) { - targets[k][j] += frame; - } - vol += vol_inc; - } - } - - prev_outputs[i] = current; - } - - prev_output_count = oc; - - //stream is no longer active, disable this. - if (!stream_playback->is_playing()) { - active.clear(); - } - - output_ready.clear(); - stream_paused_fade_in = false; - stream_paused_fade_out = false; -} - void AudioStreamPlayer2D::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - AudioServer::get_singleton()->add_callback(_mix_audios, this); + AudioServer::get_singleton()->add_listener_changed_callback(_listener_changed_cb, this); if (autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); } } if (p_what == NOTIFICATION_EXIT_TREE) { - AudioServer::get_singleton()->remove_callback(_mix_audios, this); + stop(); + AudioServer::get_singleton()->remove_listener_changed_callback(_listener_changed_cb, this); } if (p_what == NOTIFICATION_PAUSED) { @@ -168,116 +60,129 @@ void AudioStreamPlayer2D::_notification(int p_what) { if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course - if (!output_ready.is_set()) { - Ref<World2D> world_2d = get_world_2d(); - ERR_FAIL_COND(world_2d.is_null()); + if (!stream_playback.is_valid()) { + return; + } + if (setplay.get() >= 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { + _update_panning(); + if (setplay.get() >= 0) { + active.set(); + AudioServer::get_singleton()->start_playback_stream(stream_playback, _get_actual_bus(), volume_vector, setplay.get()); + setplay.set(-1); + } + } + + // Stop playing if no longer active. + if (active.is_set() && !AudioServer::get_singleton()->is_playback_active(stream_playback)) { + active.clear(); + set_physics_process_internal(false); + emit_signal(SNAME("finished")); + } + } +} - int new_output_count = 0; +StringName AudioStreamPlayer2D::_get_actual_bus() { + if (!stream_playback.is_valid()) { + return SNAME("Master"); + } - Vector2 global_pos = get_global_position(); + 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 + Ref<World2D> world_2d = get_world_2d(); + ERR_FAIL_COND_V(world_2d.is_null(), SNAME("Master")); - //check if any area is diverting sound into a bus + PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); + PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; - PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); + int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); - PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; + for (int i = 0; i < areas; i++) { + Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider); + if (!area2d) { + continue; + } - int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); + if (!area2d->is_overriding_audio_bus()) { + continue; + } - for (int i = 0; i < areas; i++) { - Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider); - if (!area2d) { - continue; - } + return area2d->get_audio_bus_name(); + } + return default_bus; +} - if (!area2d->is_overriding_audio_bus()) { - continue; - } +void AudioStreamPlayer2D::_update_panning() { + if (!stream_playback.is_valid()) { + return; + } - StringName bus_name = area2d->get_audio_bus_name(); - bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name); - break; - } + last_mix_count = AudioServer::get_singleton()->get_mix_count(); - const Set<Viewport *> viewports = world_2d->get_viewports(); + Ref<World2D> world_2d = get_world_2d(); + ERR_FAIL_COND(world_2d.is_null()); - 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; + Vector2 global_pos = get_global_position(); - //screen in global is used for attenuation - Vector2 screen_in_global = to_screen.affine_inverse().xform(screen_size * 0.5); + 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 dist = global_pos.distance_to(screen_in_global); //distance to screen center + 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); - if (dist > max_distance) { - continue; //can't hear this sound in this viewport - } + for (Viewport *vp : viewports) { + if (!vp->is_audio_listener_2d()) { + continue; + } + //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; - float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); - multiplier *= Math::db2linear(volume_db); //also apply player volume! + //screen in global is used for attenuation + Vector2 screen_in_global = to_screen.affine_inverse().xform(screen_size * 0.5); - //point in screen is used for panning - Vector2 point_in_screen = to_screen.xform(global_pos); + float dist = global_pos.distance_to(screen_in_global); //distance to screen center - float pan = CLAMP(point_in_screen.x / screen_size.width, 0.0, 1.0); + if (dist > max_distance) { + continue; //can't hear this sound in this viewport + } - float l = 1.0 - pan; - float r = pan; + float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); + multiplier *= Math::db2linear(volume_db); //also apply player volume! - 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; - } - } - } + //point in screen is used for panning + Vector2 point_in_screen = to_screen.xform(global_pos); - output_count.set(new_output_count); - output_ready.set(); - } + float pan = CLAMP(point_in_screen.x / screen_size.width, 0.0, 1.0); - //start playing if requested - if (setplay.get() >= 0.0) { - setseek.set(setplay.get()); - active.set(); - setplay.set(-1); - } + float l = 1.0 - pan; + float r = pan; - //stop playing if no longer active - if (!active.is_set()) { - set_physics_process_internal(false); - emit_signal(SNAME("finished")); - } + volume_vector.write[0] = AudioFrame(l, r) * multiplier; } + + AudioServer::get_singleton()->set_playback_bus_exclusive(stream_playback, _get_actual_bus(), volume_vector); } void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { - AudioServer::get_singleton()->lock(); - - 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); + stop(); } + stream_playback.unref(); + stream.unref(); if (p_stream.is_valid()) { - stream = p_stream; stream_playback = p_stream->instance_playback(); + if (stream_playback.is_valid()) { + stream = p_stream; + } else { + stream.unref(); + } } - AudioServer::get_singleton()->unlock(); - if (p_stream.is_valid() && stream_playback.is_null()) { stream.unref(); } @@ -298,6 +203,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; + if (stream_playback.is_valid()) { + AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, p_pitch_scale); + } } float AudioStreamPlayer2D::get_pitch_scale() const { @@ -305,27 +213,26 @@ 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; + stop(); + if (stream.is_valid()) { + stream_playback = stream->instance_playback(); } - if (stream_playback.is_valid()) { setplay.set(p_from_pos); - output_ready.clear(); set_physics_process_internal(true); } } void AudioStreamPlayer2D::seek(float p_seconds) { - if (stream_playback.is_valid()) { - setseek.set(p_seconds); + if (stream_playback.is_valid() && active.is_set()) { + play(p_seconds); } } void AudioStreamPlayer2D::stop() { if (stream_playback.is_valid()) { active.clear(); + AudioServer::get_singleton()->stop_playback_stream(stream_playback); set_physics_process_internal(false); setplay.set(-1); } @@ -333,7 +240,7 @@ void AudioStreamPlayer2D::stop() { bool AudioStreamPlayer2D::is_playing() const { if (stream_playback.is_valid()) { - return active.is_set() || setplay.get() >= 0; + return AudioServer::get_singleton()->is_playback_active(stream_playback); } return false; @@ -341,30 +248,23 @@ bool AudioStreamPlayer2D::is_playing() const { 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 AudioServer::get_singleton()->get_playback_position(stream_playback); } 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) { @@ -384,7 +284,11 @@ void AudioStreamPlayer2D::_set_playing(bool p_enable) { } bool AudioStreamPlayer2D::_is_active() const { - return active.is_set(); + if (stream_playback.is_valid()) { + // TODO make sure this doesn't change any behavior w.r.t. pauses. Is a paused stream active? + return AudioServer::get_singleton()->is_playback_active(stream_playback); + } + return false; } void AudioStreamPlayer2D::_validate_property(PropertyInfo &property) const { @@ -432,15 +336,17 @@ 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 the stream isn't set, we can't persist this bool. + if (stream_playback.is_valid()) { + AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause); } } bool AudioStreamPlayer2D::get_stream_paused() const { - return stream_paused; + if (stream_playback.is_valid()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playback); + } + return false; } Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() { diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index cf05a49b00..6428fbe017 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -51,38 +51,30 @@ 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; Ref<AudioStream> stream; - Vector<AudioFrame> mix_buffer; - SafeNumeric<float> setseek{ -1.0 }; SafeFlag active; 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 = "Master"; 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; diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 13b37aa2b2..bf91ce8e65 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -261,6 +261,7 @@ void Camera2D::_notification(int p_what) { if (viewport && !(custom_viewport && !ObjectDB::get_instance(custom_viewport_id))) { viewport->set_canvas_transform(Transform2D()); clear_current(); + current = true; } } remove_from_group(group_name); 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/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 00da94c3b0..8d8b187445 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, @@ -529,9 +529,9 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { sleeping = state->is_sleeping(); emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed); } - if (get_script_instance()) { - get_script_instance()->call("_integrate_forces", state); - } + + GDVIRTUAL_CALL(_integrate_forces, state); + set_block_transform_notify(false); // want it back if (contact_monitor) { @@ -978,7 +978,7 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_colliding_bodies"), &RigidBody2D::get_colliding_bodies); - BIND_VMETHOD(MethodInfo("_integrate_forces", PropertyInfo(Variant::OBJECT, "state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectBodyState2D"))); + GDVIRTUAL_BIND(_integrate_forces, "state"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Dynamic,Static,DynamicLocked,Kinematic"), "set_mode", "get_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp"), "set_mass", "get_mass"); @@ -1075,16 +1075,30 @@ 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); } } - Vector2 motion = linear_velocity * delta; + if (motion_mode == MOTION_MODE_GROUNDED) { + _move_and_slide_grounded(delta, was_on_floor, current_platform_velocity); + } else { + _move_and_slide_free(delta); + } + + if (!on_floor && !on_wall) { + // Add last platform velocity when just left a moving platform. + linear_velocity += current_platform_velocity; + } + + return motion_results.size() > 0; +} + +void CharacterBody2D::_move_and_slide_grounded(real_t p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity) { + Vector2 motion = linear_velocity * p_delta; Vector2 motion_slide_up = motion.slide(up_direction); - Vector2 prev_platform_velocity = current_platform_velocity; Vector2 prev_floor_normal = floor_normal; RID prev_platform_rid = platform_rid; int prev_platform_layer = platform_layer; @@ -1095,7 +1109,7 @@ bool CharacterBody2D::move_and_slide() { // No sliding on first attempt to keep floor motion stable when possible, // When stop on slope is enabled or when there is no up direction. - bool sliding_enabled = !floor_stop_on_slope || up_direction == Vector2(); + bool sliding_enabled = !floor_stop_on_slope; // Constant speed can be applied only the first time sliding is enabled. bool can_apply_constant_speed = sliding_enabled; bool first_slide = true; @@ -1104,14 +1118,12 @@ bool CharacterBody2D::move_and_slide() { for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer2D::MotionResult result; - bool found_collision = false; Vector2 prev_position = get_global_transform().elements[2]; bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled); if (collided) { - found_collision = true; motion_results.push_back(result); _set_collision_direction(result); @@ -1136,7 +1148,7 @@ bool CharacterBody2D::move_and_slide() { // Move on floor only checks. if (floor_block_on_wall && on_wall && motion_slide_up.dot(result.collision_normal) <= 0) { // Avoid to move forward on a wall if floor_block_on_wall is true. - if (was_on_floor && !is_on_floor_only() && !vel_dir_facing_up) { + if (p_was_on_floor && !on_floor && !vel_dir_facing_up) { // If the movement is large the body can be prevented from reaching the walls. if (result.travel.length() <= margin) { // Cancels the motion. @@ -1147,15 +1159,14 @@ bool CharacterBody2D::move_and_slide() { on_floor = true; platform_rid = prev_platform_rid; platform_layer = prev_platform_layer; - - platform_velocity = prev_platform_velocity; + platform_velocity = p_prev_platform_velocity; floor_normal = prev_floor_normal; linear_velocity = Vector2(); motion = Vector2(); break; } // Prevents the body from being able to climb a slope when it moves forward against the wall. - else if (!is_on_floor_only()) { + else if (!on_floor) { motion = up_direction * up_direction.dot(result.remainder); motion = motion.slide(result.collision_normal); } else { @@ -1163,12 +1174,10 @@ bool CharacterBody2D::move_and_slide() { } } // Constant Speed when the slope is upward. - else if (floor_constant_speed && is_on_floor_only() && can_apply_constant_speed && was_on_floor && motion.dot(result.collision_normal) < 0) { + else if (floor_constant_speed && is_on_floor_only() && can_apply_constant_speed && p_was_on_floor && motion.dot(result.collision_normal) < 0) { can_apply_constant_speed = false; Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized(); - if (!motion_slide_norm.is_equal_approx(Vector2())) { - motion = motion_slide_norm * (motion_slide_up.length() - result.travel.slide(up_direction).length() - last_travel.slide(up_direction).length()); - } + motion = motion_slide_norm * (motion_slide_up.length() - result.travel.slide(up_direction).length() - last_travel.slide(up_direction).length()); } // Regular sliding, the last part of the test handle the case when you don't want to slide on the ceiling. else if ((sliding_enabled || !on_floor) && (!on_ceiling || slide_on_ceiling || !vel_dir_facing_up)) { @@ -1201,7 +1210,7 @@ bool CharacterBody2D::move_and_slide() { } // When you move forward in a downward slope you don’t collide because you will be in the air. // This test ensures that constant speed is applied, only if the player is still on the ground after the snap is applied. - else if (floor_constant_speed && first_slide && _on_floor_if_snapped(was_on_floor, vel_dir_facing_up)) { + else if (floor_constant_speed && first_slide && _on_floor_if_snapped(p_was_on_floor, vel_dir_facing_up)) { can_apply_constant_speed = false; sliding_enabled = true; Transform2D gt = get_global_transform(); @@ -1209,49 +1218,78 @@ bool CharacterBody2D::move_and_slide() { set_global_transform(gt); Vector2 motion_slide_norm = motion.slide(prev_floor_normal).normalized(); - if (!motion_slide_norm.is_equal_approx(Vector2())) { - motion = motion_slide_norm * (motion_slide_up.length()); - found_collision = true; - } + motion = motion_slide_norm * (motion_slide_up.length()); + collided = true; } can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled; sliding_enabled = true; first_slide = false; - if (!found_collision || motion.is_equal_approx(Vector2())) { + if (!collided || motion.is_equal_approx(Vector2())) { break; } } - _snap_on_floor(was_on_floor, vel_dir_facing_up); - - if (!on_floor && !on_wall) { - // Add last platform velocity when just left a moving platform. - linear_velocity += current_platform_velocity; - } + _snap_on_floor(p_was_on_floor, vel_dir_facing_up); // Reset the gravity accumulation when touching the ground. if (on_floor && !vel_dir_facing_up) { linear_velocity = linear_velocity.slide(up_direction); } +} - return motion_results.size() > 0; +void CharacterBody2D::_move_and_slide_free(real_t p_delta) { + Vector2 motion = linear_velocity * p_delta; + + platform_rid = RID(); + floor_normal = Vector2(); + platform_velocity = Vector2(); + + bool first_slide = true; + for (int iteration = 0; iteration < max_slides; ++iteration) { + PhysicsServer2D::MotionResult result; + + bool collided = move_and_collide(motion, result, margin, false, false); + + if (collided) { + motion_results.push_back(result); + _set_collision_direction(result); + + if (free_mode_min_slide_angle != 0 && result.get_angle(-linear_velocity.normalized()) < free_mode_min_slide_angle + FLOOR_ANGLE_THRESHOLD) { + motion = Vector2(); + } else if (first_slide) { + Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized(); + motion = motion_slide_norm * (motion.length() - result.travel.length()); + } else { + motion = result.remainder.slide(result.collision_normal); + } + + if (motion.dot(linear_velocity) <= 0.0) { + motion = Vector2(); + } + } + + first_slide = false; + + if (!collided || motion.is_equal_approx(Vector2())) { + break; + } + } } void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) { - if (Math::is_equal_approx(floor_snap_length, 0) || up_direction == Vector2() || on_floor || !was_on_floor || vel_dir_facing_up) { + if (Math::is_equal_approx(floor_snap_length, 0) || on_floor || !was_on_floor || vel_dir_facing_up) { return; } 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) { @@ -1280,7 +1318,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; } @@ -1290,26 +1328,24 @@ bool CharacterBody2D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facin } void CharacterBody2D::_set_collision_direction(const PhysicsServer2D::MotionResult &p_result) { - if (up_direction == Vector2()) { - return; - } - - if (p_result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor + 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 (p_result.get_angle(-up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling + } 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) { @@ -1441,6 +1477,14 @@ void CharacterBody2D::set_moving_platform_ignore_layers(uint32_t p_exclude_layer moving_platform_ignore_layers = p_exclude_layers; } +void CharacterBody2D::set_motion_mode(MotionMode p_mode) { + motion_mode = p_mode; +} + +CharacterBody2D::MotionMode CharacterBody2D::get_motion_mode() const { + return motion_mode; +} + int CharacterBody2D::get_max_slides() const { return max_slides; } @@ -1467,11 +1511,20 @@ void CharacterBody2D::set_floor_snap_length(real_t p_floor_snap_length) { floor_snap_length = p_floor_snap_length; } +real_t CharacterBody2D::get_free_mode_min_slide_angle() const { + return free_mode_min_slide_angle; +} + +void CharacterBody2D::set_free_mode_min_slide_angle(real_t p_radians) { + free_mode_min_slide_angle = p_radians; +} + const Vector2 &CharacterBody2D::get_up_direction() const { return up_direction; } void CharacterBody2D::set_up_direction(const Vector2 &p_up_direction) { + ERR_FAIL_COND_MSG(p_up_direction == Vector2(), "up_direction can't be equal to Vector2.ZERO, consider using Free motion mode instead."); up_direction = p_up_direction.normalized(); } @@ -1515,8 +1568,12 @@ void CharacterBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_floor_max_angle", "radians"), &CharacterBody2D::set_floor_max_angle); ClassDB::bind_method(D_METHOD("get_floor_snap_length"), &CharacterBody2D::get_floor_snap_length); ClassDB::bind_method(D_METHOD("set_floor_snap_length", "floor_snap_length"), &CharacterBody2D::set_floor_snap_length); + ClassDB::bind_method(D_METHOD("get_free_mode_min_slide_angle"), &CharacterBody2D::get_free_mode_min_slide_angle); + ClassDB::bind_method(D_METHOD("set_free_mode_min_slide_angle", "radians"), &CharacterBody2D::set_free_mode_min_slide_angle); ClassDB::bind_method(D_METHOD("get_up_direction"), &CharacterBody2D::get_up_direction); ClassDB::bind_method(D_METHOD("set_up_direction", "up_direction"), &CharacterBody2D::set_up_direction); + ClassDB::bind_method(D_METHOD("set_motion_mode", "mode"), &CharacterBody2D::set_motion_mode); + ClassDB::bind_method(D_METHOD("get_motion_mode"), &CharacterBody2D::get_motion_mode); ClassDB::bind_method(D_METHOD("is_on_floor"), &CharacterBody2D::is_on_floor); ClassDB::bind_method(D_METHOD("is_on_floor_only"), &CharacterBody2D::is_on_floor_only); @@ -1531,10 +1588,13 @@ void CharacterBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody2D::_get_slide_collision); ClassDB::bind_method(D_METHOD("get_last_slide_collision"), &CharacterBody2D::_get_last_slide_collision); + ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Free", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "up_direction"), "set_up_direction", "get_up_direction"); + ADD_GROUP("Free Mode", "free_mode_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "free_mode_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_free_mode_min_slide_angle", "get_free_mode_min_slide_angle"); ADD_GROUP("Floor", "floor_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_stop_on_slope"), "set_floor_stop_on_slope_enabled", "is_floor_stop_on_slope_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_constant_speed"), "set_floor_constant_speed_enabled", "is_floor_constant_speed_enabled"); @@ -1544,6 +1604,21 @@ void CharacterBody2D::_bind_methods() { 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::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); + + BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED); + BIND_ENUM_CONSTANT(MOTION_MODE_FREE); +} + +void CharacterBody2D::_validate_property(PropertyInfo &property) const { + if (motion_mode == MOTION_MODE_FREE) { + if (property.name.begins_with("floor_") || property.name == "up_direction" || property.name == "slide_on_ceiling") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } else { + if (property.name == "free_mode_min_slide_angle") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } } CharacterBody2D::CharacterBody2D() : diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 81c5067146..885f0ace05 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(); @@ -189,6 +189,8 @@ protected: void _notification(int p_what); static void _bind_methods(); + GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState2D *) + public: void set_mode(Mode p_mode); Mode get_mode() const; @@ -268,8 +270,35 @@ VARIANT_ENUM_CAST(RigidBody2D::CCDMode); class CharacterBody2D : public PhysicsBody2D { GDCLASS(CharacterBody2D, PhysicsBody2D); +public: + enum MotionMode { + MOTION_MODE_GROUNDED, + MOTION_MODE_FREE, + }; + bool move_and_slide(); + + const Vector2 &get_linear_velocity() const; + void set_linear_velocity(const Vector2 &p_velocity); + + bool is_on_floor() const; + bool is_on_floor_only() const; + bool is_on_wall() const; + bool is_on_wall_only() const; + bool is_on_ceiling() const; + bool is_on_ceiling_only() const; + Vector2 get_floor_normal() const; + real_t get_floor_angle(const Vector2 &p_up_direction = Vector2(0.0, -1.0)) const; + Vector2 get_platform_velocity() const; + + int get_slide_collision_count() const; + PhysicsServer2D::MotionResult get_slide_collision(int p_bounce) const; + + CharacterBody2D(); + ~CharacterBody2D(); + private: real_t margin = 0.08; + MotionMode motion_mode = MOTION_MODE_GROUNDED; bool floor_stop_on_slope = false; bool floor_constant_speed = false; @@ -279,6 +308,7 @@ private: int platform_layer; real_t floor_max_angle = Math::deg2rad((real_t)45.0); float floor_snap_length = 0; + real_t free_mode_min_slide_angle = Math::deg2rad((real_t)15.0); Vector2 up_direction = Vector2(0.0, -1.0); uint32_t moving_platform_ignore_layers = 0; Vector2 linear_velocity; @@ -317,9 +347,18 @@ private: real_t get_floor_snap_length(); void set_floor_snap_length(real_t p_floor_snap_length); + 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); + 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); + Ref<KinematicCollision2D> _get_slide_collision(int p_bounce); Ref<KinematicCollision2D> _get_last_slide_collision(); const Vector2 &get_up_direction() const; @@ -332,30 +371,11 @@ private: protected: void _notification(int p_what); static void _bind_methods(); - -public: - bool move_and_slide(); - - const Vector2 &get_linear_velocity() const; - void set_linear_velocity(const Vector2 &p_velocity); - - bool is_on_floor() const; - bool is_on_floor_only() const; - bool is_on_wall() const; - bool is_on_wall_only() const; - bool is_on_ceiling() const; - bool is_on_ceiling_only() const; - Vector2 get_floor_normal() const; - real_t get_floor_angle(const Vector2 &p_up_direction = Vector2(0.0, -1.0)) const; - Vector2 get_platform_velocity() const; - - int get_slide_collision_count() const; - PhysicsServer2D::MotionResult get_slide_collision(int p_bounce) const; - - CharacterBody2D(); - ~CharacterBody2D(); + virtual void _validate_property(PropertyInfo &property) const override; }; +VARIANT_ENUM_CAST(CharacterBody2D::MotionMode); + class KinematicCollision2D : public RefCounted { GDCLASS(KinematicCollision2D, RefCounted); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 74eb3f2fc2..13f1d258a8 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -782,7 +782,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List // 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(); - int z_index = layers[q.layer].z_index + tile_data->get_z_index(); + int z_index = tile_data->get_z_index(); // Quandrant pos. Vector2 position = map_to_world(q.coords * get_effective_quadrant_size(q.layer)); @@ -1051,9 +1051,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 +1082,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 +1097,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; } } } 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..16d5b9da3e 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; - } +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); - bool started = false; - if (setseek.get() >= 0.0) { - stream_playback->start(setseek.get()); - setseek.set(-1.0); //reset seek - started = true; - } + float uniformity = area->get_reverb_uniformity(); + float area_send = area->get_reverb_amount(); - //get data - AudioFrame *buffer = mix_buffer.ptrw(); - int buffer_size = mix_buffer.size(); + if (uniformity > 0.0) { + float distance = listener_area_pos.length(); + float attenuation = Math::db2linear(_get_attenuation_db(distance)); - if (stream_paused_fade_out) { - // Short fadeout ramp - buffer_size = MIN(buffer_size, 128); - } + // 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]); - // 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 (attenuation < 1.0) { + //pan the uniform sound + Vector3 rev_pos = listener_area_pos; + rev_pos.y = 0; + rev_pos.normalize(); - stream_playback->mix(buffer, pitch_scale * output_pitch_scale, buffer_size); - } - - //write all outputs - for (int i = 0; i < output_count.get(); 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 (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) { @@ -359,277 +272,227 @@ void AudioStreamPlayer3D::_notification(int p_what) { if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course - if (!output_ready.is_set()) { - Vector3 linear_velocity; + if (!stream_playback.is_valid()) { + return; + } + //start playing if requested - //compute linear velocity for doppler - if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { - linear_velocity = velocity_tracker->get_tracked_linear_velocity(); - } + if (setplay.get() >= 0) { + Vector<AudioFrame> volume_vector = _update_panning(); + AudioServer::get_singleton()->start_playback_stream(stream_playback, _get_actual_bus(), volume_vector, setplay.get()); + active.set(); + setplay.set(-1); + } - Ref<World3D> world_3d = get_world_3d(); - ERR_FAIL_COND(world_3d.is_null()); + if (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) { + _update_panning(); + last_mix_count = AudioServer::get_singleton()->get_mix_count(); + } - int new_output_count = 0; + // Stop playing if no longer active. + if (!active.is_set()) { + set_physics_process_internal(false); + emit_signal(SNAME("finished")); + } + } +} - 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); + + for (int i = 0; i < areas; i++) { + if (!sr[i].collider) { + continue; + } - int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); - Area3D *area = nullptr; + Area3D *tarea = Object::cast_to<Area3D>(sr[i].collider); + if (!tarea) { + continue; + } + + if (!tarea->is_overriding_audio_bus() && !tarea->is_using_reverb_bus()) { + continue; + } + + return tarea; + } + return nullptr; +} + +StringName AudioStreamPlayer3D::_get_actual_bus() { + if (!stream_playback.is_valid()) { + return SNAME("Master"); + } + 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; +} + +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); + } + + ERR_FAIL_COND_V(stream_playback.is_null(), output_volume_vector); + + Vector3 linear_velocity; + + //compute linear velocity for doppler + if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { + linear_velocity = velocity_tracker->get_tracked_linear_velocity(); + } - for (int i = 0; i < areas; i++) { - if (!sr[i].collider) { - continue; - } + Vector3 global_pos = get_global_transform().origin; - Area3D *tarea = Object::cast_to<Area3D>(sr[i].collider); - if (!tarea) { - continue; - } + Ref<World3D> world_3d = get_world_3d(); + ERR_FAIL_COND_V(world_3d.is_null(), output_volume_vector); - if (!tarea->is_overriding_audio_bus() && !tarea->is_using_reverb_bus()) { - continue; - } + Set<Camera3D *> cameras = world_3d->get_cameras(); + cameras.insert(get_viewport()->get_camera_3d()); - area = tarea; - break; + PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space()); + + for (Camera3D *camera : cameras) { + Viewport *vp = camera->get_viewport(); + if (!vp->is_audio_listener_3d()) { + continue; + } + + bool listener_is_camera = true; + Node3D *listener_node = camera; + + Listener3D *listener = vp->get_listener_3d(); + if (listener) { + listener_node = listener; + listener_is_camera = false; + } + + Vector3 local_pos = listener_node->get_global_transform().orthonormalized().affine_inverse().xform(global_pos); + + float dist = local_pos.length(); + + Vector3 area_sound_pos; + Vector3 listener_area_pos; + + Area3D *area = _get_overriding_area(); + + 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 (max_distance > 0) { + float total_max = max_distance; + + 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 } + } - 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; - } - - bool listener_is_camera = true; - Node3D *listener_node = camera; - - Listener3D *listener = vp->get_listener_3d(); - if (listener) { - listener_node = listener; - listener_is_camera = false; - } - - Vector3 local_pos = listener_node->get_global_transform().orthonormalized().affine_inverse().xform(global_pos); - - float dist = local_pos.length(); - - Vector3 area_sound_pos; - Vector3 listener_area_pos; - - 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); - } - - if (max_distance > 0) { - float total_max = max_distance; - - 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)); - } - - Output output; - output.bus_index = bus_index; - output.reverb_bus_index = -1; //no reverb by default - output.viewport = vp; - - float db_att = (1.0 - MIN(1.0, multiplier)) * attenuation_filter_db; - - 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; - } - } - - output.filter_gain = Math::db2linear(db_att); - - //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); - - unsigned int cc = AudioServer::get_singleton()->get_channel_count(); - for (unsigned int k = 0; k < cc; k++) { - output.vol[k] *= multiplier; - } - - 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 (doppler_tracking != DOPPLER_TRACKING_DISABLED) { - Vector3 listener_velocity; - - if (listener_is_camera) { - listener_velocity = camera->get_doppler_tracked_velocity(); - } - - Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity); - - 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; - - 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 - } - - } else { - output.pitch_scale = 1.0; - } - - if (!filled_reverb) { - for (int i = 0; i < vol_index_max; i++) { - output.reverb_vol[i] = AudioFrame(0, 0); - } - } - - outputs[new_output_count] = output; - new_output_count++; - if (new_output_count == MAX_OUTPUTS) { - break; - } + float multiplier = Math::db2linear(_get_attenuation_db(dist)); + if (max_distance > 0) { + multiplier *= MAX(0, 1.0 - (dist / max_distance)); + } + + float db_att = (1.0 - MIN(1.0, multiplier)) * attenuation_filter_db; + + 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; } + } - output_count.set(new_output_count); - output_ready.set(); + AudioServer::get_singleton()->set_playback_highshelf_params(stream_playback, Math::db2linear(db_att), 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); + + for (unsigned int k = 0; k < 4; k++) { + output_volume_vector.write[k] = multiplier * output_volume_vector[k]; } - //start playing if requested - if (setplay.get() >= 0.0) { - setseek.set(setplay.get()); - active.set(); - setplay.set(-1); + 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; + } + + 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; } + AudioServer::get_singleton()->set_playback_bus_volumes_linear(stream_playback, bus_volumes); - //stop playing if no longer active - if (!active.is_set()) { - set_physics_process_internal(false); - emit_signal(SNAME("finished")); + if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { + Vector3 listener_velocity; + + if (listener_is_camera) { + listener_velocity = camera->get_doppler_tracked_velocity(); + } + + Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity); + + if (local_velocity == Vector3()) { + AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale); + } else { + float approaching = local_pos.normalized().dot(local_velocity.normalized()); + float velocity = local_velocity.length(); + float speed_of_sound = 343.0; + + 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 + + AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, doppler_pitch_scale); + } + } else { + AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale); } } + return output_volume_vector; } void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) { - AudioServer::get_singleton()->lock(); - - mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); - if (stream_playback.is_valid()) { + stop(); stream_playback.unref(); stream.unref(); - active.clear(); - setseek.set(-1); } if (p_stream.is_valid()) { - stream = p_stream; stream_playback = p_stream->instance_playback(); + if (stream_playback.is_valid()) { + stream = p_stream; + } else { + stream.unref(); + } } - AudioServer::get_singleton()->unlock(); - if (p_stream.is_valid() && stream_playback.is_null()) { stream.unref(); } @@ -673,27 +536,22 @@ 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_playback.is_valid()) { setplay.set(p_from_pos); - output_ready.clear(); set_physics_process_internal(true); } } void AudioStreamPlayer3D::seek(float p_seconds) { - if (stream_playback.is_valid()) { - setseek.set(p_seconds); + if (stream_playback.is_valid() && active.is_set()) { + play(p_seconds); } } void AudioStreamPlayer3D::stop() { if (stream_playback.is_valid()) { active.clear(); + AudioServer::get_singleton()->stop_playback_stream(stream_playback); set_physics_process_internal(false); setplay.set(-1); } @@ -709,11 +567,7 @@ bool AudioStreamPlayer3D::is_playing() const { 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 AudioServer::get_singleton()->get_playback_position(stream_playback); } return 0; @@ -732,7 +586,7 @@ StringName AudioStreamPlayer3D::get_bus() const { return bus; } } - return "Master"; + return SNAME("Master"); } void AudioStreamPlayer3D::set_autoplay(bool p_enable) { @@ -875,15 +729,16 @@ 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; + if (stream_playback.is_valid()) { + AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause); } } bool AudioStreamPlayer3D::get_stream_paused() const { - return stream_paused; + if (stream_playback.is_valid()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playback); + } + return false; } Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() { diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index f86e19403c..f915abad2b 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -31,10 +31,12 @@ #ifndef AUDIO_STREAM_PLAYER_3D_H #define AUDIO_STREAM_PLAYER_3D_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,31 +68,9 @@ 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; Ref<AudioStream> stream; - Vector<AudioFrame> mix_buffer; - SafeNumeric<float> setseek{ -1.0 }; SafeFlag active; SafeNumeric<float> setplay{ -1.0 }; @@ -100,17 +80,21 @@ private: float max_db = 3.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; + StringName bus = "Master"; + + uint64_t last_mix_count = -1; + + 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 _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 _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(); 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/light_3d.cpp b/scene/3d/light_3d.cpp index 508f8a70df..ab417fafdd 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -212,10 +212,6 @@ void Light3D::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NONE; } - if (get_light_type() == RS::LIGHT_SPOT && property.name == "shadow_normal_bias") { - property.usage = PROPERTY_USAGE_NONE; - } - if (get_light_type() == RS::LIGHT_DIRECTIONAL && property.name == "light_projector") { property.usage = PROPERTY_USAGE_NONE; } @@ -425,9 +421,7 @@ DirectionalLight3D::DirectionalLight3D() : set_param(PARAM_SHADOW_MAX_DISTANCE, 100); set_param(PARAM_SHADOW_FADE_START, 0.8); // Increase the default shadow bias to better suit most scenes. - // Leave normal bias untouched as it doesn't benefit DirectionalLight3D as much as OmniLight3D. set_param(PARAM_SHADOW_BIAS, 0.1); - set_param(PARAM_SHADOW_NORMAL_BIAS, 1.0); set_shadow_mode(SHADOW_PARALLEL_4_SPLITS); blend_splits = false; } @@ -468,8 +462,7 @@ OmniLight3D::OmniLight3D() : Light3D(RenderingServer::LIGHT_OMNI) { set_shadow_mode(SHADOW_CUBE); // Increase the default shadow biases to better suit most scenes. - set_param(PARAM_SHADOW_BIAS, 0.1); - set_param(PARAM_SHADOW_NORMAL_BIAS, 2.0); + set_param(PARAM_SHADOW_BIAS, 0.2); } TypedArray<String> SpotLight3D::get_configuration_warnings() const { diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 610974ff90..06a4087b60 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, @@ -599,9 +599,9 @@ void RigidBody3D::_direct_state_changed(Object *p_state) { sleeping = state->is_sleeping(); emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed); } - if (get_script_instance()) { - get_script_instance()->call("_integrate_forces", state); - } + + GDVIRTUAL_CALL(_integrate_forces, state); + set_ignore_transform_notification(false); _on_transform_changed(); @@ -1022,7 +1022,7 @@ void RigidBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_colliding_bodies"), &RigidBody3D::get_colliding_bodies); - BIND_VMETHOD(MethodInfo("_integrate_forces", PropertyInfo(Variant::OBJECT, "state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectBodyState3D"))); + 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"); @@ -1080,8 +1080,6 @@ 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 @@ -1111,11 +1109,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 +1128,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,21 +1165,16 @@ bool CharacterBody3D::move_and_slide() { sliding_enabled = true; - if (!found_collision || motion == Vector3()) { + if (!collided || motion.is_equal_approx(Vector3())) { break; } } - if (!on_floor && !on_wall) { - // Add last platform velocity when just left a moving platform. - linear_velocity += current_floor_velocity; - } - - 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 +1202,16 @@ bool CharacterBody3D::move_and_slide() { } } + if (!on_floor && !on_wall) { + // Add last platform velocity when just left a moving platform. + 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 +1229,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; + } } } } diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index 9c40f92f06..17e688451d 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); @@ -132,6 +132,8 @@ public: MODE_KINEMATIC, }; + GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState3D *) + protected: bool can_sleep = true; PhysicsDirectBodyState3D *state = nullptr; diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 857916e23d..94fb49ae81 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -173,12 +173,15 @@ void Skeleton3D::_update_process_order() { parentless_bones.clear(); for (int i = 0; i < len; i++) { + bonesptr[i].child_bones.clear(); + } + + for (int i = 0; i < len; i++) { if (bonesptr[i].parent >= len) { //validate this just in case ERR_PRINT("Bone " + itos(i) + " has invalid parent: " + itos(bonesptr[i].parent)); bonesptr[i].parent = -1; } - bonesptr[i].child_bones.clear(); if (bonesptr[i].parent != -1) { int parent_bone_idx = bonesptr[i].parent; 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/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 543545b90f..88fb960164 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -36,8 +36,9 @@ #include "servers/audio/audio_stream.h" void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const { - if (get_script_instance()) { - Array parameters = get_script_instance()->call("_get_parameter_list"); + Array parameters; + + if (GDVIRTUAL_CALL(_get_parameter_list, parameters)) { for (int i = 0; i < parameters.size(); i++) { Dictionary d = parameters[i]; ERR_CONTINUE(d.is_empty()); @@ -47,8 +48,9 @@ void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const { } Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const { - if (get_script_instance()) { - return get_script_instance()->call("_get_parameter_default_value", p_parameter); + Variant ret; + if (GDVIRTUAL_CALL(_get_parameter_default_value, p_parameter, ret)) { + return ret; } return Variant(); } @@ -72,8 +74,8 @@ Variant AnimationNode::get_parameter(const StringName &p_name) const { } void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) { - if (get_script_instance()) { - Dictionary cn = get_script_instance()->call("_get_child_nodes"); + Dictionary cn; + if (GDVIRTUAL_CALL(_get_child_nodes, cn)) { List<Variant> keys; cn.get_key_list(&keys); for (const Variant &E : keys) { @@ -298,8 +300,9 @@ String AnimationNode::get_input_name(int p_input) { } String AnimationNode::get_caption() const { - if (get_script_instance()) { - return get_script_instance()->call("_get_caption"); + String ret; + if (GDVIRTUAL_CALL(_get_caption, ret)) { + return ret; } return "Node"; @@ -329,8 +332,9 @@ void AnimationNode::remove_input(int p_index) { } double AnimationNode::process(double p_time, bool p_seek) { - if (get_script_instance()) { - return get_script_instance()->call("_process", p_time, p_seek); + double ret; + if (GDVIRTUAL_CALL(_process, p_time, p_seek, ret)) { + return ret; } return 0; @@ -357,8 +361,9 @@ bool AnimationNode::is_path_filtered(const NodePath &p_path) const { } bool AnimationNode::has_filter() const { - if (get_script_instance()) { - return get_script_instance()->call("_has_filter"); + bool ret; + if (GDVIRTUAL_CALL(_has_filter, ret)) { + return ret; } return false; @@ -390,8 +395,9 @@ void AnimationNode::_validate_property(PropertyInfo &property) const { } Ref<AnimationNode> AnimationNode::get_child_by_name(const StringName &p_name) { - if (get_script_instance()) { - return get_script_instance()->call("_get_child_by_name", p_name); + Ref<AnimationNode> ret; + if (GDVIRTUAL_CALL(_get_child_by_name, p_name, ret)) { + return ret; } return Ref<AnimationNode>(); } @@ -422,17 +428,13 @@ void AnimationNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_filter_enabled", "is_filter_enabled"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters"); - BIND_VMETHOD(MethodInfo(Variant::DICTIONARY, "_get_child_nodes")); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_get_parameter_list")); - BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_get_child_by_name", PropertyInfo(Variant::STRING, "name"))); - { - MethodInfo mi = MethodInfo(Variant::NIL, "_get_parameter_default_value", PropertyInfo(Variant::STRING_NAME, "name")); - mi.return_val.usage = PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(mi); - } - BIND_VMETHOD(MethodInfo("_process", PropertyInfo(Variant::FLOAT, "time"), PropertyInfo(Variant::BOOL, "seek"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_caption")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_filter")); + GDVIRTUAL_BIND(_get_child_nodes); + GDVIRTUAL_BIND(_get_parameter_list); + GDVIRTUAL_BIND(_get_child_by_name, "name"); + GDVIRTUAL_BIND(_get_parameter_default_value, "parameter"); + GDVIRTUAL_BIND(_process, "time", "seek"); + GDVIRTUAL_BIND(_get_caption); + GDVIRTUAL_BIND(_has_filter); ADD_SIGNAL(MethodInfo("removed_from_graph")); diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 59bbc5b4da..1e0267682e 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -112,6 +112,14 @@ protected: void _set_parent(Object *p_parent); + GDVIRTUAL0RC(Dictionary, _get_child_nodes) + GDVIRTUAL0RC(Array, _get_parameter_list) + GDVIRTUAL1RC(Ref<AnimationNode>, _get_child_by_name, StringName) + GDVIRTUAL1RC(Variant, _get_parameter_default_value, StringName) + GDVIRTUAL2RC(double, _process, double, bool) + GDVIRTUAL0RC(String, _get_caption) + GDVIRTUAL0RC(bool, _has_filter) + public: virtual void get_parameter_list(List<PropertyInfo> *r_list) const; virtual Variant get_parameter_default_value(const StringName &p_parameter) const; diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index 298d75b668..f7b7604fd5 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -31,119 +31,18 @@ #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())) { + if (stream_playback.is_valid() && active.is_set() && !AudioServer::get_singleton()->is_playback_active(stream_playback)) { active.clear(); set_process_internal(false); emit_signal(SNAME("finished")); @@ -151,7 +50,9 @@ void AudioStreamPlayer::_notification(int p_what) { } if (p_what == NOTIFICATION_EXIT_TREE) { - AudioServer::get_singleton()->remove_callback(_mix_audios, this); + if (stream_playback.is_valid()) { + AudioServer::get_singleton()->stop_playback_stream(stream_playback); + } } if (p_what == NOTIFICATION_PAUSED) { @@ -167,47 +68,21 @@ 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()) { + stop(); 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(); + if (stream_playback.is_valid()) { + stream = p_stream; + } else { + stream.unref(); + } } - AudioServer::get_singleton()->unlock(); - if (p_stream.is_valid() && stream_playback.is_null()) { stream.unref(); } @@ -219,6 +94,8 @@ Ref<AudioStream> AudioStreamPlayer::get_stream() const { void AudioStreamPlayer::set_volume_db(float p_volume) { volume_db = p_volume; + + AudioServer::get_singleton()->set_playback_all_bus_volumes_linear(stream_playback, _get_volume_vector()); } float AudioStreamPlayer::get_volume_db() const { @@ -228,6 +105,8 @@ 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; + + AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale); } float AudioStreamPlayer::get_pitch_scale() const { @@ -235,31 +114,32 @@ float AudioStreamPlayer::get_pitch_scale() const { } void AudioStreamPlayer::play(float p_from_pos) { + stop(); + if (stream.is_valid()) { + stream_playback = stream->instance_playback(); + } 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(); + AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos); active.set(); - set_process_internal(true); } } void AudioStreamPlayer::seek(float p_seconds) { - if (stream_playback.is_valid()) { - setseek.set(p_seconds); + if (stream_playback.is_valid() && active.is_set()) { + play(p_seconds); } } void AudioStreamPlayer::stop() { - if (stream_playback.is_valid() && active.is_set()) { - setstop.set(); - stop_has_priority.set(); + if (stream_playback.is_valid()) { + active.clear(); + AudioServer::get_singleton()->stop_playback_stream(stream_playback); } } bool AudioStreamPlayer::is_playing() const { if (stream_playback.is_valid()) { - return active.is_set() && !setstop.is_set(); //&& stream_playback->is_playing(); + return AudioServer::get_singleton()->is_playback_active(stream_playback); } return false; @@ -267,26 +147,22 @@ bool AudioStreamPlayer::is_playing() const { 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 AudioServer::get_singleton()->get_playback_position(stream_playback); } 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(); + if (stream_playback.is_valid()) { + AudioServer::get_singleton()->set_playback_bus_exclusive(stream_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; } } @@ -318,18 +194,61 @@ void AudioStreamPlayer::_set_playing(bool p_enable) { } bool AudioStreamPlayer::_is_active() const { - return active.is_set(); + if (stream_playback.is_valid()) { + return AudioServer::get_singleton()->is_playback_active(stream_playback); + } + 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 the stream isn't set, we can't persist this bool. + if (stream_playback.is_valid()) { + AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause); } } bool AudioStreamPlayer::get_stream_paused() const { - return stream_paused; + if (stream_playback.is_valid()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playback); + } + 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 { @@ -406,8 +325,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..76b6698c9d 100644 --- a/scene/audio/audio_stream_player.h +++ b/scene/audio/audio_stream_player.h @@ -48,22 +48,13 @@ public: private: Ref<AudioStreamPlayback> stream_playback; 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 = "Master"; MixTarget mix_target = MIX_TARGET_STEREO; @@ -77,6 +68,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); diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index df1aece80a..f4e477b613 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -88,7 +88,7 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra } else if (p_msg == "override_camera_2D:transform") { ERR_FAIL_COND_V(p_args.size() < 1, ERR_INVALID_DATA); - Transform2D transform = p_args[1]; + Transform2D transform = p_args[0]; scene_tree->get_root()->set_canvas_transform_override(transform); #ifndef _3D_DISABLED } else if (p_msg == "override_camera_3D:set") { diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index d252a4507c..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 @@ -121,17 +121,13 @@ void BaseButton::_notification(int p_what) { } void BaseButton::_pressed() { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_pressed); - } + GDVIRTUAL_CALL(_pressed); pressed(); emit_signal(SNAME("pressed")); } void BaseButton::_toggled(bool p_pressed) { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, p_pressed); - } + GDVIRTUAL_CALL(_toggled, p_pressed); toggled(p_pressed); emit_signal(SNAME("toggled"), p_pressed); } @@ -342,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()) { @@ -411,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); @@ -440,8 +434,8 @@ void BaseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &BaseButton::set_shortcut_context); ClassDB::bind_method(D_METHOD("get_shortcut_context"), &BaseButton::get_shortcut_context); - BIND_VMETHOD(MethodInfo("_pressed")); - BIND_VMETHOD(MethodInfo("_toggled", PropertyInfo(Variant::BOOL, "button_pressed"))); + GDVIRTUAL_BIND(_pressed); + GDVIRTUAL_BIND(_toggled, "button_pressed"); ADD_SIGNAL(MethodInfo("pressed")); ADD_SIGNAL(MethodInfo("button_up")); diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index d86b35daf0..cd6e78464f 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -75,12 +75,15 @@ 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; + GDVIRTUAL0(_pressed) + GDVIRTUAL1(_toggled, bool) + public: enum DrawMode { DRAW_NORMAL, diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 08dd7f28eb..5f3ab18cca 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; } @@ -450,7 +450,7 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } if (k->is_action("ui_text_backspace", true)) { backspace(); - _filter_code_completion_candidates(); + _filter_code_completion_candidates_impl(); accept_event(); return; } @@ -519,10 +519,10 @@ 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(); + _filter_code_completion_candidates_impl(); } } @@ -557,7 +557,7 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { /* Text manipulation */ // Overridable actions -void CodeEdit::_handle_unicode_input(const uint32_t p_unicode) { +void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { bool had_selection = has_selection(); if (had_selection) { begin_complex_operation(); @@ -609,7 +609,7 @@ void CodeEdit::_handle_unicode_input(const uint32_t p_unicode) { } } -void CodeEdit::_backspace() { +void CodeEdit::_backspace_internal() { if (!is_editable()) { return; } @@ -1739,9 +1739,7 @@ String CodeEdit::get_text_for_code_completion() const { } void CodeEdit::request_code_completion(bool p_force) { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_request_code_completion")) { - si->call("_request_code_completion", p_force); + if (GDVIRTUAL_CALL(_request_code_completion, p_force)) { return; } @@ -1798,7 +1796,7 @@ void CodeEdit::update_code_completion_options(bool p_forced) { code_completion_forced = p_forced; code_completion_option_sources = code_completion_option_submitted; code_completion_option_submitted.clear(); - _filter_code_completion_candidates(); + _filter_code_completion_candidates_impl(); } TypedArray<Dictionary> CodeEdit::get_code_completion_options() const { @@ -1855,11 +1853,10 @@ void CodeEdit::confirm_code_completion(bool p_replace) { return; } - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_confirm_code_completion")) { - si->call("_confirm_code_completion", p_replace); + if (GDVIRTUAL_CALL(_confirm_code_completion, p_replace)) { return; } + begin_complex_operation(); int caret_line = get_caret_line(); @@ -2179,9 +2176,10 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_code_comletion_prefixes"), &CodeEdit::get_code_completion_prefixes); // Overridable - BIND_VMETHOD(MethodInfo("_confirm_code_completion", PropertyInfo(Variant::BOOL, "replace"))); - BIND_VMETHOD(MethodInfo("_request_code_completion", PropertyInfo(Variant::BOOL, "force"))); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_filter_code_completion_candidates", PropertyInfo(Variant::ARRAY, "candidates"))); + + GDVIRTUAL_BIND(_confirm_code_completion, "replace") + GDVIRTUAL_BIND(_request_code_completion, "force") + GDVIRTUAL_BIND(_filter_code_completion_candidates, "candidates") /* Line length guidelines */ ClassDB::bind_method(D_METHOD("set_line_length_guidelines", "guideline_columns"), &CodeEdit::set_line_length_guidelines); @@ -2650,11 +2648,10 @@ TypedArray<String> CodeEdit::_get_delimiters(DelimiterType p_type) const { } /* Code Completion */ -void CodeEdit::_filter_code_completion_candidates() { - ScriptInstance *si = get_script_instance(); +void CodeEdit::_filter_code_completion_candidates_impl() { int line_height = get_line_height(); - if (si && si->has_method("_filter_code_completion_candidates")) { + if (GDVIRTUAL_IS_OVERRIDDEN(_filter_code_completion_candidates)) { code_completion_options.clear(); code_completion_base = ""; @@ -2674,7 +2671,9 @@ void CodeEdit::_filter_code_completion_candidates() { i++; } - TypedArray<Dictionary> completion_options = si->call("_filter_code_completion_candidates", completion_options_sources); + Array completion_options; + + GDVIRTUAL_CALL(_filter_code_completion_candidates, completion_options_sources, completion_options); /* No options to complete, cancel. */ if (completion_options.size() == 0) { diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 76ac15f553..4fbb5194e6 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -219,7 +219,7 @@ private: List<ScriptCodeCompletionOption> code_completion_option_sources; String code_completion_base; - void _filter_code_completion_candidates(); + void _filter_code_completion_candidates_impl(); /* Line length guidelines */ TypedArray<int> line_length_guideline_columns; @@ -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(); @@ -256,8 +256,12 @@ protected: /* Text manipulation */ // Overridable actions - virtual void _handle_unicode_input(const uint32_t p_unicode) override; - virtual void _backspace() override; + virtual void _handle_unicode_input_internal(const uint32_t p_unicode) override; + virtual void _backspace_internal() override; + + GDVIRTUAL1(_confirm_code_completion, bool) + GDVIRTUAL1(_request_code_completion, bool) + GDVIRTUAL1RC(Array, _filter_code_completion_candidates, TypedArray<Dictionary>) public: /* General overrides */ diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 261480bcdd..0dddb2b09a 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 { @@ -1266,17 +1276,13 @@ ColorPicker::ColorPicker() : add_child(preset_separator); preset_container->set_h_size_flags(SIZE_EXPAND_FILL); + preset_container->set_columns(preset_column_count); add_child(preset_container); - 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 @@ -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 f114e06c75..4ac6a58d36 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -730,14 +730,9 @@ Variant Control::get_drag_data(const Point2 &p_point) { } } - if (get_script_instance()) { - Variant v = p_point; - const Variant *p = &v; - Callable::CallError ce; - Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_get_drag_data, &p, 1, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return ret; - } + Variant dd; + if (GDVIRTUAL_CALL(_get_drag_data, p_point, dd)) { + return dd; } return Variant(); @@ -752,16 +747,10 @@ bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const } } - if (get_script_instance()) { - Variant v = p_point; - const Variant *p[2] = { &v, &p_data }; - Callable::CallError ce; - Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_can_drop_data, p, 2, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return ret; - } + bool ret; + if (GDVIRTUAL_CALL(_can_drop_data, p_point, p_data, ret)) { + return ret; } - return false; } @@ -775,15 +764,7 @@ void Control::drop_data(const Point2 &p_point, const Variant &p_data) { } } - if (get_script_instance()) { - Variant v = p_point; - const Variant *p[2] = { &v, &p_data }; - Callable::CallError ce; - Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_drop_data, p, 2, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return; - } - } + GDVIRTUAL_CALL(_drop_data, p_point, p_data); } void Control::force_drag(const Variant &p_data, Control *p_control) { @@ -799,16 +780,26 @@ 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 { - ScriptInstance *si = const_cast<Control *>(this)->get_script_instance(); - if (si) { - Callable::CallError ce; - Variant s = si->call(SceneStringNames::get_singleton()->_get_minimum_size, nullptr, 0, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return s; - } + Vector2 ms; + if (GDVIRTUAL_CALL(_get_minimum_size, ms)) { + return ms; } - return Size2(); + return Vector2(); } template <class T> @@ -2123,8 +2114,9 @@ String Control::get_tooltip(const Point2 &p_pos) const { } Control *Control::make_custom_tooltip(const String &p_text) const { - if (get_script_instance()) { - return const_cast<Control *>(this)->call("_make_custom_tooltip", p_text); + Object *ret = nullptr; + if (GDVIRTUAL_CALL(_make_custom_tooltip, p_text, ret)) { + return Object::cast_to<Control>(ret); } return nullptr; } @@ -2499,14 +2491,11 @@ Vector<Vector2i> Control::structured_text_parser(StructuredTextParser p_theme_ty } } break; case STRUCTURED_TEXT_CUSTOM: { - if (get_script_instance()) { - Variant data = get_script_instance()->call(SceneStringNames::get_singleton()->_structured_text_parser, p_args, p_text); - if (data.get_type() == Variant::ARRAY) { - Array _data = data; - for (int i = 0; i < _data.size(); i++) { - if (_data[i].get_type() == Variant::VECTOR2I) { - ret.push_back(Vector2i(_data[i])); - } + Array r; + if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, r)) { + for (int i = 0; i < r.size(); i++) { + if (r[i].get_type() == Variant::VECTOR2I) { + ret.push_back(Vector2i(r[i])); } } } @@ -2823,21 +2812,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("_structured_text_parser", PropertyInfo(Variant::ARRAY, "args"), PropertyInfo(Variant::STRING, "text"))); - - BIND_VMETHOD(MethodInfo("_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); - BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_get_minimum_size")); - - MethodInfo get_drag_data = MethodInfo("_get_drag_data", PropertyInfo(Variant::VECTOR2, "position")); - get_drag_data.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(get_drag_data); - - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); - BIND_VMETHOD(MethodInfo("_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); - BIND_VMETHOD(MethodInfo( - PropertyInfo(Variant::OBJECT, "control", PROPERTY_HINT_RESOURCE_TYPE, "Control"), - "_make_custom_tooltip", PropertyInfo(Variant::STRING, "for_text"))); - 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); @@ -2994,5 +2968,14 @@ void Control::_bind_methods() { ADD_SIGNAL(MethodInfo("minimum_size_changed")); ADD_SIGNAL(MethodInfo("theme_changed")); - GDVIRTUAL_BIND(_has_point); + GDVIRTUAL_BIND(_has_point, "position"); + 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(_gui_input, "event"); } diff --git a/scene/gui/control.h b/scene/gui/control.h index a871a8e9fb..0faa617f8d 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" @@ -263,6 +263,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,7 +274,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) protected: virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; @@ -291,6 +292,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, @@ -333,6 +345,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/file_dialog.cpp b/scene/gui/file_dialog.cpp index 2e4204e171..2512b24623 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); 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..bbab3008e2 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -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 fcecbb5fca..0281bc7efb 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()) { @@ -805,68 +801,36 @@ bool GraphEdit::is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos, c return true; } -template <class Vector2> -static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, Vector2 start, Vector2 control_1, Vector2 control_2, Vector2 end) { - /* Formula from Wikipedia article on Bezier curves. */ - real_t omt = (1.0 - t); - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = t * t; - real_t t3 = t2 * t; - - return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; -} - -void GraphEdit::_bake_segment2d(Vector<Vector2> &points, Vector<Color> &colors, float p_begin, float p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_min_depth, int p_max_depth, float p_tol, const Color &p_color, const Color &p_to_color, int &lines) const { - float mp = p_begin + (p_end - p_begin) * 0.5; - Vector2 beg = _bezier_interp(p_begin, p_a, p_a + p_out, p_b + p_in, p_b); - Vector2 mid = _bezier_interp(mp, p_a, p_a + p_out, p_b + p_in, p_b); - Vector2 end = _bezier_interp(p_end, p_a, p_a + p_out, p_b + p_in, p_b); - - Vector2 na = (mid - beg).normalized(); - Vector2 nb = (end - mid).normalized(); - float dp = Math::rad2deg(Math::acos(na.dot(nb))); - - if (p_depth >= p_min_depth && (dp < p_tol || p_depth >= p_max_depth)) { - points.push_back((beg + end) * 0.5); - colors.push_back(p_color.lerp(p_to_color, mp)); - lines++; - } else { - _bake_segment2d(points, colors, p_begin, mp, p_a, p_out, p_b, p_in, p_depth + 1, p_min_depth, p_max_depth, p_tol, p_color, p_to_color, lines); - _bake_segment2d(points, colors, mp, p_end, p_a, p_out, p_b, p_in, p_depth + 1, p_min_depth, p_max_depth, p_tol, p_color, p_to_color, lines); +PackedVector2Array GraphEdit::get_connection_line(const Vector2 &p_from, const Vector2 &p_to) { + Vector<Vector2> ret; + if (GDVIRTUAL_CALL(_get_connection_line, p_from, p_to, ret)) { + return ret; } -} -void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio) { - //cubic bezier code - float diff = p_to.x - p_from.x; - float cp_offset; - int cp_len = get_theme_constant(SNAME("bezier_len_pos")) * p_bezier_ratio; - int cp_neg_len = get_theme_constant(SNAME("bezier_len_neg")) * p_bezier_ratio; - - if (diff > 0) { - cp_offset = MIN(cp_len, diff * 0.5); - } else { - cp_offset = MAX(MIN(cp_len - diff, cp_neg_len), -diff * 0.5); - } - - Vector2 c1 = Vector2(cp_offset * zoom, 0); - Vector2 c2 = Vector2(-cp_offset * zoom, 0); - - int lines = 0; + Curve2D curve; + Vector<Color> colors; + curve.add_point(p_from); + curve.set_point_out(0, Vector2(60, 0)); + curve.add_point(p_to); + curve.set_point_in(1, Vector2(-60, 0)); + return curve.tessellate(); +} - Vector<Point2> points; +void GraphEdit::_draw_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_zoom) { + Vector<Vector2> points = get_connection_line(p_from / p_zoom, p_to / p_zoom); + Vector<Vector2> scaled_points; Vector<Color> colors; - points.push_back(p_from); - colors.push_back(p_color); - _bake_segment2d(points, colors, 0, 1, p_from, c1, p_to, c2, 0, 3, 9, 3, p_color, p_to_color, lines); - points.push_back(p_to); - colors.push_back(p_to_color); + float length = (p_from / p_zoom).distance_to(p_to / p_zoom); + for (int i = 0; i < points.size(); i++) { + 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); + } #ifdef TOOLS_ENABLED - p_where->draw_polyline_colors(points, colors, Math::floor(p_width * EDSCALE), lines_antialiased); + p_where->draw_polyline_colors(scaled_points, colors, Math::floor(p_width * EDSCALE), lines_antialiased); #else - p_where->draw_polyline_colors(points, colors, p_width, lines_antialiased); + p_where->draw_polyline_colors(scaled_points, colors, p_width, lines_antialiased); #endif } @@ -913,7 +877,7 @@ void GraphEdit::_connections_layer_draw() { color = color.lerp(activity_color, E->get().activity); tocolor = tocolor.lerp(activity_color, E->get().activity); } - _draw_cos_line(connections_layer, frompos, topos, color, tocolor, lines_thickness); + _draw_connection_line(connections_layer, frompos, topos, color, tocolor, lines_thickness, zoom); } while (to_erase.size()) { @@ -952,7 +916,7 @@ void GraphEdit::_top_layer_draw() { if (!connecting_out) { SWAP(pos, topos); } - _draw_cos_line(top_layer, pos, topos, col, col, lines_thickness); + _draw_connection_line(top_layer, pos, topos, col, col, lines_thickness, zoom); } if (box_selecting) { @@ -1056,7 +1020,7 @@ void GraphEdit::_minimap_draw() { from_color = from_color.lerp(activity_color, E.activity); to_color = to_color.lerp(activity_color, E.activity); } - _draw_cos_line(minimap, from_position, to_position, from_color, to_color, 1.0, 0.5); + _draw_connection_line(minimap, from_position, to_position, from_color, to_color, 0.1, minimap->_convert_from_graph_position(Vector2(zoom, zoom)).length()); } // Draw the "camera" viewport. @@ -1080,7 +1044,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; @@ -2048,7 +2012,6 @@ void GraphEdit::arrange_nodes() { if (gn->is_selected()) { selected_nodes.insert(gn->get_name()); - origin = origin < gn->get_position_offset() ? origin : gn->get_position_offset(); Set<StringName> s; for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from]); @@ -2072,6 +2035,11 @@ void GraphEdit::arrange_nodes() { } } + if (!selected_nodes.size()) { + arranging_graph = false; + return; + } + HashMap<int, Vector<StringName>> layers = _layering(selected_nodes, upper_neighbours); _crossing_minimisation(layers, upper_neighbours); @@ -2098,16 +2066,16 @@ void GraphEdit::arrange_nodes() { for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) { _place_block(E->get(), gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions); } + origin.y = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().y - (new_positions[layers[0][0]].y + (float)inner_shift[layers[0][0]]); + origin.x = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().x; for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) { StringName u = E->get(); - StringName prev = u; float start_from = origin.y + new_positions[E->get()].y; do { Vector2 cal_pos; cal_pos.y = start_from + (real_t)inner_shift[u]; new_positions.set(u, cal_pos); - prev = u; u = align[u]; } while (u != E->get()); } @@ -2130,10 +2098,11 @@ void GraphEdit::arrange_nodes() { if (current_node_size == largest_node_size) { cal_pos.x = start_from; } else { - float current_node_start_pos; - if (current_node_size >= largest_node_size / 2) { - current_node_start_pos = start_from; - } else { + float current_node_start_pos = start_from; + if (current_node_size < largest_node_size / 2) { + if (!(i || j)) { + start_from -= (largest_node_size - current_node_size); + } current_node_start_pos = start_from + largest_node_size - current_node_size; } cal_pos.x = current_node_start_pos; @@ -2179,6 +2148,7 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("add_valid_connection_type", "from_type", "to_type"), &GraphEdit::add_valid_connection_type); ClassDB::bind_method(D_METHOD("remove_valid_connection_type", "from_type", "to_type"), &GraphEdit::remove_valid_connection_type); ClassDB::bind_method(D_METHOD("is_valid_connection_type", "from_type", "to_type"), &GraphEdit::is_valid_connection_type); + ClassDB::bind_method(D_METHOD("get_connection_line", "from", "to"), &GraphEdit::get_connection_line); ClassDB::bind_method(D_METHOD("set_zoom", "zoom"), &GraphEdit::set_zoom); ClassDB::bind_method(D_METHOD("get_zoom"), &GraphEdit::get_zoom); @@ -2218,7 +2188,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); @@ -2227,6 +2196,8 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected); + 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"); ADD_PROPERTY(PropertyInfo(Variant::INT, "snap_distance"), "set_snap", "get_snap"); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 9fd7cbef22..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); }; @@ -169,9 +167,8 @@ private: float lines_thickness = 2.0f; bool lines_antialiased = true; - void _bake_segment2d(Vector<Vector2> &points, Vector<Color> &colors, float p_begin, float p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_min_depth, int p_max_depth, float p_tol, const Color &p_color, const Color &p_to_color, int &lines) const; - - void _draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio = 1.0); + PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to); + void _draw_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_zoom); void _graph_node_raised(Node *p_gn); void _graph_node_moved(Node *p_gn); @@ -179,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; @@ -256,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..8297de9f30 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"); 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 2b3be1d5c2..d524e78caa 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: @@ -946,6 +946,17 @@ void LineEdit::paste_text() { } } +bool LineEdit::has_undo() const { + if (undo_stack_pos == nullptr) { + return undo_stack.size() > 1; + } + return undo_stack_pos != undo_stack.front(); +} + +bool LineEdit::has_redo() const { + return undo_stack_pos != nullptr && undo_stack_pos != undo_stack.back(); +} + void LineEdit::undo() { if (!editable) { return; @@ -1520,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(); @@ -1930,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)); @@ -2073,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); @@ -2277,6 +2288,11 @@ void LineEdit::_ensure_menu() { menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); + + if (editable) { + menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo()); + menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo()); + } } LineEdit::LineEdit() { diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index c5c92d60aa..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; @@ -285,6 +285,8 @@ public: void copy_text(); void cut_text(); void paste_text(); + bool has_undo() const; + bool has_redo() const; void undo(); void redo(); diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index dc6c7fec28..e312aaed57 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 { 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/popup_menu.cpp b/scene/gui/popup_menu.cpp index 4bd88fde5f..f75d6755a8 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -251,7 +251,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()) { @@ -358,9 +358,10 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { } int button_idx = b->get_button_index(); - if (b->is_pressed() || (!b->is_pressed() && during_grabbed_click)) { - // Allow activating item by releasing the LMB or any that was down when the popup appeared. - // However, if button was not held when opening menu, do not allow release to activate item. + if (!b->is_pressed()) { + // Activate the item on release of either the left mouse button or + // any mouse button held down when the popup was opened. + // This allows for opening the popup and triggering an action in a single mouse click. if (button_idx == MOUSE_BUTTON_LEFT || (initial_button_mask & (1 << (button_idx - 1)))) { bool was_during_grabbed_click = during_grabbed_click; during_grabbed_click = false; @@ -1580,8 +1581,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)); @@ -1707,7 +1706,7 @@ PopupMenu::PopupMenu() { scroll_container->add_child(control); 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); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index aedc5d0155..5c427e43bc 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(); diff --git a/scene/gui/rich_text_effect.cpp b/scene/gui/rich_text_effect.cpp index 39718a269a..236d106af8 100644 --- a/scene/gui/rich_text_effect.cpp +++ b/scene/gui/rich_text_effect.cpp @@ -32,8 +32,15 @@ #include "core/object/script_language.h" -void RichTextEffect::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_process_custom_fx", PropertyInfo(Variant::OBJECT, "char_fx", PROPERTY_HINT_RESOURCE_TYPE, "CharFXTransform"))); +CharFXTransform::CharFXTransform() { +} + +CharFXTransform::~CharFXTransform() { + environment.clear(); +} + +void RichTextEffect::_bind_methods(){ + GDVIRTUAL_BIND(_process_custom_fx, "char_fx") } Variant RichTextEffect::get_bbcode() const { @@ -49,15 +56,10 @@ Variant RichTextEffect::get_bbcode() const { bool RichTextEffect::_process_effect_impl(Ref<CharFXTransform> p_cfx) { bool return_value = false; - if (get_script_instance()) { - Variant v = get_script_instance()->call("_process_custom_fx", p_cfx); - if (v.get_type() != Variant::BOOL) { - return_value = false; - } else { - return_value = (bool)v; - } + if (GDVIRTUAL_CALL(_process_custom_fx, p_cfx, return_value)) { + return return_value; } - return return_value; + return false; } RichTextEffect::RichTextEffect() { @@ -101,10 +103,3 @@ void CharFXTransform::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_index"), "set_glyph_index", "get_glyph_index"); ADD_PROPERTY(PropertyInfo(Variant::RID, "font"), "set_font", "get_font"); } - -CharFXTransform::CharFXTransform() { -} - -CharFXTransform::~CharFXTransform() { - environment.clear(); -} diff --git a/scene/gui/rich_text_effect.h b/scene/gui/rich_text_effect.h index 67323e7f93..f5506542bb 100644 --- a/scene/gui/rich_text_effect.h +++ b/scene/gui/rich_text_effect.h @@ -32,20 +32,8 @@ #define RICH_TEXT_EFFECT_H #include "core/io/resource.h" - -class RichTextEffect : public Resource { - GDCLASS(RichTextEffect, Resource); - OBJ_SAVE_TYPE(RichTextEffect); - -protected: - static void _bind_methods(); - -public: - Variant get_bbcode() const; - bool _process_effect_impl(Ref<class CharFXTransform> p_cfx); - - RichTextEffect(); -}; +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" class CharFXTransform : public RefCounted { GDCLASS(CharFXTransform, RefCounted); @@ -61,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(); @@ -80,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; }; @@ -89,4 +77,20 @@ public: void set_environment(Dictionary p_environment) { environment = p_environment; } }; +class RichTextEffect : public Resource { + GDCLASS(RichTextEffect, Resource); + OBJ_SAVE_TYPE(RichTextEffect); + +protected: + static void _bind_methods(); + + GDVIRTUAL1RC(bool, _process_custom_fx, Ref<CharFXTransform>) + +public: + Variant get_bbcode() const; + bool _process_effect_impl(Ref<class CharFXTransform> p_cfx); + + RichTextEffect(); +}; + #endif // RICH_TEXT_EFFECT_H diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index cf2a1481a1..b7519af4aa 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) { @@ -1127,7 +1127,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); while (ofs.y < size.height && from_line < main->lines.size()) { _find_click_in_line(p_frame, from_line, ofs, text_rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char); - ofs.y += main->lines[from_line].text_buf->get_size().y; + ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) { if (r_outside != nullptr) { *r_outside = false; @@ -1435,7 +1435,7 @@ void RichTextLabel::_notification(int p_what) { while (ofs.y < size.height && from_line < main->lines.size()) { visible_paragraph_count++; visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, use_outline, shadow_ofs); - ofs.y += main->lines[from_line].text_buf->get_size().y; + ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); from_line++; } } break; @@ -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; @@ -3993,7 +3993,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); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 28dfe74b08..ae04a7e684 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -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; 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..b5daa8b3f1 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(); @@ -568,7 +568,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); 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..65a3eb3adf 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); 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..5884d2a854 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; @@ -1193,7 +1193,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); 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 a2c5769947..a731f80741 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -888,7 +888,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; @@ -1305,7 +1305,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(); @@ -2150,7 +2150,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 { @@ -2768,48 +2767,38 @@ Point2i TextEdit::get_next_visible_line_index_offset_from(int p_line_from, int p // Overridable actions void TextEdit::handle_unicode_input(const uint32_t p_unicode) { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_handle_unicode_input")) { - si->call("_handle_unicode_input", p_unicode); + if (GDVIRTUAL_CALL(_handle_unicode_input, p_unicode)) { return; } - _handle_unicode_input(p_unicode); + _handle_unicode_input_internal(p_unicode); } void TextEdit::backspace() { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_backspace")) { - si->call("_backspace"); + if (GDVIRTUAL_CALL(_backspace)) { return; } - _backspace(); + _backspace_internal(); } void TextEdit::cut() { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_cut")) { - si->call("_cut"); + if (GDVIRTUAL_CALL(_cut)) { return; } - _cut(); + _cut_internal(); } void TextEdit::copy() { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_copy")) { - si->call("_copy"); + if (GDVIRTUAL_CALL(_copy)) { return; } - _copy(); + _copy_internal(); } void TextEdit::paste() { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_paste")) { - si->call("_paste"); + if (GDVIRTUAL_CALL(_paste)) { return; } - _paste(); + _paste_internal(); } // Context menu. @@ -2963,6 +2952,18 @@ void TextEdit::end_complex_operation() { undo_stack.back()->get().chain_backward = true; } +bool TextEdit::has_undo() const { + if (undo_stack_pos == nullptr) { + int pending = current_op.type == TextOperation::TYPE_NONE ? 0 : 1; + return undo_stack.size() + pending > 0; + } + return undo_stack_pos != undo_stack.front(); +} + +bool TextEdit::has_redo() const { + return undo_stack_pos != nullptr; +} + void TextEdit::undo() { if (!editable) { return; @@ -4365,7 +4366,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 */ @@ -4440,12 +4441,11 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy); ClassDB::bind_method(D_METHOD("paste"), &TextEdit::paste); - BIND_VMETHOD(MethodInfo("_handle_unicode_input", PropertyInfo(Variant::INT, "unicode"))) - BIND_VMETHOD(MethodInfo("_backspace")); - - BIND_VMETHOD(MethodInfo("_cut")); - BIND_VMETHOD(MethodInfo("_copy")); - BIND_VMETHOD(MethodInfo("_paste")); + GDVIRTUAL_BIND(_handle_unicode_input, "unicode_char") + GDVIRTUAL_BIND(_backspace) + GDVIRTUAL_BIND(_cut) + GDVIRTUAL_BIND(_copy) + GDVIRTUAL_BIND(_paste) // Context Menu BIND_ENUM_CONSTANT(MENU_CUT); @@ -4482,6 +4482,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("begin_complex_operation"), &TextEdit::begin_complex_operation); ClassDB::bind_method(D_METHOD("end_complex_operation"), &TextEdit::end_complex_operation); + ClassDB::bind_method(D_METHOD("has_undo"), &TextEdit::has_undo); + ClassDB::bind_method(D_METHOD("has_redo"), &TextEdit::has_redo); ClassDB::bind_method(D_METHOD("undo"), &TextEdit::undo); ClassDB::bind_method(D_METHOD("redo"), &TextEdit::redo); ClassDB::bind_method(D_METHOD("clear_undo_history"), &TextEdit::clear_undo_history); @@ -4870,7 +4872,7 @@ void TextEdit::_set_symbol_lookup_word(const String &p_symbol) { /* Text manipulation */ // Overridable actions -void TextEdit::_handle_unicode_input(const uint32_t p_unicode) { +void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { if (!editable) { return; } @@ -4901,11 +4903,16 @@ void TextEdit::_handle_unicode_input(const uint32_t p_unicode) { } } -void TextEdit::_backspace() { +void TextEdit::_backspace_internal() { if (!editable) { return; } + if (has_selection()) { + delete_selection(); + return; + } + int cc = get_caret_column(); int cl = get_caret_line(); @@ -4913,11 +4920,6 @@ void TextEdit::_backspace() { return; } - if (has_selection()) { - delete_selection(); - return; - } - int prev_line = cc ? cl : cl - 1; int prev_column = cc ? (cc - 1) : (text[cl - 1].length()); @@ -4932,7 +4934,7 @@ void TextEdit::_backspace() { set_caret_column(prev_column); } -void TextEdit::_cut() { +void TextEdit::_cut_internal() { if (!editable) { return; } @@ -4962,7 +4964,7 @@ void TextEdit::_cut() { cut_copy_line = clipboard; } -void TextEdit::_copy() { +void TextEdit::_copy_internal() { if (has_selection()) { DisplayServer::get_singleton()->clipboard_set(get_selected_text()); cut_copy_line = ""; @@ -4977,7 +4979,7 @@ void TextEdit::_copy() { } } -void TextEdit::_paste() { +void TextEdit::_paste_internal() { if (!editable) { return; } @@ -5070,6 +5072,11 @@ void TextEdit::_generate_context_menu() { menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); + + if (editable) { + menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo()); + menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo()); + } } int TextEdit::_get_menu_action_accelerator(const String &p_action) { diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index da322a7bcd..ced03e19d0 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -528,7 +528,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(); @@ -562,12 +562,18 @@ protected: /* Text manipulation */ // Overridable actions - virtual void _handle_unicode_input(const uint32_t p_unicode); - virtual void _backspace(); + virtual void _handle_unicode_input_internal(const uint32_t p_unicode); + virtual void _backspace_internal(); - virtual void _cut(); - virtual void _copy(); - virtual void _paste(); + virtual void _cut_internal(); + virtual void _copy_internal(); + virtual void _paste_internal(); + + GDVIRTUAL1(_handle_unicode_input, int) + GDVIRTUAL0(_backspace) + GDVIRTUAL0(_cut) + GDVIRTUAL0(_copy) + GDVIRTUAL0(_paste) public: /* General overrides. */ @@ -659,6 +665,8 @@ public: void begin_complex_operation(); void end_complex_operation(); + bool has_undo() const; + bool has_redo() const; void undo(); void redo(); void clear_undo_history(); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 09db3205d9..3e46e2749b 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -167,6 +167,18 @@ TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const { void TreeItem::set_checked(int p_column, bool p_checked) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].checked = p_checked; + cells.write[p_column].indeterminate = false; + _changed_notify(p_column); +} + +void TreeItem::set_indeterminate(int p_column, bool p_indeterminate) { + ERR_FAIL_INDEX(p_column, cells.size()); + // Prevent uncheck if indeterminate set to false twice + if (p_indeterminate == cells[p_column].indeterminate) { + return; + } + cells.write[p_column].indeterminate = p_indeterminate; + cells.write[p_column].checked = false; _changed_notify(p_column); } @@ -175,6 +187,11 @@ bool TreeItem::is_checked(int p_column) const { return cells[p_column].checked; } +bool TreeItem::is_indeterminate(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), false); + return cells[p_column].indeterminate; +} + void TreeItem::set_text(int p_column, String p_text) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].text = p_text; @@ -872,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; @@ -1042,7 +1070,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_cell_mode", "column"), &TreeItem::get_cell_mode); ClassDB::bind_method(D_METHOD("set_checked", "column", "checked"), &TreeItem::set_checked); + ClassDB::bind_method(D_METHOD("set_indeterminate", "column", "indeterminate"), &TreeItem::set_indeterminate); ClassDB::bind_method(D_METHOD("is_checked", "column"), &TreeItem::is_checked); + ClassDB::bind_method(D_METHOD("is_indeterminate", "column"), &TreeItem::is_indeterminate); ClassDB::bind_method(D_METHOD("set_text", "column", "text"), &TreeItem::set_text); ClassDB::bind_method(D_METHOD("get_text", "column"), &TreeItem::get_text); @@ -1113,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); @@ -1228,6 +1261,7 @@ void Tree::update_cache() { cache.checked = get_theme_icon(SNAME("checked")); cache.unchecked = get_theme_icon(SNAME("unchecked")); + cache.indeterminate = get_theme_icon(SNAME("indeterminate")); if (is_layout_rtl()) { cache.arrow_collapsed = get_theme_icon(SNAME("arrow_collapsed_mirrored")); } else { @@ -1487,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; } @@ -1676,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); + } } } @@ -1721,10 +1768,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 case TreeItem::CELL_MODE_CHECK: { Ref<Texture2D> checked = cache.checked; Ref<Texture2D> unchecked = cache.unchecked; + Ref<Texture2D> indeterminate = cache.indeterminate; Point2i check_ofs = item_rect.position; check_ofs.y += Math::floor((real_t)(item_rect.size.y - checked->get_height()) / 2); - if (p_item->cells[i].checked) { + if (p_item->cells[i].indeterminate) { + indeterminate->draw(ci, check_ofs); + } else if (p_item->cells[i].checked) { checked->draw(ci, check_ofs); } else { unchecked->draw(ci, check_ofs); @@ -2745,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; @@ -4627,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)); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index be711b4c4f..8b7ddc3faf 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -82,6 +82,7 @@ private: int icon_max_w = 0; bool expr = false; bool checked = false; + bool indeterminate = false; bool editable = false; bool selected = false; bool selectable = true; @@ -113,6 +114,7 @@ private: Vector<Button> buttons; Ref<Font> custom_font; + int custom_font_size = -1; Cell() { text_buf.instantiate(); @@ -209,7 +211,9 @@ public: /* check mode */ void set_checked(int p_column, bool p_checked); + void set_indeterminate(int p_column, bool p_indeterminate); bool is_checked(int p_column) const; + bool is_indeterminate(int p_column) const; void set_text(int p_column, String p_text); String get_text(int p_column) const; @@ -296,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; @@ -459,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); @@ -491,6 +497,7 @@ private: Ref<Texture2D> checked; Ref<Texture2D> unchecked; + Ref<Texture2D> indeterminate; Ref<Texture2D> arrow_collapsed; Ref<Texture2D> arrow; Ref<Texture2D> select_arrow; @@ -622,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 f2415eaf71..64b169b1fb 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -151,9 +151,7 @@ void CanvasItem::_update_callback() { current_item_drawn = this; notification(NOTIFICATION_DRAW); emit_signal(SceneStringNames::get_singleton()->draw); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_draw); - } + GDVIRTUAL_CALL(_draw); current_item_drawn = nullptr; drawing = false; } @@ -573,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."); @@ -883,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>())); @@ -934,7 +939,7 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_clip_children", "enable"), &CanvasItem::set_clip_children); ClassDB::bind_method(D_METHOD("is_clipping_children"), &CanvasItem::is_clipping_children); - BIND_VMETHOD(MethodInfo("_draw")); + GDVIRTUAL_BIND(_draw); ADD_GROUP("Visibility", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index dc7ad2bf5d..01ed47d4dc 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -142,6 +142,7 @@ protected: void _notification(int p_what); static void _bind_methods(); + GDVIRTUAL0(_draw) public: enum { NOTIFICATION_TRANSFORM_CHANGED = SceneTree::NOTIFICATION_TRANSFORM_CHANGED, //unique @@ -225,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 155af30a6d..cafa4fa2d2 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -54,16 +54,11 @@ int Node::orphan_node_count = 0; void Node::_notification(int p_notification) { switch (p_notification) { case NOTIFICATION_PROCESS: { - if (get_script_instance()) { - Variant time = get_process_delta_time(); - get_script_instance()->call(SceneStringNames::get_singleton()->_process, time); - } + GDVIRTUAL_CALL(_process, get_process_delta_time()); + } break; case NOTIFICATION_PHYSICS_PROCESS: { - if (get_script_instance()) { - Variant time = get_physics_process_delta_time(); - get_script_instance()->call(SceneStringNames::get_singleton()->_physics_process, time); - } + GDVIRTUAL_CALL(_physics_process, get_physics_process_delta_time()); } break; case NOTIFICATION_ENTER_TREE: { @@ -128,27 +123,26 @@ void Node::_notification(int p_notification) { } break; case NOTIFICATION_READY: { if (get_script_instance()) { - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_input)) { + if (GDVIRTUAL_IS_OVERRIDDEN(_input)) { set_process_input(true); } - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_unhandled_input)) { + if (GDVIRTUAL_IS_OVERRIDDEN(_unhandled_input)) { set_process_unhandled_input(true); } - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_unhandled_key_input)) { + if (GDVIRTUAL_IS_OVERRIDDEN(_unhandled_key_input)) { set_process_unhandled_key_input(true); } - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_process)) { + if (GDVIRTUAL_IS_OVERRIDDEN(_process)) { set_process(true); } - - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_physics_process)) { + if (GDVIRTUAL_IS_OVERRIDDEN(_physics_process)) { set_physics_process(true); } - get_script_instance()->call(SceneStringNames::get_singleton()->_ready); + GDVIRTUAL_CALL(_ready); } if (data.filename.length()) { ERR_FAIL_COND(!is_inside_tree()); @@ -221,9 +215,7 @@ void Node::_propagate_enter_tree() { notification(NOTIFICATION_ENTER_TREE); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_enter_tree); - } + GDVIRTUAL_CALL(_enter_tree); emit_signal(SceneStringNames::get_singleton()->tree_entered); @@ -269,9 +261,8 @@ void Node::_propagate_exit_tree() { data.blocked--; - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_exit_tree); - } + GDVIRTUAL_CALL(_exit_tree); + emit_signal(SceneStringNames::get_singleton()->tree_exiting); notification(NOTIFICATION_EXIT_TREE, true); @@ -2496,9 +2487,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(); } @@ -2544,6 +2540,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")); @@ -2625,6 +2652,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); @@ -2737,15 +2766,15 @@ void Node::_bind_methods() { ADD_GROUP("Editor Description", "editor_"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "editor_description", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "set_editor_description", "get_editor_description"); - BIND_VMETHOD(MethodInfo("_process", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo("_physics_process", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo("_enter_tree")); - BIND_VMETHOD(MethodInfo("_exit_tree")); - BIND_VMETHOD(MethodInfo("_ready")); - 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(_process, "delta"); + GDVIRTUAL_BIND(_physics_process, "delta"); + GDVIRTUAL_BIND(_enter_tree); + GDVIRTUAL_BIND(_exit_tree); + GDVIRTUAL_BIND(_ready); + GDVIRTUAL_BIND(_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 9997f4e055..a07accba2f 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -202,12 +202,33 @@ protected: static String _get_name_num_separator(); friend class SceneState; - friend class MultiplayerAPI; + friend class MultiplayerReplicator; void _add_child_nocheck(Node *p_child, const StringName &p_name); 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 diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 5b707498a7..8260d0dff5 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -867,7 +867,7 @@ void SceneMainLoop::_update_listener_2d() { */ -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 +886,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 +902,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..b1b94ae910 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.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..6e0b161e54 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -32,6 +32,7 @@ #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" @@ -51,6 +52,7 @@ #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) { @@ -662,7 +664,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); } } } @@ -819,12 +821,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) { @@ -1234,27 +1231,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()) { @@ -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) { @@ -2205,7 +2179,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); } } } @@ -2317,7 +2291,7 @@ void Viewport::_post_gui_grab_click_focus() { 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); } } @@ -2335,7 +2309,7 @@ void Viewport::_post_gui_grab_click_focus() { 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 +2317,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; } @@ -2665,7 +2639,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 +2669,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 +2677,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; @@ -2727,11 +2700,11 @@ void Viewport::unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coor } // Unhandled Input - get_tree()->_call_input_pause(unhandled_input_group, "_unhandled_input", ev, this); + 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 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()) { @@ -3095,6 +3068,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() { @@ -3159,7 +3133,7 @@ void Viewport::_collision_object_3d_input_event(CollisionObject3D *p_object, Cam 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 +3448,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 +3500,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 +3583,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 +3632,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); @@ -3757,6 +3752,11 @@ 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 +#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 } diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 9c51f404d7..d9b21ce6a8 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -88,6 +88,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, @@ -508,9 +516,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 +585,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 +656,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 +717,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 6995c77b8e..1fcfce2ea9 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -300,7 +300,7 @@ void Window::_propagate_window_notification(Node *p_node, int p_notification) { Node *child = p_node->get_child(i); Window *window = Object::cast_to<Window>(child); if (window) { - break; + continue; } _propagate_window_notification(child, p_notification); } @@ -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) { diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 40ab439a51..0c97fee711 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -160,6 +160,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 +249,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 +263,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 +308,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); @@ -753,6 +740,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); @@ -812,7 +800,6 @@ void register_scene_types() { GDREGISTER_CLASS(Font); GDREGISTER_CLASS(Curve); - GDREGISTER_CLASS(TextFile); GDREGISTER_CLASS(TextLine); GDREGISTER_CLASS(TextParagraph); @@ -842,6 +829,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); @@ -962,6 +950,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"); @@ -1067,14 +1057,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..2ab9b7b5a4 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() {} diff --git a/scene/resources/audio_stream_sample.h b/scene/resources/audio_stream_sample.h index 70b8ba79ad..8bf3d29123 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(); }; diff --git a/scene/resources/capsule_shape_2d.cpp b/scene/resources/capsule_shape_2d.cpp index 596fa70f15..0818e4fd99 100644 --- a/scene/resources/capsule_shape_2d.cpp +++ b/scene/resources/capsule_shape_2d.cpp @@ -59,7 +59,10 @@ void CapsuleShape2D::_update_shape() { } void CapsuleShape2D::set_radius(real_t p_radius) { - radius = MIN(p_radius, height * 0.5); + radius = p_radius; + if (radius > height * 0.5) { + height = radius * 2.0; + } _update_shape(); } @@ -68,7 +71,10 @@ real_t CapsuleShape2D::get_radius() const { } void CapsuleShape2D::set_height(real_t p_height) { - height = MAX(p_height, radius * 2); + height = p_height; + if (radius > height * 0.5) { + radius = height * 0.5; + } _update_shape(); } @@ -105,6 +111,8 @@ void CapsuleShape2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height"), "set_height", "get_height"); + ADD_LINKED_PROPERTY("radius", "height"); + ADD_LINKED_PROPERTY("height", "radius"); } CapsuleShape2D::CapsuleShape2D() : diff --git a/scene/resources/capsule_shape_3d.cpp b/scene/resources/capsule_shape_3d.cpp index e267941d6a..afec7b1877 100644 --- a/scene/resources/capsule_shape_3d.cpp +++ b/scene/resources/capsule_shape_3d.cpp @@ -81,7 +81,7 @@ void CapsuleShape3D::_update_shape() { void CapsuleShape3D::set_radius(float p_radius) { radius = p_radius; if (radius > height * 0.5) { - radius = height * 0.5; + height = radius * 2.0; } _update_shape(); notify_change_to_owners(); @@ -94,7 +94,7 @@ float CapsuleShape3D::get_radius() const { void CapsuleShape3D::set_height(float p_height) { height = p_height; if (radius > height * 0.5) { - height = radius * 2; + radius = height * 0.5; } _update_shape(); notify_change_to_owners(); @@ -112,6 +112,8 @@ void CapsuleShape3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,4096,0.001"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,4096,0.001"), "set_height", "get_height"); + ADD_LINKED_PROPERTY("radius", "height"); + ADD_LINKED_PROPERTY("height", "radius"); } CapsuleShape3D::CapsuleShape3D() : diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 3b666640f8..a364a27e80 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -662,19 +662,27 @@ void Curve2D::_bake() const { if (points.size() == 0) { baked_point_cache.resize(0); + baked_dist_cache.resize(0); return; } if (points.size() == 1) { baked_point_cache.resize(1); baked_point_cache.set(0, points[0].pos); + + baked_dist_cache.resize(1); + baked_dist_cache.set(0, 0.0); return; } Vector2 pos = points[0].pos; + float dist = 0.0; + List<Vector2> pointlist; + List<float> distlist; pointlist.push_back(pos); //start always from origin + distlist.push_back(0.0); for (int i = 0; i < points.size() - 1; i++) { float step = 0.1; // at least 10 substeps ought to be enough? @@ -712,7 +720,10 @@ void Curve2D::_bake() const { pos = npp; p = mid; + dist += d; + pointlist.push_back(pos); + distlist.push_back(dist); } else { p = np; } @@ -722,16 +733,20 @@ void Curve2D::_bake() const { Vector2 lastpos = points[points.size() - 1].pos; float rem = pos.distance_to(lastpos); - baked_max_ofs = (pointlist.size() - 1) * bake_interval + rem; + dist += rem; + baked_max_ofs = dist; pointlist.push_back(lastpos); + distlist.push_back(dist); baked_point_cache.resize(pointlist.size()); + baked_dist_cache.resize(distlist.size()); + Vector2 *w = baked_point_cache.ptrw(); - int idx = 0; + float *wd = baked_dist_cache.ptrw(); - for (const Vector2 &E : pointlist) { - w[idx] = E; - idx++; + for (int i = 0; i < pointlist.size(); i++) { + w[i] = pointlist[i]; + wd[i] = distlist[i]; } } @@ -766,19 +781,26 @@ Vector2 Curve2D::interpolate_baked(float p_offset, bool p_cubic) const { return r[bpc - 1]; } - int idx = Math::floor((double)p_offset / (double)bake_interval); - float frac = Math::fmod(p_offset, (float)bake_interval); - - if (idx >= bpc - 1) { - return r[bpc - 1]; - } else if (idx == bpc - 2) { - if (frac > 0) { - frac /= Math::fmod(baked_max_ofs, bake_interval); + int start = 0, end = bpc, idx = (end + start) / 2; + // binary search to find baked points + while (start < idx) { + float offset = baked_dist_cache[idx]; + if (p_offset <= offset) { + end = idx; + } else { + start = idx; } - } else { - frac /= bake_interval; + idx = (end + start) / 2; } + float offset_begin = baked_dist_cache[idx]; + float offset_end = baked_dist_cache[idx + 1]; + + float idx_interval = offset_end - offset_begin; + ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, Vector2(), "failed to find baked segment"); + + float frac = (p_offset - offset_begin) / idx_interval; + if (p_cubic) { Vector2 pre = idx > 0 ? r[idx - 1] : r[idx]; Vector2 post = (idx < (bpc - 2)) ? r[idx + 2] : r[idx + 1]; @@ -1145,6 +1167,7 @@ void Curve3D::_bake() const { baked_point_cache.resize(0); baked_tilt_cache.resize(0); baked_up_vector_cache.resize(0); + baked_dist_cache.resize(0); return; } @@ -1153,6 +1176,8 @@ void Curve3D::_bake() const { baked_point_cache.set(0, points[0].pos); baked_tilt_cache.resize(1); baked_tilt_cache.set(0, points[0].tilt); + baked_dist_cache.resize(1); + baked_dist_cache.set(0, 0.0); if (up_vector_enabled) { baked_up_vector_cache.resize(1); @@ -1165,8 +1190,12 @@ void Curve3D::_bake() const { } Vector3 pos = points[0].pos; + float dist = 0.0; List<Plane> pointlist; + List<float> distlist; + pointlist.push_back(Plane(pos, points[0].tilt)); + distlist.push_back(0.0); for (int i = 0; i < points.size() - 1; i++) { float step = 0.1; // at least 10 substeps ought to be enough? @@ -1207,7 +1236,10 @@ void Curve3D::_bake() const { Plane post; post.normal = pos; post.d = Math::lerp(points[i].tilt, points[i + 1].tilt, mid); + dist += d; + pointlist.push_back(post); + distlist.push_back(dist); } else { p = np; } @@ -1218,8 +1250,10 @@ void Curve3D::_bake() const { float lastilt = points[points.size() - 1].tilt; float rem = pos.distance_to(lastpos); - baked_max_ofs = (pointlist.size() - 1) * bake_interval + rem; + dist += rem; + baked_max_ofs = dist; pointlist.push_back(Plane(lastpos, lastilt)); + distlist.push_back(dist); baked_point_cache.resize(pointlist.size()); Vector3 *w = baked_point_cache.ptrw(); @@ -1231,6 +1265,9 @@ void Curve3D::_bake() const { baked_up_vector_cache.resize(up_vector_enabled ? pointlist.size() : 0); Vector3 *up_write = baked_up_vector_cache.ptrw(); + baked_dist_cache.resize(pointlist.size()); + float *wd = baked_dist_cache.ptrw(); + Vector3 sideways; Vector3 up; Vector3 forward; @@ -1242,6 +1279,7 @@ void Curve3D::_bake() const { for (const Plane &E : pointlist) { w[idx] = E.normal; wt[idx] = E.d; + wd[idx] = distlist[idx]; if (!up_vector_enabled) { idx++; @@ -1308,19 +1346,26 @@ Vector3 Curve3D::interpolate_baked(float p_offset, bool p_cubic) const { return r[bpc - 1]; } - int idx = Math::floor((double)p_offset / (double)bake_interval); - float frac = Math::fmod(p_offset, bake_interval); - - if (idx >= bpc - 1) { - return r[bpc - 1]; - } else if (idx == bpc - 2) { - if (frac > 0) { - frac /= Math::fmod(baked_max_ofs, bake_interval); + int start = 0, end = bpc, idx = (end + start) / 2; + // binary search to find baked points + while (start < idx) { + float offset = baked_dist_cache[idx]; + if (p_offset <= offset) { + end = idx; + } else { + start = idx; } - } else { - frac /= bake_interval; + idx = (end + start) / 2; } + float offset_begin = baked_dist_cache[idx]; + float offset_end = baked_dist_cache[idx + 1]; + + float idx_interval = offset_end - offset_begin; + ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, Vector3(), "failed to find baked segment"); + + float frac = (p_offset - offset_begin) / idx_interval; + if (p_cubic) { Vector3 pre = idx > 0 ? r[idx - 1] : r[idx]; Vector3 post = (idx < (bpc - 2)) ? r[idx + 2] : r[idx + 1]; diff --git a/scene/resources/curve.h b/scene/resources/curve.h index c25d307608..5808fd6508 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -161,6 +161,7 @@ class Curve2D : public Resource { mutable bool baked_cache_dirty = false; mutable PackedVector2Array baked_point_cache; + mutable PackedFloat32Array baked_dist_cache; mutable float baked_max_ofs = 0.0; void _bake() const; @@ -224,6 +225,7 @@ class Curve3D : public Resource { mutable PackedVector3Array baked_point_cache; mutable Vector<real_t> baked_tilt_cache; mutable PackedVector3Array baked_up_vector_cache; + mutable PackedFloat32Array baked_dist_cache; mutable float baked_max_ofs = 0.0; void _bake() const; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 8208c55801..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); @@ -704,6 +684,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("checked", "Tree", make_icon(checked_png)); theme->set_icon("unchecked", "Tree", make_icon(unchecked_png)); + theme->set_icon("indeterminate", "Tree", make_icon(indeterminate_png)); theme->set_icon("updown", "Tree", make_icon(updown_png)); theme->set_icon("select_arrow", "Tree", make_icon(dropdown_png)); theme->set_icon("arrow", "Tree", make_icon(arrow_down_png)); @@ -858,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)); @@ -866,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 @@ -916,7 +925,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("shadow_offset_y", "RichTextLabel", 1 * scale); theme->set_constant("shadow_as_outline", "RichTextLabel", 0 * scale); - theme->set_constant("line_separation", "RichTextLabel", 1 * scale); + theme->set_constant("line_separation", "RichTextLabel", 0 * scale); theme->set_constant("table_hseparation", "RichTextLabel", 3 * scale); theme->set_constant("table_vseparation", "RichTextLabel", 3 * scale); @@ -1011,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/default_theme/indeterminate.png b/scene/resources/default_theme/indeterminate.png Binary files differnew file mode 100644 index 0000000000..28a457b251 --- /dev/null +++ b/scene/resources/default_theme/indeterminate.png diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h index 4a53926066..6a556c1112 100644 --- a/scene/resources/default_theme/theme_data.h +++ b/scene/resources/default_theme/theme_data.h @@ -214,6 +214,14 @@ static const unsigned char icon_zoom_reset_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x33, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x20, 0xa, 0x3c, 0xc, 0x7b, 0xf0, 0xff, 0xc1, 0x7f, 0x9c, 0x22, 0xcf, 0x44, 0x1e, 0xbc, 0x84, 0x72, 0xb1, 0x8b, 0x3c, 0x58, 0x5, 0xe4, 0x40, 0xb8, 0x38, 0x45, 0x18, 0x60, 0x5c, 0x84, 0x30, 0x59, 0xa, 0xa0, 0x80, 0x6e, 0xa, 0x86, 0x92, 0x2f, 0x8, 0x3, 0x0, 0x69, 0xc8, 0x86, 0x87, 0x72, 0xca, 0x85, 0x23, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char indeterminate_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x36, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x38, 0x37, 0x40, 0x20, 0x20, 0x24, 0x20, 0x20, 0x24, 0x38, 0x36, 0x40, 0x20, 0x20, 0x25, 0x1e, 0x1e, 0x22, 0x1f, 0x1f, 0x23, 0x20, 0x20, 0x24, 0x22, 0x22, 0x27, 0x23, 0x23, 0x28, 0x25, 0x25, 0x2a, 0xfe, 0xfe, 0xfe, 0x98, 0x4d, 0x2d, 0x9a, 0x0, 0x0, 0x0, 0x12, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x50, 0x66, 0x68, 0xb4, 0xfa, 0xfb, 0xb4, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1c, 0x77, 0x5e, 0x7f, 0x0, 0x0, 0x0, 0x59, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x85, 0xcf, 0xc1, 0x12, 0x80, 0x20, 0x8, 0x45, 0xd1, 0x4, 0x14, 0xd, 0xc5, 0xfa, 0xff, 0x9f, 0x8d, 0x9c, 0x6c, 0x9a, 0xb4, 0xe9, 0x2e, 0xcf, 0x42, 0x9e, 0xcb, 0x32, 0xe4, 0x0, 0xc9, 0xb7, 0x8, 0xc1, 0x19, 0x40, 0x60, 0xc9, 0x2d, 0xe1, 0x0, 0x6, 0xc8, 0x45, 0x6b, 0x4b, 0xb, 0xa3, 0x1, 0x89, 0x6e, 0x57, 0x2a, 0x64, 0xe0, 0x73, 0xed, 0x50, 0xb3, 0x9f, 0xc3, 0x7e, 0xf7, 0x5, 0x7f, 0x6f, 0xc, 0x67, 0x9f, 0xc3, 0xe2, 0x39, 0xc, 0x52, 0xec, 0xd3, 0xd7, 0x4, 0xb3, 0xcf, 0xbd, 0x3a, 0x0, 0xa0, 0xa2, 0x8, 0xbc, 0xf6, 0x84, 0x3a, 0x9d, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + +static const unsigned char indeterminate_disabled_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x3c, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x6a, 0x69, 0x70, 0x6a, 0x68, 0x70, 0x58, 0x58, 0x5c, 0x58, 0x58, 0x5b, 0x58, 0x58, 0x5b, 0x5c, 0x5c, 0x5f, 0x5a, 0x5a, 0x5e, 0x59, 0x59, 0x5d, 0x58, 0x58, 0x5b, 0x57, 0x57, 0x5a, 0x56, 0x56, 0x59, 0xff, 0x0, 0x0, 0xff, 0xff, 0xff, 0x9e, 0x9e, 0x9e, 0x8c, 0x93, 0x80, 0x95, 0x0, 0x0, 0x0, 0x14, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x50, 0x66, 0x68, 0xb4, 0xb4, 0xfa, 0xfa, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x6c, 0xb9, 0x8d, 0x1e, 0x0, 0x0, 0x0, 0x59, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x85, 0xcf, 0xd1, 0xe, 0x80, 0x20, 0x8, 0x85, 0xe1, 0x4, 0xd4, 0x30, 0x51, 0xb6, 0xde, 0xff, 0x5d, 0x23, 0x97, 0xad, 0xa5, 0xad, 0xff, 0xf2, 0xbb, 0x90, 0xe3, 0xb2, 0xc, 0x39, 0x40, 0xf2, 0x2d, 0x42, 0x70, 0x6, 0x10, 0x58, 0x6b, 0x4b, 0x39, 0x80, 0x1, 0x72, 0x91, 0xdc, 0x92, 0xc2, 0x68, 0x40, 0x2a, 0xdb, 0x95, 0x28, 0x19, 0xf8, 0x9a, 0x3b, 0xe4, 0xea, 0xe7, 0xb0, 0xdf, 0x7d, 0xc1, 0xdf, 0x1b, 0xc3, 0xd9, 0xe7, 0xb0, 0x74, 0xe, 0x83, 0x98, 0xfa, 0xf4, 0x35, 0xc2, 0xec, 0x73, 0xaf, 0xe, 0x57, 0x20, 0x8, 0x2c, 0x1a, 0x56, 0xe5, 0x32, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char line_edit_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xa, 0x4, 0x3, 0x0, 0x0, 0x0, 0x7f, 0x1c, 0xd2, 0x8e, 0x0, 0x0, 0x0, 0x2a, 0x50, 0x4c, 0x54, 0x45, 0x17, 0x16, 0x1a, 0x1d, 0x1c, 0x21, 0x20, 0x1e, 0x24, 0x21, 0x1f, 0x25, 0x1d, 0x1c, 0x21, 0x20, 0x1e, 0x24, 0x1d, 0x1c, 0x21, 0x1d, 0x1c, 0x21, 0x24, 0x22, 0x29, 0x28, 0x26, 0x2d, 0x28, 0x26, 0x2e, 0x2b, 0x2a, 0x31, 0x2c, 0x2a, 0x32, 0xff, 0xff, 0xff, 0xb9, 0x11, 0x56, 0x3e, 0x0, 0x0, 0x0, 0x8, 0x74, 0x52, 0x4e, 0x53, 0x6f, 0xef, 0xf7, 0xf7, 0xf0, 0xf9, 0xf1, 0xee, 0xcf, 0x21, 0xd2, 0xdf, 0x0, 0x0, 0x0, 0x2d, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x60, 0x54, 0x36, 0x36, 0x12, 0x60, 0xf0, 0x98, 0xb5, 0x6a, 0x65, 0xb, 0x43, 0xe4, 0x9e, 0x33, 0xa7, 0xa7, 0x32, 0x58, 0x9d, 0x39, 0x73, 0x66, 0x31, 0x16, 0x12, 0x22, 0xb, 0x52, 0xd9, 0xc6, 0xc0, 0x2, 0xd4, 0x55, 0x0, 0x0, 0xc, 0x14, 0x1a, 0x90, 0x55, 0x1a, 0xec, 0xdb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; 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/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 ac7a500f73..8ab26aff77 100644 --- a/scene/resources/particles_material.h +++ b/scene/resources/particles_material.h @@ -61,6 +61,7 @@ public: PARAM_MAX }; + // When extending, make sure not to overflow the size of the MaterialKey below. enum ParticleFlags { PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, PARTICLE_FLAG_ROTATE_Y, @@ -68,6 +69,7 @@ public: PARTICLE_FLAG_MAX }; + // When extending, make sure not to overflow the size of the MaterialKey below. enum EmissionShape { EMISSION_SHAPE_POINT, EMISSION_SHAPE_SPHERE, @@ -78,6 +80,7 @@ public: EMISSION_SHAPE_MAX }; + // When extending, make sure not to overflow the size of the MaterialKey below. enum SubEmitterMode { SUB_EMITTER_DISABLED, SUB_EMITTER_CONSTANT, @@ -88,11 +91,13 @@ public: private: union MaterialKey { + // The bit size of the struct must be kept below or equal to 32 bits. + // Consider this when extending ParticleFlags, EmissionShape, or SubEmitterMode. struct { uint32_t texture_mask : 16; uint32_t texture_color : 1; uint32_t particle_flags : 4; - uint32_t emission_shape : 2; + uint32_t emission_shape : 3; uint32_t invalid_key : 1; uint32_t has_emission_color : 1; uint32_t sub_emitter : 2; @@ -149,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; @@ -225,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; @@ -278,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/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/scene/resources/separation_ray_shape_3d.h b/scene/resources/separation_ray_shape_3d.h new file mode 100644 index 0000000000..54058b6095 --- /dev/null +++ b/scene/resources/separation_ray_shape_3d.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* separation_ray_shape_3d.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_H +#define SEPARATION_RAY_SHAPE_H +#include "scene/resources/shape_3d.h" + +class SeparationRayShape3D : public Shape3D { + GDCLASS(SeparationRayShape3D, Shape3D); + float length = 1.0; + bool slide_on_slope = false; + +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/skeleton_modification_2d.cpp b/scene/resources/skeleton_modification_2d.cpp index 2d5b42ddf4..e533fb054a 100644 --- a/scene/resources/skeleton_modification_2d.cpp +++ b/scene/resources/skeleton_modification_2d.cpp @@ -44,7 +44,7 @@ /////////////////////////////////////// void SkeletonModification2D::_execute(float p_delta) { - call("_execute", p_delta); + GDVIRTUAL_CALL(_execute, p_delta); if (!enabled) { return; @@ -59,11 +59,11 @@ void SkeletonModification2D::_setup_modification(SkeletonModificationStack2D *p_ WARN_PRINT("Could not setup modification with name " + get_name()); } - call("_setup_modification", p_stack); + GDVIRTUAL_CALL(_setup_modification, Ref<SkeletonModificationStack2D>(p_stack)); } void SkeletonModification2D::_draw_editor_gizmo() { - call("_draw_editor_gizmo"); + GDVIRTUAL_CALL(_draw_editor_gizmo); } void SkeletonModification2D::set_enabled(bool p_enabled) { @@ -228,9 +228,9 @@ bool SkeletonModification2D::get_editor_draw_gizmo() const { } void SkeletonModification2D::_bind_methods() { - BIND_VMETHOD(MethodInfo("_execute", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo("_setup_modification", PropertyInfo(Variant::OBJECT, "modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D"))); - BIND_VMETHOD(MethodInfo("_draw_editor_gizmo")); + GDVIRTUAL_BIND(_execute, "delta"); + GDVIRTUAL_BIND(_setup_modification, "modification_stack") + GDVIRTUAL_BIND(_draw_editor_gizmo) ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification2D::set_enabled); ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification2D::get_enabled); diff --git a/scene/resources/skeleton_modification_2d.h b/scene/resources/skeleton_modification_2d.h index 18633e55cb..aaddb9136e 100644 --- a/scene/resources/skeleton_modification_2d.h +++ b/scene/resources/skeleton_modification_2d.h @@ -57,6 +57,10 @@ protected: bool _print_execution_error(bool p_condition, String p_message); + GDVIRTUAL1(_execute, double) + GDVIRTUAL1(_setup_modification, Ref<SkeletonModificationStack2D>) + GDVIRTUAL0(_draw_editor_gizmo) + public: virtual void _execute(float _delta); virtual void _setup_modification(SkeletonModificationStack2D *p_stack); diff --git a/scene/resources/skeleton_modification_2d_fabrik.cpp b/scene/resources/skeleton_modification_2d_fabrik.cpp index c03a92b503..6e9429034f 100644 --- a/scene/resources/skeleton_modification_2d_fabrik.cpp +++ b/scene/resources/skeleton_modification_2d_fabrik.cpp @@ -149,15 +149,6 @@ void SkeletonModification2DFABRIK::_execute(float p_delta) { return; } fabrik_transform_chain.write[i] = joint_bone2d_node->get_global_transform(); - - // Apply magnet positions - if (i == 0) { - continue; // The origin cannot use a magnet position! - } else { - Transform2D joint_trans = fabrik_transform_chain[i]; - joint_trans.set_origin(joint_trans.get_origin() + fabrik_data_chain[i].magnet_position); - fabrik_transform_chain.write[i] = joint_trans; - } } Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[fabrik_data_chain.size() - 1].bone2d_node_cache)); @@ -223,6 +214,11 @@ void SkeletonModification2DFABRIK::chain_backwards() { Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[final_joint_index].bone2d_node_cache)); Transform2D final_bone2d_trans = fabrik_transform_chain[final_joint_index]; + // Apply magnet position + if (final_joint_index != 0) { + final_bone2d_trans.set_origin(final_bone2d_trans.get_origin() + fabrik_data_chain[final_joint_index].magnet_position); + } + // Set the rotation of the tip bone final_bone2d_trans = final_bone2d_trans.looking_at(target_global_pose.get_origin()); @@ -245,6 +241,11 @@ void SkeletonModification2DFABRIK::chain_backwards() { Bone2D *current_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); Transform2D current_pose = fabrik_transform_chain[i]; + // Apply magnet position + if (i != 0) { + current_pose.set_origin(current_pose.get_origin() + fabrik_data_chain[i].magnet_position); + } + float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y); float length = current_bone2d_node_length / (previous_pose.get_origin() - current_pose.get_origin()).length(); Vector2 finish_position = previous_pose.get_origin().lerp(current_pose.get_origin(), length); diff --git a/scene/resources/skeleton_modification_3d.cpp b/scene/resources/skeleton_modification_3d.cpp index 9306ee14cd..ee02ede2d5 100644 --- a/scene/resources/skeleton_modification_3d.cpp +++ b/scene/resources/skeleton_modification_3d.cpp @@ -32,11 +32,7 @@ #include "scene/3d/skeleton_3d.h" void SkeletonModification3D::_execute(real_t p_delta) { - if (get_script_instance()) { - if (get_script_instance()->has_method("execute")) { - get_script_instance()->call("execute", p_delta); - } - } + GDVIRTUAL_CALL(_execute, p_delta); if (!enabled) return; @@ -50,11 +46,7 @@ void SkeletonModification3D::_setup_modification(SkeletonModificationStack3D *p_ WARN_PRINT("Could not setup modification with name " + this->get_name()); } - if (get_script_instance()) { - if (get_script_instance()->has_method("setup_modification")) { - get_script_instance()->call("setup_modification", p_stack); - } - } + GDVIRTUAL_CALL(_setup_modification, Ref<SkeletonModificationStack3D>(p_stack)); } void SkeletonModification3D::set_enabled(bool p_enabled) { @@ -148,8 +140,8 @@ int SkeletonModification3D::get_execution_mode() const { } void SkeletonModification3D::_bind_methods() { - BIND_VMETHOD(MethodInfo("_execute", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo("_setup_modification", PropertyInfo(Variant::OBJECT, "modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack3D"))); + GDVIRTUAL_BIND(_execute, "delta"); + GDVIRTUAL_BIND(_setup_modification, "modification_stack") ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification3D::set_enabled); ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification3D::get_enabled); diff --git a/scene/resources/skeleton_modification_3d.h b/scene/resources/skeleton_modification_3d.h index 94ab0bf32c..fb1f3d33d1 100644 --- a/scene/resources/skeleton_modification_3d.h +++ b/scene/resources/skeleton_modification_3d.h @@ -53,6 +53,9 @@ protected: bool _print_execution_error(bool p_condition, String p_message); + GDVIRTUAL1(_execute, double) + GDVIRTUAL1(_setup_modification, Ref<SkeletonModificationStack3D>) + public: virtual void _execute(real_t p_delta); virtual void _setup_modification(SkeletonModificationStack3D *p_stack); diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp index 173ce2adce..52a3abf74d 100644 --- a/scene/resources/syntax_highlighter.cpp +++ b/scene/resources/syntax_highlighter.cpp @@ -43,12 +43,10 @@ Dictionary SyntaxHighlighter::get_line_syntax_highlighting(int p_line) { return color_map; } - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_get_line_syntax_highlighting")) { - color_map = si->call("_get_line_syntax_highlighting", p_line); - } else { - color_map = _get_line_syntax_highlighting(p_line); + 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; } @@ -69,9 +67,7 @@ void SyntaxHighlighter::_lines_edited_from(int p_from_line, int p_to_line) { void SyntaxHighlighter::clear_highlighting_cache() { highlighting_cache.clear(); - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_clear_highlighting_cache")) { - si->call("_clear_highlighting_cache"); + if (GDVIRTUAL_CALL(_clear_highlighting_cache)) { return; } _clear_highlighting_cache(); @@ -83,9 +79,7 @@ void SyntaxHighlighter::update_cache() { if (text_edit == nullptr) { return; } - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_update_cache")) { - si->call("_update_cache"); + if (GDVIRTUAL_CALL(_update_cache)) { return; } _update_cache(); @@ -115,9 +109,9 @@ void SyntaxHighlighter::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_highlighting_cache"), &SyntaxHighlighter::clear_highlighting_cache); ClassDB::bind_method(D_METHOD("get_text_edit"), &SyntaxHighlighter::get_text_edit); - BIND_VMETHOD(MethodInfo(Variant::DICTIONARY, "_get_line_syntax_highlighting", PropertyInfo(Variant::INT, "line"))); - BIND_VMETHOD(MethodInfo("_clear_highlighting_cache")); - BIND_VMETHOD(MethodInfo("_update_cache")); + GDVIRTUAL_BIND(_get_line_syntax_highlighting, "line") + GDVIRTUAL_BIND(_clear_highlighting_cache) + GDVIRTUAL_BIND(_update_cache) } //////////////////////////////////////////////////////////////////////////////// @@ -130,7 +124,7 @@ static bool _is_hex_symbol(char32_t c) { return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } -Dictionary CodeHighlighter::_get_line_syntax_highlighting(int p_line) { +Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { Dictionary color_map; bool prev_is_char = false; diff --git a/scene/resources/syntax_highlighter.h b/scene/resources/syntax_highlighter.h index f3964b0c8f..0fe39ccff6 100644 --- a/scene/resources/syntax_highlighter.h +++ b/scene/resources/syntax_highlighter.h @@ -32,6 +32,8 @@ #define SYNTAX_HIGHLIGHTER_H #include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" class TextEdit; @@ -48,9 +50,12 @@ protected: static void _bind_methods(); + GDVIRTUAL1RC(Dictionary, _get_line_syntax_highlighting, int) + GDVIRTUAL0(_clear_highlighting_cache) + GDVIRTUAL0(_update_cache) public: Dictionary get_line_syntax_highlighting(int p_line); - virtual Dictionary _get_line_syntax_highlighting(int p_line) { return Dictionary(); } + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) { return Dictionary(); } void clear_highlighting_cache(); virtual void _clear_highlighting_cache() {} @@ -93,7 +98,7 @@ protected: static void _bind_methods(); public: - virtual Dictionary _get_line_syntax_highlighting(int p_line) override; + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override; virtual void _clear_highlighting_cache() override; virtual void _update_cache() override; 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/theme.cpp b/scene/resources/theme.cpp index e4a731c7f7..e49d883ba4 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -1350,40 +1350,36 @@ void Theme::clear() { _emit_theme_changed(); } -void Theme::copy_default_theme() { - Ref<Theme> default_theme2 = get_default(); - copy_theme(default_theme2); -} - -void Theme::copy_theme(const Ref<Theme> &p_other) { +void Theme::merge_with(const Ref<Theme> &p_other) { if (p_other.is_null()) { - clear(); return; } _freeze_change_propagation(); - // These items need reconnecting, so add them normally. + // Colors. { const StringName *K = nullptr; - while ((K = p_other->icon_map.next(K))) { + while ((K = p_other->color_map.next(K))) { const StringName *L = nullptr; - while ((L = p_other->icon_map[*K].next(L))) { - set_icon(*L, *K, p_other->icon_map[*K][*L]); + while ((L = p_other->color_map[*K].next(L))) { + set_color(*L, *K, p_other->color_map[*K][*L]); } } } + // Constants. { const StringName *K = nullptr; - while ((K = p_other->style_map.next(K))) { + while ((K = p_other->constant_map.next(K))) { const StringName *L = nullptr; - while ((L = p_other->style_map[*K].next(L))) { - set_stylebox(*L, *K, p_other->style_map[*K][*L]); + while ((L = p_other->constant_map[*K].next(L))) { + set_constant(*L, *K, p_other->constant_map[*K][*L]); } } } + // Fonts. { const StringName *K = nullptr; while ((K = p_other->font_map.next(K))) { @@ -1394,13 +1390,46 @@ void Theme::copy_theme(const Ref<Theme> &p_other) { } } - // These items can be simply copied. - font_size_map = p_other->font_size_map; - color_map = p_other->color_map; - constant_map = p_other->constant_map; + // Font sizes. + { + const StringName *K = nullptr; + while ((K = p_other->font_size_map.next(K))) { + const StringName *L = nullptr; + while ((L = p_other->font_size_map[*K].next(L))) { + set_font_size(*L, *K, p_other->font_size_map[*K][*L]); + } + } + } - variation_map = p_other->variation_map; - variation_base_map = p_other->variation_base_map; + // Icons. + { + const StringName *K = nullptr; + while ((K = p_other->icon_map.next(K))) { + const StringName *L = nullptr; + while ((L = p_other->icon_map[*K].next(L))) { + set_icon(*L, *K, p_other->icon_map[*K][*L]); + } + } + } + + // Styleboxes. + { + const StringName *K = nullptr; + while ((K = p_other->style_map.next(K))) { + const StringName *L = nullptr; + while ((L = p_other->style_map[*K].next(L))) { + set_stylebox(*L, *K, p_other->style_map[*K][*L]); + } + } + } + + // Type variations. + { + const StringName *K = nullptr; + while ((K = p_other->variation_map.next(K))) { + set_type_variation(*K, p_other->variation_map[*K]); + } + } _unfreeze_and_propagate_changes(); } @@ -1534,8 +1563,6 @@ void Theme::_bind_methods() { ClassDB::bind_method(D_METHOD("get_constant_list", "theme_type"), &Theme::_get_constant_list); ClassDB::bind_method(D_METHOD("get_constant_type_list"), &Theme::_get_constant_type_list); - ClassDB::bind_method(D_METHOD("clear"), &Theme::clear); - ClassDB::bind_method(D_METHOD("set_default_font", "font"), &Theme::set_default_theme_font); ClassDB::bind_method(D_METHOD("get_default_font"), &Theme::get_default_theme_font); @@ -1558,8 +1585,8 @@ void Theme::_bind_methods() { ClassDB::bind_method(D_METHOD("get_type_list"), &Theme::_get_type_list); - ClassDB::bind_method("copy_default_theme", &Theme::copy_default_theme); - ClassDB::bind_method(D_METHOD("copy_theme", "other"), &Theme::copy_theme); + ClassDB::bind_method(D_METHOD("merge_with", "other"), &Theme::merge_with); + ClassDB::bind_method(D_METHOD("clear"), &Theme::clear); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_default_font", "get_default_font"); ADD_PROPERTY(PropertyInfo(Variant::INT, "default_font_size"), "set_default_font_size", "get_default_font_size"); diff --git a/scene/resources/theme.h b/scene/resources/theme.h index 8a8fc28be1..15f21b91b8 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -210,8 +210,7 @@ public: void get_type_list(List<StringName> *p_list) const; void get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variant, List<StringName> *p_list); - void copy_default_theme(); - void copy_theme(const Ref<Theme> &p_other); + void merge_with(const Ref<Theme> &p_other); void clear(); Theme(); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index b00dcca004..e8fe3ff3cd 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -261,54 +261,47 @@ VisualShaderNode::VisualShaderNode() { ///////////////////////////////////////////////////////// void VisualShaderNodeCustom::update_ports() { - ERR_FAIL_COND(!get_script_instance()); + { + input_ports.clear(); + int input_port_count; + if (GDVIRTUAL_CALL(_get_input_port_count, input_port_count)) { + for (int i = 0; i < input_port_count; i++) { + Port port; + if (!GDVIRTUAL_CALL(_get_input_port_name, i, port.name)) { + port.name = "in" + itos(i); + } + if (!GDVIRTUAL_CALL(_get_input_port_type, i, port.type)) { + port.type = (int)PortType::PORT_TYPE_SCALAR; + } - input_ports.clear(); - if (get_script_instance()->has_method("_get_input_port_count")) { - int input_port_count = (int)get_script_instance()->call("_get_input_port_count"); - bool has_name = get_script_instance()->has_method("_get_input_port_name"); - bool has_type = get_script_instance()->has_method("_get_input_port_type"); - for (int i = 0; i < input_port_count; i++) { - Port port; - if (has_name) { - port.name = (String)get_script_instance()->call("_get_input_port_name", i); - } else { - port.name = "in" + itos(i); + input_ports.push_back(port); } - if (has_type) { - port.type = (int)get_script_instance()->call("_get_input_port_type", i); - } else { - port.type = (int)PortType::PORT_TYPE_SCALAR; - } - input_ports.push_back(port); } } - output_ports.clear(); - if (get_script_instance()->has_method("_get_output_port_count")) { - int output_port_count = (int)get_script_instance()->call("_get_output_port_count"); - bool has_name = get_script_instance()->has_method("_get_output_port_name"); - bool has_type = get_script_instance()->has_method("_get_output_port_type"); - for (int i = 0; i < output_port_count; i++) { - Port port; - if (has_name) { - port.name = (String)get_script_instance()->call("_get_output_port_name", i); - } else { - port.name = "out" + itos(i); - } - if (has_type) { - port.type = (int)get_script_instance()->call("_get_output_port_type", i); - } else { - port.type = (int)PortType::PORT_TYPE_SCALAR; + + { + output_ports.clear(); + int output_port_count; + if (GDVIRTUAL_CALL(_get_output_port_count, output_port_count)) { + for (int i = 0; i < output_port_count; i++) { + Port port; + if (!GDVIRTUAL_CALL(_get_output_port_name, i, port.name)) { + port.name = "out" + itos(i); + } + if (!GDVIRTUAL_CALL(_get_output_port_type, i, port.type)) { + port.type = (int)PortType::PORT_TYPE_SCALAR; + } + + output_ports.push_back(port); } - output_ports.push_back(port); } } } String VisualShaderNodeCustom::get_caption() const { - ERR_FAIL_COND_V(!get_script_instance(), ""); - if (get_script_instance()->has_method("_get_name")) { - return (String)get_script_instance()->call("_get_name"); + String ret; + if (GDVIRTUAL_CALL(_get_name, ret)) { + return ret; } return "Unnamed"; } @@ -342,9 +335,8 @@ 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(!get_script_instance(), ""); - ERR_FAIL_COND_V(!get_script_instance()->has_method("_get_code"), ""); - Array input_vars; + 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]); } @@ -353,7 +345,8 @@ String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader:: output_vars.push_back(p_output_vars[i]); } String code = " {\n"; - String _code = (String)get_script_instance()->call("_get_code", input_vars, output_vars, (int)p_mode, (int)p_type); + String _code; + GDVIRTUAL_CALL(_get_code, input_vars, output_vars, (int)p_mode, (int)p_type, _code); bool nend = _code.ends_with("\n"); _code = _code.insert(0, " "); _code = _code.replace("\n", "\n "); @@ -369,10 +362,10 @@ String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader:: } String VisualShaderNodeCustom::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { - ERR_FAIL_COND_V(!get_script_instance(), ""); - if (get_script_instance()->has_method("_get_global_code")) { + String ret; + if (GDVIRTUAL_CALL(_get_global_code, (int)p_mode, ret)) { String code = "// " + get_caption() + "\n"; - code += (String)get_script_instance()->call("_get_global_code", (int)p_mode); + code += ret; code += "\n"; return code; } @@ -416,19 +409,19 @@ void VisualShaderNodeCustom::_set_initialized(bool p_enabled) { } void VisualShaderNodeCustom::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_name")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_description")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_category")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_return_icon_type")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_input_port_count")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_input_port_type", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::STRING_NAME, "_get_input_port_name", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_port_count")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_port_type", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::STRING_NAME, "_get_output_port_name", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_code", PropertyInfo(Variant::ARRAY, "input_vars"), PropertyInfo(Variant::ARRAY, "output_vars"), PropertyInfo(Variant::INT, "mode"), PropertyInfo(Variant::INT, "type"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_global_code", PropertyInfo(Variant::INT, "mode"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_highend")); + GDVIRTUAL_BIND(_get_name); + GDVIRTUAL_BIND(_get_description); + GDVIRTUAL_BIND(_get_category); + GDVIRTUAL_BIND(_get_return_icon_type); + GDVIRTUAL_BIND(_get_input_port_count); + GDVIRTUAL_BIND(_get_input_port_type, "port"); + GDVIRTUAL_BIND(_get_input_port_name, "port"); + GDVIRTUAL_BIND(_get_output_port_count); + GDVIRTUAL_BIND(_get_output_port_type, "port"); + GDVIRTUAL_BIND(_get_output_port_name, "port"); + GDVIRTUAL_BIND(_get_code, "input_vars", "output_vars", "mode", "type"); + GDVIRTUAL_BIND(_get_global_code, "mode"); + GDVIRTUAL_BIND(_is_highend); ClassDB::bind_method(D_METHOD("_set_initialized", "enabled"), &VisualShaderNodeCustom::_set_initialized); ClassDB::bind_method(D_METHOD("_is_initialized"), &VisualShaderNodeCustom::_is_initialized); diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 31651318ae..b3efac02aa 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -314,6 +314,20 @@ protected: virtual void remove_input_port_default_value(int p_port) override; virtual void clear_default_input_values() override; + GDVIRTUAL0RC(String, _get_name) + GDVIRTUAL0RC(String, _get_description) + GDVIRTUAL0RC(String, _get_category) + GDVIRTUAL0RC(int, _get_return_icon_type) + GDVIRTUAL0RC(int, _get_input_port_count) + GDVIRTUAL1RC(int, _get_input_port_type, int) + GDVIRTUAL1RC(String, _get_input_port_name, int) + GDVIRTUAL0RC(int, _get_output_port_count) + GDVIRTUAL1RC(int, _get_output_port_type, int) + GDVIRTUAL1RC(String, _get_output_port_name, int) + GDVIRTUAL4RC(String, _get_code, Vector<String>, TypedArray<String>, int, int) + GDVIRTUAL1RC(String, _get_global_code, int) + GDVIRTUAL0RC(bool, _is_highend) + protected: void _set_input_port_default_value(int p_port, const Variant &p_value); diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index f3fa857682..e1b391b823 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,39 @@ 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; +} + void AudioStream::_bind_methods() { ClassDB::bind_method(D_METHOD("get_length"), &AudioStream::get_length); + GDVIRTUAL_BIND(_instance_playback); + GDVIRTUAL_BIND(_get_stream_name); + GDVIRTUAL_BIND(_get_length); } //////////////////////////////// @@ -127,7 +227,7 @@ 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 +238,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 +259,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 +274,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() { @@ -345,16 +452,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..922335508e 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,15 @@ class AudioStream : public Resource { protected: static void _bind_methods(); + GDVIRTUAL0RC(Ref<AudioStreamPlayback>, _instance_playback) + GDVIRTUAL0RC(String, _get_stream_name) + GDVIRTUAL0RC(float, _get_length) + 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; }; // Microphone @@ -123,11 +142,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; @@ -191,7 +210,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_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..edb5c6d2dd 100644 --- a/servers/audio/effects/audio_stream_generator.cpp +++ b/servers/audio/effects/audio_stream_generator.cpp @@ -138,7 +138,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 +156,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..6bec744081 100644 --- a/servers/audio/effects/audio_stream_generator.h +++ b/servers/audio/effects/audio_stream_generator.h @@ -67,7 +67,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..81735d522f 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,156 @@ 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; + + 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.87; + float fadeout_coefficient = 1; + static_assert(LOOKAHEAD_BUFFER_SIZE == 32, "Update fadeout_base and comment here if you change LOOKAHEAD_BUFFER_SIZE."); + // 0.87 ^ 32 = 0.0116. 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]; + } + } + + ERR_FAIL_COND(playback->bus_details.load() == 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 = nullptr; + { + std::atomic<AudioStreamPlaybackBusDetails *> bus_details_atomic = nullptr; + bus_details = playback->bus_details.exchange(bus_details_atomic); + } + ERR_FAIL_COND(bus_details == nullptr); + AudioStreamPlaybackBusDetails *prev_bus_details = playback->prev_bus_details; + + // 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 (!prev_bus_details->bus_active[search_idx]) { + continue; + } + if (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 = 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 (!prev_bus_details->bus_active[idx]) { + continue; + } + int bus_idx = thread_find_bus_index(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] == 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 = 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(prev_bus_details->bus_active)); + std::copy(std::begin(bus_details->bus), std::end(bus_details->bus), std::begin(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(prev_bus_details->volume[bus_idx])); + } + + AudioStreamPlaybackBusDetails *bus_details_expected = nullptr; + // Only put the bus details pointer back if it hasn't been updated already. + if (!playback->bus_details.compare_exchange_strong(/* expected= */ bus_details_expected, /* new= */ bus_details)) { + // If it *has* been updated already, queue the old one for deletion. + bus_details_graveyard.insert(bus_details); + } - //make callbacks for mixing the audio - for (Set<CallbackItem>::Element *E = callbacks.front(); E; E = E->next()) { - E->get().callback(E->get().userdata); + 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 +617,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 +1123,216 @@ 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) { + 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->setseek.set(-1); + playback_node->pitch_scale.set(1); + playback_node->highshelf_gain.set(0); + playback_node->attenuation_filter_cutoff_hz.set(0); + + 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(); + 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) { + 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]; + } + } + + 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; + } + if (!p_paused && playback_node->state == AudioStreamPlaybackListNode::PLAYING) { + return; // No-op. + } + if (p_paused && (playback_node->state == AudioStreamPlaybackListNode::PAUSED || playback_node->state == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE)) { + return; // No-op. + } + + AudioStreamPlaybackListNode::PlaybackState new_state, old_state; + do { + old_state = playback_node->state.load(); + new_state = p_paused ? AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE : AudioStreamPlaybackListNode::PLAYING; + } 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 +1350,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 +1437,17 @@ 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) { + bus_details_graveyard.erase(bus_details, [](AudioStreamPlaybackBusDetails *d) { delete d; }); + } + bus_details_graveyard.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..affcb3df7b 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 = 32, }; typedef void (*AudioCallback)(void *p_userdata); @@ -219,7 +227,43 @@ 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; + Vector<Vector<AudioFrame>> temp_buffer; //temp_buffer for each level + Vector<AudioFrame> mix_buffer; Vector<Bus *> buses; Map<StringName, Bus *> bus_map; @@ -230,18 +274,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 +364,25 @@ public: void set_playback_speed_scale(float p_scale); float get_playback_speed_scale() const; + void start_playback_stream(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time = 0); + void start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time = 0); + 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 +404,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_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_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/physics_server_2d_sw.cpp b/servers/physics_2d/physics_server_2d_sw.cpp index 88c097453e..813e5ddc17 100644 --- a/servers/physics_2d/physics_server_2d_sw.cpp +++ b/servers/physics_2d/physics_server_2d_sw.cpp @@ -45,6 +45,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 +82,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); } @@ -939,7 +946,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,7 +954,7 @@ 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) { diff --git a/servers/physics_2d/physics_server_2d_sw.h b/servers/physics_2d/physics_server_2d_sw.h index 3610f43f93..c5b6d550d7 100644 --- a/servers/physics_2d/physics_server_2d_sw.h +++ b/servers/physics_2d/physics_server_2d_sw.h @@ -88,6 +88,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; @@ -246,7 +247,7 @@ public: 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..05d4ac64b0 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) @@ -252,9 +253,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 2de0f3cb9f..1185d343ee 100644 --- a/servers/physics_2d/shape_2d_sw.h +++ b/servers/physics_2d/shape_2d_sw.h @@ -34,18 +34,6 @@ #include "servers/physics_server_2d.h" #define _SEGMENT_IS_VALID_SUPPORT_THRESHOLD 0.99998 -/* - -SHAPE_WORLD_MARGIN, ///< plane:"plane" -SHAPE_SEGMENT, ///< real_t:"length" -SHAPE_CIRCLE, ///< real_t:"radius" -SHAPE_RECTANGLE, ///< vec3:"extents" -SHAPE_CONVEX_POLYGON, ///< array of planes:"planes" -SHAPE_CONCAVE_POLYGON, ///< Vector2 array:"triangles" , or Dictionary with "indices" (int array) and "triangles" (Vector2 array) -SHAPE_CUSTOM, ///< Server-Implementation based custom shape, calling shape_create() with this value will result in an error - -*/ - class Shape2DSW; class ShapeOwner2DSW { @@ -76,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; @@ -137,19 +127,19 @@ public: }; //let the optimizer do the magic -#define DEFAULT_PROJECT_RANGE_CAST \ - virtual void project_range_castv(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { \ - project_range_cast(p_cast, p_normal, p_transform, r_min, r_max); \ - } \ - _FORCE_INLINE_ void project_range_cast(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { \ - real_t mina, maxa; \ - real_t minb, maxb; \ - Transform2D ofsb = p_transform; \ - ofsb.elements[2] += p_cast; \ - project_range(p_normal, p_transform, mina, maxa); \ - project_range(p_normal, ofsb, minb, maxb); \ - r_min = MIN(mina, minb); \ - r_max = MAX(maxa, maxb); \ +#define DEFAULT_PROJECT_RANGE_CAST \ + virtual void project_range_castv(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { \ + project_range_cast(p_cast, p_normal, p_transform, r_min, r_max); \ + } \ + _FORCE_INLINE_ void project_range_cast(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { \ + real_t mina, maxa; \ + real_t minb, maxb; \ + Transform2D ofsb = p_transform; \ + ofsb.elements[2] += p_cast; \ + project_range(p_normal, p_transform, mina, maxa); \ + project_range(p_normal, ofsb, minb, maxb); \ + r_min = MIN(mina, minb); \ + r_max = MAX(maxa, maxb); \ } class WorldMarginShape2DSW : public Shape2DSW { @@ -160,17 +150,17 @@ public: _FORCE_INLINE_ Vector2 get_normal() const { return normal; } _FORCE_INLINE_ real_t get_d() const { return d; } - virtual PhysicsServer2D::ShapeType get_type() const { return PhysicsServer2D::SHAPE_WORLD_MARGIN; } + virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_WORLD_MARGIN; } - virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { 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; + 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; - virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const; - virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const; + 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); - virtual Variant get_data() const; + 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 @@ -178,7 +168,7 @@ public: r_max = 1e10; } - virtual void project_range_castv(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { + virtual void project_range_castv(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { project_range_cast(p_cast, p_normal, p_transform, r_min, r_max); } @@ -189,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; @@ -199,20 +226,20 @@ public: _FORCE_INLINE_ const Vector2 &get_b() const { return b; } _FORCE_INLINE_ const Vector2 &get_normal() const { return n; } - virtual PhysicsServer2D::ShapeType get_type() const { return PhysicsServer2D::SHAPE_SEGMENT; } + virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_SEGMENT; } _FORCE_INLINE_ Vector2 get_xformed_normal(const Transform2D &p_xform) const { return (p_xform.xform(b) - p_xform.xform(a)).normalized().orthogonal(); } - virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { 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; + 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; - virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const; - virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const; + 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); - virtual Variant get_data() const; + 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 @@ -239,17 +266,17 @@ class CircleShape2DSW : public Shape2DSW { public: _FORCE_INLINE_ const real_t &get_radius() const { return radius; } - virtual PhysicsServer2D::ShapeType get_type() const { return PhysicsServer2D::SHAPE_CIRCLE; } + virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_CIRCLE; } - virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { 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; + 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; - virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const; - virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const; + 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); - virtual Variant get_data() const; + 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 @@ -272,17 +299,17 @@ class RectangleShape2DSW : public Shape2DSW { public: _FORCE_INLINE_ const Vector2 &get_half_extents() const { return half_extents; } - virtual PhysicsServer2D::ShapeType get_type() const { return PhysicsServer2D::SHAPE_RECTANGLE; } + virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_RECTANGLE; } - virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { 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; + 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; - virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const; - virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const; + 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); - virtual Variant get_data() const; + 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 { // no matter the angle, the box is mirrored anyway @@ -346,17 +373,17 @@ public: _FORCE_INLINE_ const real_t &get_radius() const { return radius; } _FORCE_INLINE_ const real_t &get_height() const { return height; } - virtual PhysicsServer2D::ShapeType get_type() const { return PhysicsServer2D::SHAPE_CAPSULE; } + virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_CAPSULE; } - virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { 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; + 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; - virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const; - virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const; + 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); - virtual Variant get_data() const; + 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 { // no matter the angle, the box is mirrored anyway @@ -399,17 +426,17 @@ public: return (p_xform.xform(b) - p_xform.xform(a)).normalized().orthogonal(); } - virtual PhysicsServer2D::ShapeType get_type() const { return PhysicsServer2D::SHAPE_CONVEX_POLYGON; } + virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_CONVEX_POLYGON; } - virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { 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; + 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; - virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const; - virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const; + 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); - virtual Variant get_data() const; + 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 { if (!points || point_count <= 0) { @@ -437,10 +464,12 @@ public: class ConcaveShape2DSW : public Shape2DSW { public: - virtual bool is_concave() const { return true; } - typedef void (*Callback)(void *p_userdata, Shape2DSW *p_convex); + virtual bool is_concave() const override { return true; } - 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 { @@ -474,23 +503,31 @@ class ConcavePolygonShape2DSW : public ConcaveShape2DSW { int _generate_bvh(BVH *p_bvh, int p_len, int p_depth); public: - virtual PhysicsServer2D::ShapeType get_type() const { return PhysicsServer2D::SHAPE_CONCAVE_POLYGON; } + virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_CONCAVE_POLYGON; } - virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { /*project_range(p_normal,p_transform,r_min,r_max);*/ + virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { + r_min = 0; + r_max = 0; + ERR_FAIL_MSG("Unsupported call to project_rangev in ConcavePolygonShape2DSW"); } - virtual void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { /*project_range(p_normal,p_transform,r_min,r_max);*/ + + void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { + r_min = 0; + r_max = 0; + ERR_FAIL_MSG("Unsupported call to project_range in ConcavePolygonShape2DSW"); } - virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const; - virtual bool contains_point(const Vector2 &p_point) const; - virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const; + 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 { return 0; } + virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const override { return 0; } - 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; - virtual void cull(const Rect2 &p_local_aabb, Callback p_callback, void *p_userdata) const; + 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..c0493749e0 100644 --- a/servers/physics_2d/space_2d_sw.cpp +++ b/servers/physics_2d/space_2d_sw.cpp @@ -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); @@ -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); diff --git a/servers/physics_2d/space_2d_sw.h b/servers/physics_2d/space_2d_sw.h index 3be36852b0..392df9b025 100644 --- a/servers/physics_2d/space_2d_sw.h +++ b/servers/physics_2d/space_2d_sw.h @@ -183,7 +183,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_3d/area_3d_sw.cpp b/servers/physics_3d/area_3d_sw.cpp index 364f63e4ad..c292abad48 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(); diff --git a/servers/physics_3d/area_3d_sw.h b/servers/physics_3d/area_3d_sw.h index 5959ee1e95..814472885c 100644 --- a/servers/physics_3d/area_3d_sw.h +++ b/servers/physics_3d/area_3d_sw.h @@ -50,6 +50,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; @@ -154,6 +158,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; } 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 ea6064cb4c..0c4079332d 100644 --- a/servers/physics_3d/body_3d_sw.cpp +++ b/servers/physics_3d/body_3d_sw.cpp @@ -578,7 +578,7 @@ void Body3DSW::integrate_velocities(real_t p_step) { real_t ang_vel = total_angular_velocity.length(); Transform3D transform = get_transform(); - if (ang_vel != 0.0) { + if (!Math::is_zero_approx(ang_vel)) { Vector3 ang_vel_axis = total_angular_velocity / ang_vel; Basis rot(ang_vel_axis, ang_vel * p_step); Basis identity3(1, 0, 0, 0, 1, 0, 0, 0, 1); 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..fbc6f7eee8 100644 --- a/servers/physics_3d/physics_server_3d_sw.cpp +++ b/servers/physics_3d/physics_server_3d_sw.cpp @@ -48,6 +48,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); @@ -848,7 +854,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,7 +862,7 @@ 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) { diff --git a/servers/physics_3d/physics_server_3d_sw.h b/servers/physics_3d/physics_server_3d_sw.h index 6e59a77e89..a18593c90c 100644 --- a/servers/physics_3d/physics_server_3d_sw.h +++ b/servers/physics_3d/physics_server_3d_sw.h @@ -83,6 +83,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; @@ -241,7 +242,7 @@ public: 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..674c52d4a3 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) @@ -249,9 +250,9 @@ public: 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..b81d3272c3 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; @@ -1790,7 +1882,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; } @@ -1826,13 +1918,17 @@ void HeightMapShape3DSW::cull(const AABB &p_local_aabb, Callback p_callback, voi _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); + 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); + if (p_callback(p_userdata, &face)) { + return; + } } } } diff --git a/servers/physics_3d/shape_3d_sw.h b/servers/physics_3d/shape_3d_sw.h index c11c3a08f6..b05f65f268 100644 --- a/servers/physics_3d/shape_3d_sw.h +++ b/servers/physics_3d/shape_3d_sw.h @@ -33,17 +33,6 @@ #include "core/math/geometry_3d.h" #include "servers/physics_server_3d.h" -/* - -SHAPE_LINE, ///< plane:"plane" -SHAPE_SEGMENT, ///< real_t:"length" -SHAPE_CIRCLE, ///< real_t:"radius" -SHAPE_RECTANGLE, ///< vec3:"extents" -SHAPE_CONVEX_POLYGON, ///< array of planes:"planes" -SHAPE_CONCAVE_POLYGON, ///< Vector3 array:"triangles" , or Dictionary with "indices" (int array) and "triangles" (Vector3 array) -SHAPE_CUSTOM, ///< Server-Implementation based custom shape, calling shape_create() with this value will result in an error - -*/ class Shape3DSW; @@ -111,11 +100,13 @@ public: class ConcaveShape3DSW : public Shape3DSW { public: - virtual bool is_concave() const { 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 { r_amount = 0; } + virtual bool is_concave() const override { return true; } + 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; } + + // Returns true to stop the query. + typedef bool (*QueryCallback)(void *p_userdata, Shape3DSW *p_convex); - virtual void cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const = 0; + virtual void cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata) const = 0; ConcaveShape3DSW() {} }; @@ -128,23 +119,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; @@ -153,21 +172,21 @@ class SphereShape3DSW : public Shape3DSW { public: real_t get_radius() const; - virtual real_t get_area() const { return 4.0 / 3.0 * Math_PI * radius * radius * radius; } + virtual real_t get_area() const override { return 4.0 / 3.0 * Math_PI * radius * radius * radius; } - virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_SPHERE; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_SPHERE; } - 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; - 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 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; + 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; SphereShape3DSW(); }; @@ -178,21 +197,21 @@ class BoxShape3DSW : public Shape3DSW { public: _FORCE_INLINE_ Vector3 get_half_extents() const { return half_extents; } - virtual real_t get_area() const { return 8 * half_extents.x * half_extents.y * half_extents.z; } + virtual real_t get_area() const override { return 8 * half_extents.x * half_extents.y * half_extents.z; } - virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_BOX; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_BOX; } - 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; - 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 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; + 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; BoxShape3DSW(); }; @@ -207,21 +226,21 @@ public: _FORCE_INLINE_ real_t get_height() const { return height; } _FORCE_INLINE_ real_t get_radius() const { return radius; } - virtual real_t get_area() const { return 4.0 / 3.0 * Math_PI * radius * radius * radius + (height - radius * 2.0) * Math_PI * radius * radius; } + virtual real_t get_area() const override { return 4.0 / 3.0 * Math_PI * radius * radius * radius + (height - radius * 2.0) * Math_PI * radius * radius; } - virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_CAPSULE; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CAPSULE; } - 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; - 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 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; + 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; CapsuleShape3DSW(); }; @@ -236,21 +255,21 @@ public: _FORCE_INLINE_ real_t get_height() const { return height; } _FORCE_INLINE_ real_t get_radius() const { return radius; } - virtual real_t get_area() const { return 4.0 / 3.0 * Math_PI * radius * radius * radius + height * Math_PI * radius * radius; } + virtual real_t get_area() const override { return 4.0 / 3.0 * Math_PI * radius * radius * radius + height * Math_PI * radius * radius; } - virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_CYLINDER; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CYLINDER; } - 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; - 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 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; + 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; CylinderShape3DSW(); }; @@ -263,19 +282,19 @@ struct ConvexPolygonShape3DSW : public Shape3DSW { public: const Geometry3D::MeshData &get_mesh() const { return mesh; } - virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_CONVEX_POLYGON; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CONVEX_POLYGON; } - 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; - 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 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; + 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; ConvexPolygonShape3DSW(); }; @@ -306,7 +325,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; @@ -332,7 +351,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); @@ -341,21 +360,21 @@ struct ConcavePolygonShape3DSW : public ConcaveShape3DSW { public: Vector<Vector3> get_faces() const; - virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_CONCAVE_POLYGON; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CONCAVE_POLYGON; } - 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 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 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 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 void cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const; + 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; + 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; ConcavePolygonShape3DSW(); }; @@ -385,20 +404,20 @@ public: int get_width() const; int get_depth() const; - virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_HEIGHTMAP; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_HEIGHTMAP; } - 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 bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal) const; - virtual bool intersect_point(const Vector3 &p_point) const; + 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 bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, 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; - virtual void cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) 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; + 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; HeightMapShape3DSW(); }; @@ -409,21 +428,21 @@ struct FaceShape3DSW : public Shape3DSW { Vector3 vertex[3]; bool backface_collision = false; - virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_CONCAVE_POLYGON; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CONCAVE_POLYGON; } const Vector3 &get_vertex(int p_idx) const { return vertex[p_idx]; } - void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const; - 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; - 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 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; - Vector3 get_moment_of_inertia(real_t p_mass) const; + 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 { return Variant(); } + virtual void set_data(const Variant &p_data) override {} + virtual Variant get_data() const override { return Variant(); } FaceShape3DSW(); }; @@ -432,9 +451,9 @@ struct MotionShape3DSW : public Shape3DSW { Shape3DSW *shape; Vector3 motion; - virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_CONVEX_POLYGON; } + virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_CONVEX_POLYGON; } - void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const { + virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override { Vector3 cast = p_transform.basis.xform(motion); real_t mina, maxa; real_t minb, maxb; @@ -446,22 +465,23 @@ struct MotionShape3DSW : public Shape3DSW { r_max = MAX(maxa, maxb); } - Vector3 get_support(const Vector3 &p_normal) const { + virtual Vector3 get_support(const Vector3 &p_normal) const override { Vector3 support = shape->get_support(p_normal); if (p_normal.dot(motion) > 0) { support += motion; } return support; } - virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { r_amount = 0; } - bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal) const { return false; } - virtual bool intersect_point(const Vector3 &p_point) const { return false; } - virtual Vector3 get_closest_point_to(const Vector3 &p_point) const { return p_point; } - Vector3 get_moment_of_inertia(real_t p_mass) const { return Vector3(); } + 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 override { return false; } + virtual bool intersect_point(const Vector3 &p_point) const override { return false; } + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override { return p_point; } + + virtual Vector3 get_moment_of_inertia(real_t p_mass) const override { return Vector3(); } - virtual void set_data(const Variant &p_data) {} - virtual Variant get_data() const { return Variant(); } + virtual void set_data(const Variant &p_data) override {} + virtual Variant get_data() const override { return Variant(); } MotionShape3DSW() { configure(AABB()); } }; diff --git a/servers/physics_3d/soft_body_3d_sw.cpp b/servers/physics_3d/soft_body_3d_sw.cpp index 73b81444e1..ead6497f1f 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,32 +899,66 @@ 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); + } } } } @@ -941,6 +976,18 @@ void SoftBody3DSW::_compute_area_gravity(const Area3DSW *p_area) { } } +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) { const real_t inv_delta = 1.0 / p_delta; @@ -952,11 +999,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 +1029,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 +1110,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..37dee436df 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; diff --git a/servers/physics_3d/space_3d_sw.h b/servers/physics_3d/space_3d_sw.h index 9b5b4de069..1c3d1cf9f6 100644 --- a/servers/physics_3d/space_3d_sw.h +++ b/servers/physics_3d/space_3d_sw.h @@ -203,7 +203,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_server_2d.cpp b/servers/physics_server_2d.cpp index c96f85446c..4660b83f4d 100644 --- a/servers/physics_server_2d.cpp +++ b/servers/physics_server_2d.cpp @@ -500,7 +500,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 +509,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); @@ -635,7 +636,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 +676,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); diff --git a/servers/physics_server_2d.h b/servers/physics_server_2d.h index 4c559dd7bd..9afac0cbd1 100644 --- a/servers/physics_server_2d.h +++ b/servers/physics_server_2d.h @@ -210,7 +210,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 +220,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 +231,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; @@ -485,7 +487,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..6cde4977f4 100644 --- a/servers/physics_server_3d.cpp +++ b/servers/physics_server_3d.cpp @@ -447,7 +447,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 +456,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 +490,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); @@ -609,7 +612,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 +751,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 +770,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); diff --git a/servers/physics_server_3d.h b/servers/physics_server_3d.h index f806fd55be..8f83e360f2 100644 --- a/servers/physics_server_3d.h +++ b/servers/physics_server_3d.h @@ -212,7 +212,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 +222,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 +237,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 +300,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; @@ -493,7 +499,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_rd/effects_rd.cpp b/servers/rendering/renderer_rd/effects_rd.cpp index f5b6be4795..236eb5e596 100644 --- a/servers/rendering/renderer_rd/effects_rd.cpp +++ b/servers/rendering/renderer_rd/effects_rd.cpp @@ -759,7 +759,7 @@ void EffectsRD::make_mipmap_raster(RID p_source_rd_texture, RID p_dest_framebuff RD::get_singleton()->draw_list_end(); } -void EffectsRD::copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffer, const Rect2 &p_rect, float p_z_near, float p_z_far, bool p_dp_flip) { +void EffectsRD::copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffer, const Rect2 &p_rect, const Vector2 &p_dst_size, float p_z_near, float p_z_far, bool p_dp_flip) { CopyToDPPushConstant push_constant; push_constant.screen_rect[0] = p_rect.position.x; push_constant.screen_rect[1] = p_rect.position.y; @@ -767,7 +767,9 @@ void EffectsRD::copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffe push_constant.screen_rect[3] = p_rect.size.height; push_constant.z_far = p_z_far; push_constant.z_near = p_z_near; - push_constant.z_flip = p_dp_flip; + push_constant.texel_size[0] = 1.0f / p_dst_size.x; + push_constant.texel_size[1] = 1.0f / p_dst_size.y; + push_constant.texel_size[0] *= p_dp_flip ? -1.0f : 1.0f; // Encode dp flip as x size sign RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_dst_framebuffer, RD::INITIAL_ACTION_DROP, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ); RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, cube_to_dp.pipeline.get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(p_dst_framebuffer))); @@ -810,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; @@ -862,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); @@ -1914,7 +1918,7 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) { } } - { // Initialize copy + if (!prefer_raster_effects) { // Initialize copy Vector<String> copy_modes; copy_modes.push_back("\n#define MODE_GAUSSIAN_BLUR\n"); copy_modes.push_back("\n#define MODE_GAUSSIAN_BLUR\n#define DST_IMAGE_8BIT\n"); @@ -2113,7 +2117,7 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) { } } - { + if (!prefer_raster_effects) { // Initialize ssao RD::SamplerState sampler; @@ -2169,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; @@ -2257,7 +2259,7 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) { ERR_FAIL_COND(pipeline != SSAO_MAX); } - { + if (!prefer_raster_effects) { // Initialize roughness limiter Vector<String> shader_modes; shader_modes.push_back(""); @@ -2355,7 +2357,7 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) { } } - { + if (!prefer_raster_effects) { Vector<String> specular_modes; specular_modes.push_back("\n#define MODE_MERGE\n"); specular_modes.push_back("\n#define MODE_MERGE\n#define MODE_SSR\n"); @@ -2391,72 +2393,74 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) { } } - { - Vector<String> ssr_modes; - ssr_modes.push_back("\n"); - ssr_modes.push_back("\n#define MODE_ROUGH\n"); + if (!prefer_raster_effects) { + { + Vector<String> ssr_modes; + ssr_modes.push_back("\n"); + ssr_modes.push_back("\n#define MODE_ROUGH\n"); - ssr.shader.initialize(ssr_modes); + ssr.shader.initialize(ssr_modes); - ssr.shader_version = ssr.shader.version_create(); + ssr.shader_version = ssr.shader.version_create(); - for (int i = 0; i < SCREEN_SPACE_REFLECTION_MAX; i++) { - ssr.pipelines[i] = RD::get_singleton()->compute_pipeline_create(ssr.shader.version_get_shader(ssr.shader_version, i)); + for (int i = 0; i < SCREEN_SPACE_REFLECTION_MAX; i++) { + ssr.pipelines[i] = RD::get_singleton()->compute_pipeline_create(ssr.shader.version_get_shader(ssr.shader_version, i)); + } } - } - { - Vector<String> ssr_filter_modes; - ssr_filter_modes.push_back("\n"); - ssr_filter_modes.push_back("\n#define VERTICAL_PASS\n"); + { + Vector<String> ssr_filter_modes; + ssr_filter_modes.push_back("\n"); + ssr_filter_modes.push_back("\n#define VERTICAL_PASS\n"); - ssr_filter.shader.initialize(ssr_filter_modes); + ssr_filter.shader.initialize(ssr_filter_modes); - ssr_filter.shader_version = ssr_filter.shader.version_create(); + ssr_filter.shader_version = ssr_filter.shader.version_create(); - for (int i = 0; i < SCREEN_SPACE_REFLECTION_FILTER_MAX; i++) { - ssr_filter.pipelines[i] = RD::get_singleton()->compute_pipeline_create(ssr_filter.shader.version_get_shader(ssr_filter.shader_version, i)); + for (int i = 0; i < SCREEN_SPACE_REFLECTION_FILTER_MAX; i++) { + ssr_filter.pipelines[i] = RD::get_singleton()->compute_pipeline_create(ssr_filter.shader.version_get_shader(ssr_filter.shader_version, i)); + } } - } - { - Vector<String> ssr_scale_modes; - ssr_scale_modes.push_back("\n"); + { + Vector<String> ssr_scale_modes; + ssr_scale_modes.push_back("\n"); - ssr_scale.shader.initialize(ssr_scale_modes); + ssr_scale.shader.initialize(ssr_scale_modes); - ssr_scale.shader_version = ssr_scale.shader.version_create(); + ssr_scale.shader_version = ssr_scale.shader.version_create(); - ssr_scale.pipeline = RD::get_singleton()->compute_pipeline_create(ssr_scale.shader.version_get_shader(ssr_scale.shader_version, 0)); - } + ssr_scale.pipeline = RD::get_singleton()->compute_pipeline_create(ssr_scale.shader.version_get_shader(ssr_scale.shader_version, 0)); + } - { - Vector<String> sss_modes; - sss_modes.push_back("\n#define USE_11_SAMPLES\n"); - sss_modes.push_back("\n#define USE_17_SAMPLES\n"); - sss_modes.push_back("\n#define USE_25_SAMPLES\n"); + { + Vector<String> sss_modes; + sss_modes.push_back("\n#define USE_11_SAMPLES\n"); + sss_modes.push_back("\n#define USE_17_SAMPLES\n"); + sss_modes.push_back("\n#define USE_25_SAMPLES\n"); - sss.shader.initialize(sss_modes); + sss.shader.initialize(sss_modes); - sss.shader_version = sss.shader.version_create(); + sss.shader_version = sss.shader.version_create(); - for (int i = 0; i < sss_modes.size(); i++) { - sss.pipelines[i] = RD::get_singleton()->compute_pipeline_create(sss.shader.version_get_shader(sss.shader_version, i)); + for (int i = 0; i < sss_modes.size(); i++) { + sss.pipelines[i] = RD::get_singleton()->compute_pipeline_create(sss.shader.version_get_shader(sss.shader_version, i)); + } } - } - { - Vector<String> resolve_modes; - resolve_modes.push_back("\n#define MODE_RESOLVE_GI\n"); - resolve_modes.push_back("\n#define MODE_RESOLVE_GI\n#define VOXEL_GI_RESOLVE\n"); - resolve_modes.push_back("\n#define MODE_RESOLVE_DEPTH\n"); + { + Vector<String> resolve_modes; + resolve_modes.push_back("\n#define MODE_RESOLVE_GI\n"); + resolve_modes.push_back("\n#define MODE_RESOLVE_GI\n#define VOXEL_GI_RESOLVE\n"); + resolve_modes.push_back("\n#define MODE_RESOLVE_DEPTH\n"); - resolve.shader.initialize(resolve_modes); + resolve.shader.initialize(resolve_modes); - resolve.shader_version = resolve.shader.version_create(); + resolve.shader_version = resolve.shader.version_create(); - for (int i = 0; i < RESOLVE_MODE_MAX; i++) { - resolve.pipelines[i] = RD::get_singleton()->compute_pipeline_create(resolve.shader.version_get_shader(resolve.shader_version, i)); + for (int i = 0; i < RESOLVE_MODE_MAX; i++) { + resolve.pipelines[i] = RD::get_singleton()->compute_pipeline_create(resolve.shader.version_get_shader(resolve.shader_version, i)); + } } } @@ -2522,10 +2526,6 @@ EffectsRD::~EffectsRD() { RD::get_singleton()->free(index_buffer); //array gets freed as dependency RD::get_singleton()->free(filter.coefficient_buffer); - RD::get_singleton()->free(ssao.mirror_sampler); - RD::get_singleton()->free(ssao.gather_constants_buffer); - RD::get_singleton()->free(ssao.importance_map_load_counter); - if (prefer_raster_effects) { blur_raster.shader.version_free(blur_raster.shader_version); bokeh.raster_shader.version_free(blur_raster.shader_version); @@ -2540,21 +2540,27 @@ EffectsRD::~EffectsRD() { cubemap_downsampler.compute_shader.version_free(cubemap_downsampler.shader_version); filter.compute_shader.version_free(filter.shader_version); } - copy.shader.version_free(copy.shader_version); + if (!prefer_raster_effects) { + copy.shader.version_free(copy.shader_version); + resolve.shader.version_free(resolve.shader_version); + specular_merge.shader.version_free(specular_merge.shader_version); + ssao.blur_shader.version_free(ssao.blur_shader_version); + ssao.gather_shader.version_free(ssao.gather_shader_version); + ssao.downsample_shader.version_free(ssao.downsample_shader_version); + ssao.interleave_shader.version_free(ssao.interleave_shader_version); + ssao.importance_map_shader.version_free(ssao.importance_map_shader_version); + roughness_limiter.shader.version_free(roughness_limiter.shader_version); + ssr.shader.version_free(ssr.shader_version); + ssr_filter.shader.version_free(ssr_filter.shader_version); + ssr_scale.shader.version_free(ssr_scale.shader_version); + sss.shader.version_free(sss.shader_version); + + RD::get_singleton()->free(ssao.mirror_sampler); + RD::get_singleton()->free(ssao.gather_constants_buffer); + RD::get_singleton()->free(ssao.importance_map_load_counter); + } copy_to_fb.shader.version_free(copy_to_fb.shader_version); cube_to_dp.shader.version_free(cube_to_dp.shader_version); - resolve.shader.version_free(resolve.shader_version); - roughness_limiter.shader.version_free(roughness_limiter.shader_version); sort.shader.version_free(sort.shader_version); - specular_merge.shader.version_free(specular_merge.shader_version); - ssao.blur_shader.version_free(ssao.blur_shader_version); - ssao.gather_shader.version_free(ssao.gather_shader_version); - ssao.downsample_shader.version_free(ssao.downsample_shader_version); - ssao.interleave_shader.version_free(ssao.interleave_shader_version); - ssao.importance_map_shader.version_free(ssao.importance_map_shader_version); - ssr.shader.version_free(ssr.shader_version); - ssr_filter.shader.version_free(ssr_filter.shader_version); - ssr_scale.shader.version_free(ssr_scale.shader_version); - sss.shader.version_free(sss.shader_version); tonemap.shader.version_free(tonemap.shader_version); } diff --git a/servers/rendering/renderer_rd/effects_rd.h b/servers/rendering/renderer_rd/effects_rd.h index 0c9b2efb7f..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 { @@ -321,8 +321,7 @@ private: struct CopyToDPPushConstant { float z_far; float z_near; - uint32_t z_flip; - uint32_t pad; + float texel_size[2]; float screen_rect[4]; }; @@ -770,7 +769,7 @@ public: void cubemap_roughness_raster(RID p_source_rd_texture, RID p_dest_framebuffer, uint32_t p_face_id, uint32_t p_sample_count, float p_roughness, float p_size); void make_mipmap(RID p_source_rd_texture, RID p_dest_texture, const Size2i &p_size); void make_mipmap_raster(RID p_source_rd_texture, RID p_dest_framebuffer, const Size2i &p_size); - void copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dest_texture, const Rect2 &p_rect, float p_z_near, float p_z_far, bool p_dp_flip); + void copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffer, const Rect2 &p_rect, const Vector2 &p_dst_size, float p_z_near, float p_z_far, bool p_dp_flip); void luminance_reduction(RID p_source_texture, const Size2i p_source_size, const Vector<RID> p_reduce, RID p_prev_luminance, float p_min_luminance, float p_max_luminance, float p_adjust, bool p_set = false); void luminance_reduction_raster(RID p_source_texture, const Size2i p_source_size, const Vector<RID> p_reduce, Vector<RID> p_fb, RID p_prev_luminance, float p_min_luminance, float p_max_luminance, float p_adjust, bool p_set = false); @@ -819,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 5a0ad97c7c..611f7c6494 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -3062,7 +3062,7 @@ RenderForwardClustered::RenderForwardClustered(RendererStorageRD *p_storage) : defines += "\n#define USE_RADIANCE_CUBEMAP_ARRAY \n"; } defines += "\n#define SDFGI_OCT_SIZE " + itos(gi.sdfgi_get_lightprobe_octahedron_size()) + "\n"; - defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(get_max_directional_lights()) + "\n"; + defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(MAX_DIRECTIONAL_LIGHTS) + "\n"; { //lightmaps 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..a24860996c 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 @@ -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 96b3cdadd4..1b4052b622 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 @@ -2630,7 +2636,7 @@ RenderForwardMobile::RenderForwardMobile(RendererStorageRD *p_storage) : defines += "\n#define USE_RADIANCE_CUBEMAP_ARRAY \n"; } // defines += "\n#define SDFGI_OCT_SIZE " + itos(gi.sdfgi_get_lightprobe_octahedron_size()) + "\n"; - defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(get_max_directional_lights()) + "\n"; + defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(MAX_DIRECTIONAL_LIGHTS) + "\n"; { //lightmaps 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..14b3b6d9aa 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 @@ -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..6267f684e3 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; @@ -2575,6 +2583,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 02d548bf13..62e9386f95 100644 --- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp @@ -281,6 +281,8 @@ RendererCompositorRD::RendererCompositorRD() { scene = memnew(RendererSceneRenderImplementation::RenderForwardClustered(storage)); } + scene->init(); + // now we're ready to create our effects, storage->init_effects(!scene->_render_buffers_can_be_storage()); } diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp index 98d08f68e8..098e2a5c87 100644 --- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp @@ -2812,8 +2812,6 @@ void RendererSceneGIRD::init(RendererStorageRD *p_storage, RendererSceneSkyRD *p { //kinda complicated to compute the amount of slots, we try to use as many as we can - voxel_gi_max_lights = 32; - voxel_gi_lights = memnew_arr(VoxelGILight, voxel_gi_max_lights); voxel_gi_lights_uniform = RD::get_singleton()->uniform_buffer_create(voxel_gi_max_lights * sizeof(VoxelGILight)); voxel_gi_quality = RS::VoxelGIQuality(CLAMP(int(GLOBAL_GET("rendering/global_illumination/voxel_gi/quality")), 0, 1)); @@ -3009,7 +3007,9 @@ void RendererSceneGIRD::free() { sdfgi_shader.integrate.version_free(sdfgi_shader.integrate_shader); sdfgi_shader.preprocess.version_free(sdfgi_shader.preprocess_shader); - memdelete_arr(voxel_gi_lights); + if (voxel_gi_lights) { + memdelete_arr(voxel_gi_lights); + } } RendererSceneGIRD::SDFGI *RendererSceneGIRD::create_sdfgi(RendererSceneEnvironmentRD *p_env, const Vector3 &p_world_position, uint32_t p_requested_history_size) { diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.h b/servers/rendering/renderer_rd/renderer_scene_gi_rd.h index 128bf09063..0b4622646f 100644 --- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.h @@ -110,8 +110,8 @@ private: float pad[3]; }; - VoxelGILight *voxel_gi_lights; - uint32_t voxel_gi_max_lights; + VoxelGILight *voxel_gi_lights = nullptr; + uint32_t voxel_gi_max_lights = 32; RID voxel_gi_lights_uniform; enum { diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 5016c99a1e..fa66ed85a9 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -860,7 +860,7 @@ void RendererSceneRenderRD::shadow_atlas_set_size(RID p_atlas, int p_size, bool shadow_atlas->shadow_owners.clear(); shadow_atlas->size = p_size; - shadow_atlas->use_16_bits = p_size; + shadow_atlas->use_16_bits = p_16_bits; } void RendererSceneRenderRD::shadow_atlas_set_quadrant_subdivision(RID p_atlas, int p_quadrant, int p_subdivision) { @@ -935,7 +935,7 @@ bool RendererSceneRenderRD::_shadow_atlas_find_shadow(ShadowAtlas *shadow_atlas, //look for an empty space int sc = shadow_atlas->quadrants[qidx].shadows.size(); - ShadowAtlas::Quadrant::Shadow *sarr = shadow_atlas->quadrants[qidx].shadows.ptrw(); + const ShadowAtlas::Quadrant::Shadow *sarr = shadow_atlas->quadrants[qidx].shadows.ptr(); int found_free_idx = -1; //found a free one int found_used_idx = -1; //found existing one, must steal it @@ -980,6 +980,78 @@ bool RendererSceneRenderRD::_shadow_atlas_find_shadow(ShadowAtlas *shadow_atlas, return false; } +bool RendererSceneRenderRD::_shadow_atlas_find_omni_shadows(ShadowAtlas *shadow_atlas, int *p_in_quadrants, int p_quadrant_count, int p_current_subdiv, uint64_t p_tick, int &r_quadrant, int &r_shadow) { + for (int i = p_quadrant_count - 1; i >= 0; i--) { + int qidx = p_in_quadrants[i]; + + if (shadow_atlas->quadrants[qidx].subdivision == (uint32_t)p_current_subdiv) { + return false; + } + + //look for an empty space + int sc = shadow_atlas->quadrants[qidx].shadows.size(); + const ShadowAtlas::Quadrant::Shadow *sarr = shadow_atlas->quadrants[qidx].shadows.ptr(); + + int found_idx = -1; + uint64_t min_pass = 0; // sum of currently selected spots, try to get the least recently used pair + + for (int j = 0; j < sc - 1; j++) { + uint64_t pass = 0; + + if (sarr[j].owner.is_valid()) { + LightInstance *sli = light_instance_owner.getornull(sarr[j].owner); + ERR_CONTINUE(!sli); + + if (sli->last_scene_pass == scene_pass) { + continue; + } + + //was just allocated, don't kill it so soon, wait a bit.. + if (p_tick - sarr[j].alloc_tick < shadow_atlas_realloc_tolerance_msec) { + continue; + } + pass += sli->last_scene_pass; + } + + if (sarr[j + 1].owner.is_valid()) { + LightInstance *sli = light_instance_owner.getornull(sarr[j + 1].owner); + ERR_CONTINUE(!sli); + + if (sli->last_scene_pass == scene_pass) { + continue; + } + + //was just allocated, don't kill it so soon, wait a bit.. + if (p_tick - sarr[j + 1].alloc_tick < shadow_atlas_realloc_tolerance_msec) { + continue; + } + pass += sli->last_scene_pass; + } + + if (found_idx == -1 || pass < min_pass) { + found_idx = j; + min_pass = pass; + + // we found two empty spots, no need to check the rest + if (pass == 0) { + break; + } + } + } + + if (found_idx == -1) { + continue; //nothing found + } + + r_quadrant = qidx; + r_shadow = found_idx; + + return true; + } + + return false; +} + bool RendererSceneRenderRD::shadow_atlas_update_light(RID p_atlas, RID p_light_intance, float p_coverage, uint64_t p_light_version) { ShadowAtlas *shadow_atlas = shadow_atlas_owner.getornull(p_atlas); ERR_FAIL_COND_V(!shadow_atlas, false); @@ -1025,94 +1097,104 @@ bool RendererSceneRenderRD::shadow_atlas_update_light(RID p_atlas, RID p_light_i uint64_t tick = OS::get_singleton()->get_ticks_msec(); - //see if it already exists + uint32_t old_key = ShadowAtlas::SHADOW_INVALID; + uint32_t old_quadrant = ShadowAtlas::SHADOW_INVALID; + uint32_t old_shadow = ShadowAtlas::SHADOW_INVALID; + int old_subdivision = -1; + + bool should_realloc = false; + bool should_redraw = false; if (shadow_atlas->shadow_owners.has(p_light_intance)) { - //it does! - uint32_t key = shadow_atlas->shadow_owners[p_light_intance]; - uint32_t q = (key >> ShadowAtlas::QUADRANT_SHIFT) & 0x3; - uint32_t s = key & ShadowAtlas::SHADOW_INDEX_MASK; + old_key = shadow_atlas->shadow_owners[p_light_intance]; + old_quadrant = (old_key >> ShadowAtlas::QUADRANT_SHIFT) & 0x3; + old_shadow = old_key & ShadowAtlas::SHADOW_INDEX_MASK; - bool should_realloc = shadow_atlas->quadrants[q].subdivision != (uint32_t)best_subdiv && (shadow_atlas->quadrants[q].shadows[s].alloc_tick - tick > shadow_atlas_realloc_tolerance_msec); - bool should_redraw = shadow_atlas->quadrants[q].shadows[s].version != p_light_version; + should_realloc = shadow_atlas->quadrants[old_quadrant].subdivision != (uint32_t)best_subdiv && (shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].alloc_tick - tick > shadow_atlas_realloc_tolerance_msec); + should_redraw = shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].version != p_light_version; if (!should_realloc) { - shadow_atlas->quadrants[q].shadows.write[s].version = p_light_version; + shadow_atlas->quadrants[old_quadrant].shadows.write[old_shadow].version = p_light_version; //already existing, see if it should redraw or it's just OK return should_redraw; } - int new_quadrant, new_shadow; + old_subdivision = shadow_atlas->quadrants[old_quadrant].subdivision; + } - //find a better place - if (_shadow_atlas_find_shadow(shadow_atlas, valid_quadrants, valid_quadrant_count, shadow_atlas->quadrants[q].subdivision, tick, new_quadrant, new_shadow)) { - //found a better place! - ShadowAtlas::Quadrant::Shadow *sh = &shadow_atlas->quadrants[new_quadrant].shadows.write[new_shadow]; - if (sh->owner.is_valid()) { - //is taken, but is invalid, erasing it - shadow_atlas->shadow_owners.erase(sh->owner); - LightInstance *sli = light_instance_owner.getornull(sh->owner); - sli->shadow_atlases.erase(p_atlas); - } + bool is_omni = li->light_type == RS::LIGHT_OMNI; + bool found_shadow = false; + int new_quadrant = -1; + int new_shadow = -1; - //erase previous - shadow_atlas->quadrants[q].shadows.write[s].version = 0; - shadow_atlas->quadrants[q].shadows.write[s].owner = RID(); + if (is_omni) { + found_shadow = _shadow_atlas_find_omni_shadows(shadow_atlas, valid_quadrants, valid_quadrant_count, old_subdivision, tick, new_quadrant, new_shadow); + } else { + found_shadow = _shadow_atlas_find_shadow(shadow_atlas, valid_quadrants, valid_quadrant_count, old_subdivision, tick, new_quadrant, new_shadow); + } - sh->owner = p_light_intance; - sh->alloc_tick = tick; - sh->version = p_light_version; - li->shadow_atlases.insert(p_atlas); - - //make new key - key = new_quadrant << ShadowAtlas::QUADRANT_SHIFT; - key |= new_shadow; - //update it in map - shadow_atlas->shadow_owners[p_light_intance] = key; - //make it dirty, as it should redraw anyway - return true; + if (found_shadow) { + if (old_quadrant != ShadowAtlas::SHADOW_INVALID) { + shadow_atlas->quadrants[old_quadrant].shadows.write[old_shadow].version = 0; + shadow_atlas->quadrants[old_quadrant].shadows.write[old_shadow].owner = RID(); + + if (old_key & ShadowAtlas::OMNI_LIGHT_FLAG) { + shadow_atlas->quadrants[old_quadrant].shadows.write[old_shadow + 1].version = 0; + shadow_atlas->quadrants[old_quadrant].shadows.write[old_shadow + 1].owner = RID(); + } } - //no better place for this shadow found, keep current + uint32_t new_key = new_quadrant << ShadowAtlas::QUADRANT_SHIFT; + new_key |= new_shadow; - //already existing, see if it should redraw or it's just OK + ShadowAtlas::Quadrant::Shadow *sh = &shadow_atlas->quadrants[new_quadrant].shadows.write[new_shadow]; + _shadow_atlas_invalidate_shadow(sh, p_atlas, shadow_atlas, new_quadrant, new_shadow); - shadow_atlas->quadrants[q].shadows.write[s].version = p_light_version; + sh->owner = p_light_intance; + sh->alloc_tick = tick; + sh->version = p_light_version; - return should_redraw; - } + if (is_omni) { + new_key |= ShadowAtlas::OMNI_LIGHT_FLAG; - int new_quadrant, new_shadow; + int new_omni_shadow = new_shadow + 1; + ShadowAtlas::Quadrant::Shadow *extra_sh = &shadow_atlas->quadrants[new_quadrant].shadows.write[new_omni_shadow]; + _shadow_atlas_invalidate_shadow(extra_sh, p_atlas, shadow_atlas, new_quadrant, new_omni_shadow); - //find a better place - if (_shadow_atlas_find_shadow(shadow_atlas, valid_quadrants, valid_quadrant_count, -1, tick, new_quadrant, new_shadow)) { - //found a better place! - ShadowAtlas::Quadrant::Shadow *sh = &shadow_atlas->quadrants[new_quadrant].shadows.write[new_shadow]; - if (sh->owner.is_valid()) { - //is taken, but is invalid, erasing it - shadow_atlas->shadow_owners.erase(sh->owner); - LightInstance *sli = light_instance_owner.getornull(sh->owner); - sli->shadow_atlases.erase(p_atlas); + extra_sh->owner = p_light_intance; + extra_sh->alloc_tick = tick; + extra_sh->version = p_light_version; } - sh->owner = p_light_intance; - sh->alloc_tick = tick; - sh->version = p_light_version; li->shadow_atlases.insert(p_atlas); - //make new key - uint32_t key = new_quadrant << ShadowAtlas::QUADRANT_SHIFT; - key |= new_shadow; //update it in map - shadow_atlas->shadow_owners[p_light_intance] = key; + shadow_atlas->shadow_owners[p_light_intance] = new_key; //make it dirty, as it should redraw anyway - return true; } - //no place to allocate this light, apologies + return should_redraw; +} + +void RendererSceneRenderRD::_shadow_atlas_invalidate_shadow(RendererSceneRenderRD::ShadowAtlas::Quadrant::Shadow *p_shadow, RID p_atlas, RendererSceneRenderRD::ShadowAtlas *p_shadow_atlas, uint32_t p_quadrant, uint32_t p_shadow_idx) { + if (p_shadow->owner.is_valid()) { + LightInstance *sli = light_instance_owner.getornull(p_shadow->owner); + uint32_t old_key = p_shadow_atlas->shadow_owners[p_shadow->owner]; - return false; + if (old_key & ShadowAtlas::OMNI_LIGHT_FLAG) { + uint32_t s = old_key & ShadowAtlas::SHADOW_INDEX_MASK; + uint32_t omni_shadow_idx = p_shadow_idx + (s == (uint32_t)p_shadow_idx ? 1 : -1); + RendererSceneRenderRD::ShadowAtlas::Quadrant::Shadow *omni_shadow = &p_shadow_atlas->quadrants[p_quadrant].shadows.write[omni_shadow_idx]; + omni_shadow->version = 0; + omni_shadow->owner = RID(); + } + + p_shadow->version = 0; + p_shadow->owner = RID(); + sli->shadow_atlases.erase(p_atlas); + p_shadow_atlas->shadow_owners.erase(p_shadow->owner); + } } void RendererSceneRenderRD::_update_directional_shadow_atlas() { @@ -1137,6 +1219,7 @@ void RendererSceneRenderRD::directional_shadow_atlas_set_size(int p_size, bool p } directional_shadow.size = p_size; + directional_shadow.use_16_bits = p_16_bits; if (directional_shadow.depth.is_valid()) { RD::get_singleton()->free(directional_shadow.depth); @@ -2154,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); @@ -2218,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); @@ -2490,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; } @@ -2502,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; @@ -2548,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 @@ -2571,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); } } @@ -3120,18 +3211,19 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const light_data.shadow_enabled = true; - if (type == RS::LIGHT_SPOT) { - light_data.shadow_bias = (storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS) / 100.0); + float shadow_texel_size = light_instance_get_shadow_texel_size(li->self, p_shadow_atlas); + light_data.shadow_normal_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS) * shadow_texel_size * 10.0; - } else { //omni + if (type == RS::LIGHT_SPOT) { light_data.shadow_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS) / 100.0; - float shadow_texel_size = light_instance_get_shadow_texel_size(li->self, p_shadow_atlas); - light_data.shadow_normal_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS) * shadow_texel_size * 2.0; // applied in -1 .. 1 space + } else { //omni + light_data.shadow_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS); } light_data.transmittance_bias = storage->light_get_transmittance_bias(base); - Rect2 rect = light_instance_get_shadow_atlas_rect(li->self, p_shadow_atlas); + Vector2i omni_offset; + Rect2 rect = light_instance_get_shadow_atlas_rect(li->self, p_shadow_atlas, omni_offset); light_data.atlas_rect[0] = rect.position.x; light_data.atlas_rect[1] = rect.position.y; @@ -3142,7 +3234,6 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const light_data.shadow_volumetric_fog_fade = 1.0 / storage->light_get_shadow_volumetric_fog_fade(base); if (type == RS::LIGHT_OMNI) { - light_data.atlas_rect[3] *= 0.5; //one paraboloid on top of another Transform3D proj = (inverse_transform * light_transform).inverse(); RendererStorageRD::store_transform(proj, light_data.shadow_matrix); @@ -3154,6 +3245,8 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const light_data.soft_shadow_scale *= shadows_quality_radius_get(); // Only use quality radius for PCF } + light_data.direction[0] = omni_offset.x * float(rect.size.width); + light_data.direction[1] = omni_offset.y * float(rect.size.height); } else if (type == RS::LIGHT_SPOT) { Transform3D modelview = (inverse_transform * light_transform).inverse(); CameraMatrix bias; @@ -4066,7 +4159,7 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const CameraData render_state.voxel_gi_count = 0; - if (rb != nullptr) { + if (rb != nullptr && is_dynamic_gi_supported()) { if (rb->sdfgi) { rb->sdfgi->update_cascades(); rb->sdfgi->pre_process_gi(render_data.cam_transform, &render_data, this); @@ -4139,6 +4232,7 @@ void RendererSceneRenderRD::_render_shadow_pass(RID p_light, RID p_shadow_atlas, bool using_dual_paraboloid = false; bool using_dual_paraboloid_flip = false; + Vector2i dual_paraboloid_offset; RID render_fb; RID render_texture; float zfar; @@ -4232,6 +4326,9 @@ void RendererSceneRenderRD::_render_shadow_pass(RID p_light, RID p_shadow_atlas, zfar = storage->light_get_param(light_instance->light, RS::LIGHT_PARAM_RANGE); if (storage->light_get_type(light_instance->light) == RS::LIGHT_OMNI) { + bool wrap = (shadow + 1) % shadow_atlas->quadrants[quadrant].subdivision == 0; + dual_paraboloid_offset = wrap ? Vector2i(1 - shadow_atlas->quadrants[quadrant].subdivision, 1) : Vector2i(1, 0); + if (storage->light_omni_get_shadow_mode(light_instance->light) == RS::LIGHT_OMNI_SHADOW_CUBE) { ShadowCubemap *cubemap = _get_shadow_cubemap(shadow_size / 2); @@ -4251,12 +4348,16 @@ void RendererSceneRenderRD::_render_shadow_pass(RID p_light, RID p_shadow_atlas, } } else { + atlas_rect.position.x += 1; + atlas_rect.position.y += 1; + atlas_rect.size.x -= 2; + atlas_rect.size.y -= 2; + + atlas_rect.position += p_pass * atlas_rect.size * dual_paraboloid_offset; + light_projection = light_instance->shadow_transform[0].camera; light_transform = light_instance->shadow_transform[0].transform; - atlas_rect.size.height /= 2; - atlas_rect.position.y += p_pass * atlas_rect.size.height; - using_dual_paraboloid = true; using_dual_paraboloid_flip = p_pass == 1; render_fb = shadow_atlas->fb; @@ -4285,10 +4386,9 @@ void RendererSceneRenderRD::_render_shadow_pass(RID p_light, RID p_shadow_atlas, atlas_rect_norm.position.y /= float(atlas_size); atlas_rect_norm.size.x /= float(atlas_size); atlas_rect_norm.size.y /= float(atlas_size); - atlas_rect_norm.size.height /= 2; - storage->get_effects()->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, light_projection.get_z_near(), light_projection.get_z_far(), false); - atlas_rect_norm.position.y += atlas_rect_norm.size.height; - storage->get_effects()->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, light_projection.get_z_near(), light_projection.get_z_far(), true); + storage->get_effects()->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, atlas_rect.size, light_projection.get_z_near(), light_projection.get_z_far(), false); + atlas_rect_norm.position += Vector2(dual_paraboloid_offset) * atlas_rect_norm.size; + storage->get_effects()->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, atlas_rect.size, light_projection.get_z_near(), light_projection.get_z_far(), true); //restore transform so it can be properly used light_instance_set_shadow_transform(p_light, CameraMatrix(), light_instance->transform, zfar, 0, 0, 0); @@ -4390,6 +4490,12 @@ bool RendererSceneRenderRD::free(RID p_rid) { uint32_t s = key & ShadowAtlas::SHADOW_INDEX_MASK; shadow_atlas->quadrants[q].shadows.write[s].owner = RID(); + + if (key & ShadowAtlas::OMNI_LIGHT_FLAG) { + // Omni lights use two atlas spots, make sure to clear the other as well + shadow_atlas->quadrants[q].shadows.write[s + 1].owner = RID(); + } + shadow_atlas->shadow_owners.erase(p_rid); } @@ -4587,10 +4693,12 @@ uint32_t RendererSceneRenderRD::get_max_elements() const { } RendererSceneRenderRD::RendererSceneRenderRD(RendererStorageRD *p_storage) { - max_cluster_elements = get_max_elements(); - storage = p_storage; singleton = this; +} + +void RendererSceneRenderRD::init() { + max_cluster_elements = get_max_elements(); directional_shadow.size = GLOBAL_GET("rendering/shadows/directional_shadow/size"); directional_shadow.use_16_bits = GLOBAL_GET("rendering/shadows/directional_shadow/16_bits"); diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h index 5306df36f0..eb61af517a 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h @@ -255,7 +255,8 @@ private: struct ShadowAtlas { enum { QUADRANT_SHIFT = 27, - SHADOW_INDEX_MASK = (1 << QUADRANT_SHIFT) - 1, + OMNI_LIGHT_FLAG = 1 << 26, + SHADOW_INDEX_MASK = OMNI_LIGHT_FLAG - 1, SHADOW_INVALID = 0xFFFFFFFF }; @@ -299,7 +300,9 @@ private: void _update_shadow_atlas(ShadowAtlas *shadow_atlas); + void _shadow_atlas_invalidate_shadow(RendererSceneRenderRD::ShadowAtlas::Quadrant::Shadow *p_shadow, RID p_atlas, RendererSceneRenderRD::ShadowAtlas *p_shadow_atlas, uint32_t p_quadrant, uint32_t p_shadow_idx); bool _shadow_atlas_find_shadow(ShadowAtlas *shadow_atlas, int *p_in_quadrants, int p_quadrant_count, int p_current_subdiv, uint64_t p_tick, int &r_quadrant, int &r_shadow); + bool _shadow_atlas_find_omni_shadows(ShadowAtlas *shadow_atlas, int *p_in_quadrants, int p_quadrant_count, int p_current_subdiv, uint64_t p_tick, int &r_quadrant, int &r_shadow); RS::ShadowQuality shadows_quality = RS::SHADOW_QUALITY_MAX; //So it always updates when first set RS::ShadowQuality directional_shadow_quality = RS::SHADOW_QUALITY_MAX; @@ -379,10 +382,6 @@ private: uint32_t cull_mask = 0; uint32_t light_directional_index = 0; - uint32_t current_shadow_atlas_key = 0; - - Vector2 dp; - Rect2 directional_rect; Set<RID> shadow_atlases; //shadow atlases where this light is registered @@ -575,7 +574,7 @@ private: struct LightData { float position[3]; float inv_radius; - float direction[3]; + float direction[3]; // in omni, x and y are used for dual paraboloid offset float size; float color[3]; @@ -964,7 +963,7 @@ public: return li->transform; } - _FORCE_INLINE_ Rect2 light_instance_get_shadow_atlas_rect(RID p_light_instance, RID p_shadow_atlas) { + _FORCE_INLINE_ Rect2 light_instance_get_shadow_atlas_rect(RID p_light_instance, RID p_shadow_atlas, Vector2i &r_omni_offset) { ShadowAtlas *shadow_atlas = shadow_atlas_owner.getornull(p_shadow_atlas); LightInstance *li = light_instance_owner.getornull(p_light_instance); uint32_t key = shadow_atlas->shadow_owners[li->self]; @@ -984,6 +983,16 @@ public: x += (shadow % shadow_atlas->quadrants[quadrant].subdivision) * shadow_size; y += (shadow / shadow_atlas->quadrants[quadrant].subdivision) * shadow_size; + if (key & ShadowAtlas::OMNI_LIGHT_FLAG) { + if (((shadow + 1) % shadow_atlas->quadrants[quadrant].subdivision) == 0) { + r_omni_offset.x = 1 - int(shadow_atlas->quadrants[quadrant].subdivision); + r_omni_offset.y = 1; + } else { + r_omni_offset.x = 1; + r_omni_offset.y = 0; + } + } + uint32_t width = shadow_size; uint32_t height = shadow_size; @@ -1181,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; @@ -1290,6 +1300,8 @@ public: virtual bool is_volumetric_supported() const; virtual uint32_t get_max_elements() const; + void init(); + RendererSceneRenderRD(RendererStorageRD *p_storage); ~RendererSceneRenderRD(); }; diff --git a/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp index 9e85608f1e..da84f615b2 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); @@ -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 7d438345f1..14a5a01054 100644 --- a/servers/rendering/renderer_rd/renderer_storage_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_storage_rd.cpp @@ -9310,15 +9310,6 @@ RendererStorageRD::RendererStorageRD() { } } - { - Vector<String> sdf_versions; - sdf_versions.push_back(""); //one only - voxel_gi_sdf_shader.initialize(sdf_versions); - voxel_gi_sdf_shader_version = voxel_gi_sdf_shader.version_create(); - voxel_gi_sdf_shader_version_shader = voxel_gi_sdf_shader.version_get_shader(voxel_gi_sdf_shader_version, 0); - voxel_gi_sdf_shader_pipeline = RD::get_singleton()->compute_pipeline_create(voxel_gi_sdf_shader_version_shader); - } - using_lightmap_array = true; // high end if (using_lightmap_array) { uint32_t textures_per_stage = RD::get_singleton()->limit_get(RD::LIMIT_MAX_TEXTURES_PER_SHADER_STAGE); @@ -9407,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() { @@ -9538,7 +9531,6 @@ RendererStorageRD::~RendererStorageRD() { RD::get_singleton()->free(mesh_default_rd_buffers[i]); } - voxel_gi_sdf_shader.version_free(voxel_gi_sdf_shader_version); particles_shader.copy_shader.version_free(particles_shader.copy_shader_version); rt_sdf.shader.version_free(rt_sdf.shader_version); diff --git a/servers/rendering/renderer_rd/renderer_storage_rd.h b/servers/rendering/renderer_rd/renderer_storage_rd.h index c048be5dc8..02395a884f 100644 --- a/servers/rendering/renderer_rd/renderer_storage_rd.h +++ b/servers/rendering/renderer_rd/renderer_storage_rd.h @@ -668,14 +668,14 @@ private: }; uint32_t emitting; - double system_phase; - double prev_system_phase; + float system_phase; + float prev_system_phase; uint32_t cycle; real_t explosiveness; real_t randomness; - double time; - double delta; + float time; + float delta; uint32_t frame; uint32_t pad0; @@ -812,7 +812,7 @@ private: struct ParticlesShader { struct PushConstant { - double lifetime; + float lifetime; uint32_t clear; uint32_t total_particles; uint32_t trail_size; @@ -1100,11 +1100,6 @@ private: Dependency dependency; }; - VoxelGiSdfShaderRD voxel_gi_sdf_shader; - RID voxel_gi_sdf_shader_version; - RID voxel_gi_sdf_shader_version_shader; - RID voxel_gi_sdf_shader_pipeline; - mutable RID_Owner<VoxelGI, true> voxel_gi_owner; /* REFLECTION PROBE */ diff --git a/servers/rendering/renderer_rd/shader_compiler_rd.cpp b/servers/rendering/renderer_rd/shader_compiler_rd.cpp index 8aa03b6426..b95d4b642c 100644 --- a/servers/rendering/renderer_rd/shader_compiler_rd.cpp +++ b/servers/rendering/renderer_rd/shader_compiler_rd.cpp @@ -213,7 +213,7 @@ static String _interpstr(SL::DataInterpolation p_interp) { return ""; } -static String _prestr(SL::DataPrecision p_pres) { +static String _prestr(SL::DataPrecision p_pres, bool p_force_highp = false) { switch (p_pres) { case SL::PRECISION_LOWP: return "lowp "; @@ -222,7 +222,7 @@ static String _prestr(SL::DataPrecision p_pres) { case SL::PRECISION_HIGHP: return "highp "; case SL::PRECISION_DEFAULT: - return ""; + return p_force_highp ? "highp " : ""; } return ""; } @@ -617,7 +617,7 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge //this is an integer to index the global table ucode += _typestr(ShaderLanguage::TYPE_UINT); } else { - ucode += _prestr(uniform.precision); + ucode += _prestr(uniform.precision, ShaderLanguage::is_float_type(uniform.type)); ucode += _typestr(uniform.type); } @@ -742,7 +742,7 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge String vcode; String interp_mode = _interpstr(varying.interpolation); - vcode += _prestr(varying.precision); + vcode += _prestr(varying.precision, ShaderLanguage::is_float_type(varying.type)); vcode += _typestr(varying.type); vcode += " " + _mkid(varying_name); if (varying.array_size > 0) { @@ -777,7 +777,7 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge const SL::ShaderNode::Constant &cnode = pnode->vconstants[i]; String gcode; gcode += "const "; - gcode += _prestr(cnode.precision); + gcode += _prestr(cnode.precision, ShaderLanguage::is_float_type(cnode.type)); if (cnode.type == SL::TYPE_STRUCT) { gcode += _mkid(cnode.type_str); } else { @@ -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/shader_rd.cpp b/servers/rendering/renderer_rd/shader_rd.cpp index 5bb12fc168..82efa1318c 100644 --- a/servers/rendering/renderer_rd/shader_rd.cpp +++ b/servers/rendering/renderer_rd/shader_rd.cpp @@ -279,7 +279,7 @@ void ShaderRD::_compile_variant(uint32_t p_variant, Version *p_version) { return; } - Vector<uint8_t> shader_data = RD::get_singleton()->shader_compile_binary_from_spirv(stages); + Vector<uint8_t> shader_data = RD::get_singleton()->shader_compile_binary_from_spirv(stages, name + ":" + itos(p_variant)); ERR_FAIL_COND(shader_data.size() == 0); 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/cube_to_dp.glsl b/servers/rendering/renderer_rd/shaders/cube_to_dp.glsl index dfbce29119..69b895ed29 100644 --- a/servers/rendering/renderer_rd/shaders/cube_to_dp.glsl +++ b/servers/rendering/renderer_rd/shaders/cube_to_dp.glsl @@ -7,8 +7,7 @@ layout(push_constant, binding = 1, std430) uniform Params { float z_far; float z_near; - bool z_flip; - uint pad; + vec2 texel_size; vec4 screen_rect; } params; @@ -35,22 +34,23 @@ layout(set = 0, binding = 0) uniform samplerCube source_cube; layout(push_constant, binding = 1, std430) uniform Params { float z_far; float z_near; - bool z_flip; - uint pad; + vec2 texel_size; vec4 screen_rect; } params; void main() { vec2 uv = uv_interp; + vec2 texel_size = abs(params.texel_size); - vec3 normal = vec3(uv * 2.0 - 1.0, 0.0); + uv = clamp(uv * (1.0 + 2.0 * texel_size) - texel_size, vec2(0.0), vec2(1.0)); - normal.z = 0.5 - 0.5 * ((normal.x * normal.x) + (normal.y * normal.y)); + vec3 normal = vec3(uv * 2.0 - 1.0, 0.0); + normal.z = 0.5 * (1.0 - dot(normal.xy, normal.xy)); // z = 1/2 - 1/2 * (x^2 + y^2) normal = normalize(normal); normal.y = -normal.y; //needs to be flipped to match projection matrix - if (!params.z_flip) { + if (params.texel_size.x >= 0.0) { // Sign is used to encode Z flip normal.z = -normal.z; } 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/decal_data_inc.glsl b/servers/rendering/renderer_rd/shaders/decal_data_inc.glsl index ccaad13311..158096d3c7 100644 --- a/servers/rendering/renderer_rd/shaders/decal_data_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/decal_data_inc.glsl @@ -1,18 +1,18 @@ struct DecalData { - mat4 xform; //to decal transform - vec3 inv_extents; - float albedo_mix; - vec4 albedo_rect; - vec4 normal_rect; - vec4 orm_rect; - vec4 emission_rect; - vec4 modulate; - float emission_energy; + highp mat4 xform; //to decal transform + highp vec3 inv_extents; + mediump float albedo_mix; + highp vec4 albedo_rect; + highp vec4 normal_rect; + highp vec4 orm_rect; + highp vec4 emission_rect; + highp vec4 modulate; + mediump float emission_energy; uint mask; - float upper_fade; - float lower_fade; - mat3x4 normal_xform; - vec3 normal; - float normal_fade; + mediump float upper_fade; + mediump float lower_fade; + mediump mat3x4 normal_xform; + mediump vec3 normal; + mediump float normal_fade; }; 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/light_data_inc.glsl b/servers/rendering/renderer_rd/shaders/light_data_inc.glsl index 9155216d7e..fdc7729338 100644 --- a/servers/rendering/renderer_rd/shaders/light_data_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/light_data_inc.glsl @@ -4,28 +4,28 @@ struct LightData { //this structure needs to be as packed as possible highp vec3 position; - float inv_radius; + highp float inv_radius; - vec3 direction; - float size; + mediump vec3 direction; + highp float size; - vec3 color; - float attenuation; + mediump vec3 color; + mediump float attenuation; - float cone_attenuation; - float cone_angle; - float specular_amount; + mediump float cone_attenuation; + mediump float cone_angle; + mediump float specular_amount; bool shadow_enabled; highp vec4 atlas_rect; // rect in the shadow atlas highp mat4 shadow_matrix; - float shadow_bias; - float shadow_normal_bias; - float transmittance_bias; - float soft_shadow_size; // for spot, it's the size in uv coordinates of the light, for omni it's the span angle - float soft_shadow_scale; // scales the shadow kernel for blurrier shadows + highp float shadow_bias; + highp float shadow_normal_bias; + highp float transmittance_bias; + highp float soft_shadow_size; // for spot, it's the size in uv coordinates of the light, for omni it's the span angle + highp float soft_shadow_scale; // scales the shadow kernel for blurrier shadows uint mask; - float shadow_volumetric_fog_fade; + mediump float shadow_volumetric_fog_fade; uint bake_mode; highp vec4 projector_rect; //projector rect in srgb decal atlas }; @@ -35,53 +35,53 @@ struct LightData { //this structure needs to be as packed as possible #define REFLECTION_AMBIENT_COLOR 2 struct ReflectionData { - vec3 box_extents; - float index; - vec3 box_offset; + highp vec3 box_extents; + mediump float index; + highp vec3 box_offset; uint mask; - vec3 ambient; // ambient color - float intensity; + mediump vec3 ambient; // ambient color + mediump float intensity; bool exterior; bool box_project; uint ambient_mode; uint pad; //0-8 is intensity,8-9 is ambient, mode - mat4 local_matrix; // up to here for spot and omni, rest is for directional + highp mat4 local_matrix; // up to here for spot and omni, rest is for directional // notes: for ambientblend, use distance to edge to blend between already existing global environment }; struct DirectionalLightData { - vec3 direction; - float energy; - vec3 color; - float size; - float specular; + mediump vec3 direction; + mediump float energy; + mediump vec3 color; + mediump float size; + mediump float specular; uint mask; - float softshadow_angle; - float soft_shadow_scale; + highp float softshadow_angle; + highp float soft_shadow_scale; bool blend_splits; bool shadow_enabled; - float fade_from; - float fade_to; + highp float fade_from; + highp float fade_to; uvec2 pad; uint bake_mode; - float shadow_volumetric_fog_fade; - vec4 shadow_bias; - vec4 shadow_normal_bias; - vec4 shadow_transmittance_bias; + mediump float shadow_volumetric_fog_fade; + highp vec4 shadow_bias; + highp vec4 shadow_normal_bias; + highp vec4 shadow_transmittance_bias; highp vec4 shadow_z_range; highp vec4 shadow_range_begin; - vec4 shadow_split_offsets; + highp vec4 shadow_split_offsets; highp mat4 shadow_matrix1; highp mat4 shadow_matrix2; highp mat4 shadow_matrix3; highp mat4 shadow_matrix4; - vec4 shadow_color1; - vec4 shadow_color2; - vec4 shadow_color3; - vec4 shadow_color4; - vec2 uv_scale1; - vec2 uv_scale2; - vec2 uv_scale3; - vec2 uv_scale4; + mediump vec4 shadow_color1; + mediump vec4 shadow_color2; + mediump vec4 shadow_color3; + mediump vec4 shadow_color4; + highp vec2 uv_scale1; + highp vec2 uv_scale2; + highp vec2 uv_scale3; + highp vec2 uv_scale4; }; 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 4f140dd10d..edbe1031b7 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)); @@ -1601,7 +1604,7 @@ void main() { continue; // Statically baked light and object uses lightmap, skip } - float shadow = light_process_omni_shadow(light_index, vertex, view); + float shadow = light_process_omni_shadow(light_index, vertex, normal); shadow = blur_shadow(shadow); @@ -1677,7 +1680,7 @@ void main() { continue; // Statically baked light and object uses lightmap, skip } - float shadow = light_process_spot_shadow(light_index, vertex, view); + float shadow = light_process_spot_shadow(light_index, vertex, normal); shadow = blur_shadow(shadow); 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 4a41c66ef3..f3db4abe3b 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl @@ -279,7 +279,7 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, float atte } #ifdef USE_SHADOW_TO_OPACITY - alpha = min(alpha, clamp(1.0 - attenuation), 0.0, 1.0)); + alpha = min(alpha, clamp(1.0 - attenuation, 0.0, 1.0)); #endif #endif //defined(LIGHT_CODE_USED) @@ -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))); @@ -320,7 +320,7 @@ float sample_directional_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, ve return avg * (1.0 / float(sc_directional_soft_shadow_samples)); } -float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec4 coord) { +float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec3 coord) { vec2 pos = coord.xy; float depth = coord.z; @@ -346,6 +346,49 @@ float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec4 coord) { return avg * (1.0 / float(sc_soft_shadow_samples)); } +float sample_omni_pcf_shadow(texture2D shadow, float blur_scale, vec2 coord, vec4 uv_rect, vec2 flip_offset, float depth) { + //if only one sample is taken, take it from the center + if (sc_soft_shadow_samples == 1) { + vec2 pos = coord * 0.5 + 0.5; + pos = uv_rect.xy + pos * uv_rect.zw; + return textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos, depth, 1.0)); + } + + mat2 disk_rotation; + { + float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI; + float sr = sin(r); + float cr = cos(r); + disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr)); + } + + float avg = 0.0; + vec2 offset_scale = blur_scale * 2.0 * scene_data.shadow_atlas_pixel_size / uv_rect.zw; + + for (uint i = 0; i < sc_soft_shadow_samples; i++) { + vec2 offset = offset_scale * (disk_rotation * scene_data.soft_shadow_kernel[i].xy); + vec2 sample_coord = coord + offset; + + float sample_coord_length_sqaured = dot(sample_coord, sample_coord); + bool do_flip = sample_coord_length_sqaured > 1.0; + + if (do_flip) { + float len = sqrt(sample_coord_length_sqaured); + sample_coord = sample_coord * (2.0 / len - 1.0); + } + + sample_coord = sample_coord * 0.5 + 0.5; + sample_coord = uv_rect.xy + sample_coord * uv_rect.zw; + + if (do_flip) { + sample_coord += flip_offset; + } + avg += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(sample_coord, depth, 1.0)); + } + + return avg * (1.0 / float(sc_soft_shadow_samples)); +} + float sample_directional_soft_shadow(texture2D shadow, vec3 pssm_coord, vec2 tex_scale) { //find blocker float blocker_count = 0.0; @@ -403,15 +446,21 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) { #ifndef USE_NO_SHADOWS if (omni_lights.data[idx].shadow_enabled) { // there is a shadowmap + vec2 texel_size = scene_data.shadow_atlas_pixel_size; + vec4 base_uv_rect = omni_lights.data[idx].atlas_rect; + base_uv_rect.xy += texel_size; + base_uv_rect.zw -= texel_size * 2.0; - vec3 light_rel_vec = omni_lights.data[idx].position - vertex; - float light_length = length(light_rel_vec); + // Omni lights use direction.xy to store to store the offset between the two paraboloid regions + vec2 flip_offset = omni_lights.data[idx].direction.xy; - vec4 v = vec4(vertex, 1.0); + vec3 local_vert = (omni_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz; - vec4 splane = (omni_lights.data[idx].shadow_matrix * v); + float shadow_len = length(local_vert); //need to remember shadow len from here + vec3 shadow_dir = normalize(local_vert); - float shadow_len = length(splane.xyz); //need to remember shadow len from here + vec3 local_normal = normalize(mat3(omni_lights.data[idx].shadow_matrix) * normal); + vec3 normal_bias = local_normal * omni_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(local_normal, shadow_dir))); float shadow; @@ -431,10 +480,10 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) { disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr)); } - vec3 normal = normalize(splane.xyz); - 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)); - vec3 bitangent = normalize(cross(tangent, normal)); + vec3 basis_normal = shadow_dir; + vec3 v0 = abs(basis_normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0); + vec3 tangent = normalize(cross(v0, basis_normal)); + vec3 bitangent = normalize(cross(tangent, basis_normal)); float z_norm = shadow_len * omni_lights.data[idx].inv_radius; tangent *= omni_lights.data[idx].soft_shadow_size * omni_lights.data[idx].soft_shadow_scale; @@ -443,18 +492,17 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) { for (uint i = 0; i < sc_penumbra_shadow_samples; i++) { vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy; - vec3 pos = splane.xyz + tangent * disk.x + bitangent * disk.y; + vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y; pos = normalize(pos); - vec4 uv_rect = omni_lights.data[idx].atlas_rect; + + vec4 uv_rect = base_uv_rect; if (pos.z >= 0.0) { - pos.z += 1.0; - uv_rect.y += uv_rect.w; - } else { - pos.z = 1.0 - pos.z; + uv_rect.xy += flip_offset; } + pos.z = 1.0 + abs(pos.z); pos.xy /= pos.z; pos.xy = pos.xy * 0.5 + 0.5; @@ -479,18 +527,18 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) { shadow = 0.0; for (uint i = 0; i < sc_penumbra_shadow_samples; i++) { vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy; - vec3 pos = splane.xyz + tangent * disk.x + bitangent * disk.y; + vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y; pos = normalize(pos); - vec4 uv_rect = omni_lights.data[idx].atlas_rect; + pos = normalize(pos + normal_bias); + + vec4 uv_rect = base_uv_rect; if (pos.z >= 0.0) { - pos.z += 1.0; - uv_rect.y += uv_rect.w; - } else { - pos.z = 1.0 - pos.z; + uv_rect.xy += flip_offset; } + pos.z = 1.0 + abs(pos.z); pos.xy /= pos.z; pos.xy = pos.xy * 0.5 + 0.5; @@ -505,26 +553,19 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) { shadow = 1.0; } } else { - splane.xyz = normalize(splane.xyz); - vec4 clamp_rect = omni_lights.data[idx].atlas_rect; - - if (splane.z >= 0.0) { - splane.z += 1.0; + vec4 uv_rect = base_uv_rect; - clamp_rect.y += clamp_rect.w; - - } else { - splane.z = 1.0 - splane.z; + vec3 shadow_sample = normalize(shadow_dir + normal_bias); + if (shadow_sample.z >= 0.0) { + uv_rect.xy += flip_offset; + flip_offset *= -1.0; } - splane.xy /= splane.z; - - splane.xy = splane.xy * 0.5 + 0.5; - splane.z = shadow_len * omni_lights.data[idx].inv_radius; - splane.z -= omni_lights.data[idx].shadow_bias; - splane.xy = clamp_rect.xy + splane.xy * clamp_rect.zw; - splane.w = 1.0; //needed? i think it should be 1 already - shadow = sample_pcf_shadow(shadow_atlas, omni_lights.data[idx].soft_shadow_scale * scene_data.shadow_atlas_pixel_size, splane); + shadow_sample.z = 1.0 + abs(shadow_sample.z); + vec2 pos = shadow_sample.xy / shadow_sample.z; + float depth = shadow_len - omni_lights.data[idx].shadow_bias; + depth *= omni_lights.data[idx].inv_radius; + shadow = sample_omni_pcf_shadow(shadow_atlas, omni_lights.data[idx].soft_shadow_scale / shadow_sample.z, pos, uv_rect, flip_offset, depth); } return shadow; @@ -608,13 +649,11 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v vec4 atlas_rect = omni_lights.data[idx].projector_rect; if (local_v.z >= 0.0) { - local_v.z += 1.0; atlas_rect.y += atlas_rect.w; - - } else { - local_v.z = 1.0 - local_v.z; } + local_v.z = 1.0 + abs(local_v.z); + local_v.xy /= local_v.z; local_v.xy = local_v.xy * 0.5 + 0.5; vec2 proj_uv = local_v.xy * atlas_rect.zw; @@ -694,15 +733,18 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) { vec3 light_rel_vec = spot_lights.data[idx].position - vertex; float light_length = length(light_rel_vec); vec3 spot_dir = spot_lights.data[idx].direction; - //there is a shadowmap - vec4 v = vec4(vertex, 1.0); - float shadow; + vec3 shadow_dir = light_rel_vec / light_length; + vec3 normal_bias = normal * light_length * spot_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(normal, shadow_dir))); + + //there is a shadowmap + vec4 v = vec4(vertex + normal_bias, 1.0); vec4 splane = (spot_lights.data[idx].shadow_matrix * v); + splane.z -= spot_lights.data[idx].shadow_bias / (light_length * spot_lights.data[idx].inv_radius); splane /= splane.w; - splane.z -= spot_lights.data[idx].shadow_bias; + float shadow; if (sc_use_light_soft_shadows && spot_lights.data[idx].soft_shadow_size > 0.0) { //soft shadow @@ -753,11 +795,9 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) { //no blockers found, so no shadow shadow = 1.0; } - } else { //hard shadow - vec4 shadow_uv = vec4(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z, 1.0); - + vec3 shadow_uv = vec3(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z); shadow = sample_pcf_shadow(shadow_atlas, spot_lights.data[idx].soft_shadow_scale * scene_data.shadow_atlas_pixel_size, shadow_uv); } @@ -929,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 663100a0b3..518b0a6c7f 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl @@ -92,7 +92,7 @@ layout(set = MATERIAL_UNIFORM_SET, binding = 0, std140) uniform MaterialUniforms #ifdef MODE_DUAL_PARABOLOID -layout(location = 8) out float dp_clip; +layout(location = 8) out highp float dp_clip; #endif @@ -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); } @@ -370,11 +370,6 @@ void main() { #VERSION_DEFINES -//use medium precision for floats on mobile. - -precision mediump float; -precision highp int; - /* Specialization Constants */ #if !defined(MODE_RENDER_DEPTH) @@ -406,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" @@ -498,7 +495,7 @@ layout(location = 0) out vec4 diffuse_buffer; //diffuse (rgb) and roughness layout(location = 1) out vec4 specular_buffer; //specular and SSS (subsurface scatter) #else -layout(location = 0) out vec4 frag_color; +layout(location = 0) out mediump vec4 frag_color; #endif // MODE_MULTIPLE_RENDER_TARGETS #endif // RENDER DEPTH @@ -852,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)); @@ -1392,7 +1389,7 @@ void main() { break; } - float shadow = light_process_omni_shadow(light_index, vertex, view); + float shadow = light_process_omni_shadow(light_index, vertex, normal); shadow = blur_shadow(shadow); @@ -1440,7 +1437,7 @@ void main() { break; } - float shadow = light_process_spot_shadow(light_index, vertex, view); + float shadow = light_process_spot_shadow(light_index, vertex, normal); shadow = blur_shadow(shadow); @@ -1556,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/scene_forward_mobile_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_mobile_inc.glsl index f1e554d01c..dd8879acb4 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_mobile_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_mobile_inc.glsl @@ -93,7 +93,7 @@ directional_lights; #define LIGHTMAP_FLAG_USE_SPECULAR_DIRECTION 2 struct Lightmap { - mat3 normal_xform; + mediump mat3 normal_xform; }; layout(set = 0, binding = 9, std140) restrict readonly buffer Lightmaps { @@ -102,7 +102,7 @@ layout(set = 0, binding = 9, std140) restrict readonly buffer Lightmaps { lightmaps; struct LightmapCapture { - vec4 sh[9]; + mediump vec4 sh[9]; }; layout(set = 0, binding = 10, std140) restrict readonly buffer LightmapCaptures { @@ -110,8 +110,8 @@ layout(set = 0, binding = 10, std140) restrict readonly buffer LightmapCaptures } lightmap_captures; -layout(set = 0, binding = 11) uniform texture2D decal_atlas; -layout(set = 0, binding = 12) uniform texture2D decal_atlas_srgb; +layout(set = 0, binding = 11) uniform mediump texture2D decal_atlas; +layout(set = 0, binding = 12) uniform mediump texture2D decal_atlas_srgb; layout(set = 0, binding = 13, std430) restrict readonly buffer Decals { DecalData data[]; @@ -119,7 +119,7 @@ layout(set = 0, binding = 13, std430) restrict readonly buffer Decals { decals; layout(set = 0, binding = 14, std430) restrict readonly buffer GlobalVariableData { - vec4 data[]; + highp vec4 data[]; } global_variables; @@ -135,56 +135,56 @@ layout(set = 1, binding = 0, std140) uniform SceneData { highp mat4 projection_matrix_view[MAX_VIEWS]; highp mat4 inv_projection_matrix_view[MAX_VIEWS]; - vec2 viewport_size; - vec2 screen_pixel_size; + highp vec2 viewport_size; + highp vec2 screen_pixel_size; // Use vec4s because std140 doesn't play nice with vec2s, z and w are wasted. - vec4 directional_penumbra_shadow_kernel[32]; - vec4 directional_soft_shadow_kernel[32]; - vec4 penumbra_shadow_kernel[32]; - vec4 soft_shadow_kernel[32]; + highp vec4 directional_penumbra_shadow_kernel[32]; + highp vec4 directional_soft_shadow_kernel[32]; + highp vec4 penumbra_shadow_kernel[32]; + highp vec4 soft_shadow_kernel[32]; - vec4 ambient_light_color_energy; + mediump vec4 ambient_light_color_energy; - float ambient_color_sky_mix; + mediump float ambient_color_sky_mix; bool use_ambient_light; bool use_ambient_cubemap; bool use_reflection_cubemap; - mat3 radiance_inverse_xform; + mediump mat3 radiance_inverse_xform; - vec2 shadow_atlas_pixel_size; - vec2 directional_shadow_pixel_size; + highp vec2 shadow_atlas_pixel_size; + highp vec2 directional_shadow_pixel_size; uint directional_light_count; - float dual_paraboloid_side; - float z_far; - float z_near; + mediump float dual_paraboloid_side; + highp float z_far; + highp float z_near; bool ssao_enabled; - float ssao_light_affect; - float ssao_ao_affect; + mediump float ssao_light_affect; + mediump float ssao_ao_affect; bool roughness_limiter_enabled; - float roughness_limiter_amount; - float roughness_limiter_limit; + mediump float roughness_limiter_amount; + mediump float roughness_limiter_limit; uvec2 roughness_limiter_pad; - vec4 ao_color; + mediump vec4 ao_color; bool fog_enabled; - float fog_density; - float fog_height; - float fog_height_density; + highp float fog_density; + highp float fog_height; + highp float fog_height_density; - vec3 fog_light_color; - float fog_sun_scatter; + mediump vec3 fog_light_color; + mediump float fog_sun_scatter; - float fog_aerial_perspective; + mediump float fog_aerial_perspective; bool material_uv2_mode; - float time; - float reflection_multiplier; // one normally, zero when rendering reflections + highp float time; + mediump float reflection_multiplier; // one normally, zero when rendering reflections bool pancake_shadows; uint pad1; @@ -195,30 +195,30 @@ scene_data; #ifdef USE_RADIANCE_CUBEMAP_ARRAY -layout(set = 1, binding = 2) uniform textureCubeArray radiance_cubemap; +layout(set = 1, binding = 2) uniform mediump textureCubeArray radiance_cubemap; #else -layout(set = 1, binding = 2) uniform textureCube radiance_cubemap; +layout(set = 1, binding = 2) uniform mediump textureCube radiance_cubemap; #endif -layout(set = 1, binding = 3) uniform textureCubeArray reflection_atlas; +layout(set = 1, binding = 3) uniform mediump textureCubeArray reflection_atlas; -layout(set = 1, binding = 4) uniform texture2D shadow_atlas; +layout(set = 1, binding = 4) uniform highp texture2D shadow_atlas; -layout(set = 1, binding = 5) uniform texture2D directional_shadow_atlas; +layout(set = 1, binding = 5) uniform highp texture2D directional_shadow_atlas; // this needs to change to providing just the lightmap we're using.. layout(set = 1, binding = 6) uniform texture2DArray lightmap_textures[MAX_LIGHTMAP_TEXTURES]; -layout(set = 1, binding = 9) uniform texture2D depth_buffer; -layout(set = 1, binding = 10) uniform texture2D color_buffer; +layout(set = 1, binding = 9) uniform highp texture2D depth_buffer; +layout(set = 1, binding = 10) uniform mediump texture2D color_buffer; /* Set 2 Skeleton & Instancing (can change per item) */ layout(set = 2, binding = 0, std430) restrict readonly buffer Transforms { - vec4 data[]; + highp vec4 data[]; } transforms; 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..4411587116 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; @@ -298,15 +298,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 +333,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 +349,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 +364,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 +377,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 +386,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); } @@ -411,7 +411,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_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index 4a4976718c..cd8014632d 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -2180,8 +2180,6 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons p_scenario->indexers[Scenario::INDEXER_GEOMETRY].convex_query(planes.ptr(), planes.size(), points.ptr(), points.size(), cull_convex); - Plane near_plane(light_transform.origin, light_transform.basis.get_axis(2) * z); - RendererSceneRender::RenderShadowData &shadow_data = render_shadow_data[max_shadows_used++]; for (int j = 0; j < (int)instance_shadow_cull_result.size(); j++) { @@ -2215,7 +2213,7 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons real_t radius = RSG::storage->light_get_param(p_instance->base, RS::LIGHT_PARAM_RANGE); CameraMatrix cm; - cm.set_perspective(90, 1, 0.01, radius); + cm.set_perspective(90, 1, radius * 0.005f, radius); for (int i = 0; i < 6; i++) { RENDER_TIMESTAMP("Culling Shadow Cube side" + itos(i)); @@ -2301,7 +2299,7 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons real_t angle = RSG::storage->light_get_param(p_instance->base, RS::LIGHT_PARAM_SPOT_ANGLE); CameraMatrix cm; - cm.set_perspective(angle * 2.0, 1.0, 0.01, radius); + cm.set_perspective(angle * 2.0, 1.0, 0.005f * radius, radius); Vector<Plane> planes = cm.get_projection_planes(light_transform); @@ -2794,12 +2792,9 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c //rasterizer->set_camera(p_camera_data->main_transform, p_camera_data.main_projection, p_camera_data.is_ortogonal); - Vector<Plane> planes = p_camera_data->main_projection.get_projection_planes(p_camera_data->main_transform); - - Plane near_plane(p_camera_data->main_transform.origin, -p_camera_data->main_transform.basis.get_axis(2).normalized()); - /* STEP 2 - CULL */ + Vector<Plane> planes = p_camera_data->main_projection.get_projection_planes(p_camera_data->main_transform); cull.frustum = Frustum(planes); Vector<RID> directional_lights; diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index 15ce1dbe63..3ede9fed2d 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,7 +619,7 @@ 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 @@ -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 bb9ffffe8a..b302c6b793 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -74,8 +74,8 @@ String RenderingDevice::shader_get_spirv_cache_key() const { return String(); } -RID RenderingDevice::shader_create_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv) { - Vector<uint8_t> bytecode = shader_compile_binary_from_spirv(p_spirv); +RID RenderingDevice::shader_create_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv, const String &p_shader_name) { + Vector<uint8_t> bytecode = shader_compile_binary_from_spirv(p_spirv, p_shader_name); ERR_FAIL_COND_V(bytecode.size() == 0, RID()); return shader_create_from_bytecode(bytecode); } @@ -192,7 +192,7 @@ Ref<RDShaderSPIRV> RenderingDevice::_shader_compile_spirv_from_source(const Ref< return bytecode; } -Vector<uint8_t> RenderingDevice::_shader_compile_binary_from_spirv(const Ref<RDShaderSPIRV> &p_spirv) { +Vector<uint8_t> RenderingDevice::_shader_compile_binary_from_spirv(const Ref<RDShaderSPIRV> &p_spirv, const String &p_shader_name) { ERR_FAIL_COND_V(p_spirv.is_null(), Vector<uint8_t>()); Vector<ShaderStageSPIRVData> stage_data; @@ -209,10 +209,10 @@ Vector<uint8_t> RenderingDevice::_shader_compile_binary_from_spirv(const Ref<RDS stage_data.push_back(sd); } - return shader_compile_binary_from_spirv(stage_data); + return shader_compile_binary_from_spirv(stage_data, p_shader_name); } -RID RenderingDevice::_shader_create_from_spirv(const Ref<RDShaderSPIRV> &p_spirv) { +RID RenderingDevice::_shader_create_from_spirv(const Ref<RDShaderSPIRV> &p_spirv, const String &p_shader_name) { ERR_FAIL_COND_V(p_spirv.is_null(), RID()); Vector<ShaderStageSPIRVData> stage_data; @@ -392,8 +392,8 @@ void RenderingDevice::_bind_methods() { ClassDB::bind_method(D_METHOD("index_array_create", "index_buffer", "index_offset", "index_count"), &RenderingDevice::index_array_create); ClassDB::bind_method(D_METHOD("shader_compile_spirv_from_source", "shader_source", "allow_cache"), &RenderingDevice::_shader_compile_spirv_from_source, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("shader_compile_binary_from_spirv", "spirv_data"), &RenderingDevice::_shader_compile_binary_from_spirv); - ClassDB::bind_method(D_METHOD("shader_create_from_spirv", "spirv_data"), &RenderingDevice::_shader_compile_binary_from_spirv); + ClassDB::bind_method(D_METHOD("shader_compile_binary_from_spirv", "spirv_data", "name"), &RenderingDevice::_shader_compile_binary_from_spirv, DEFVAL("")); + ClassDB::bind_method(D_METHOD("shader_create_from_spirv", "spirv_data", "name"), &RenderingDevice::_shader_create_from_spirv, DEFVAL("")); ClassDB::bind_method(D_METHOD("shader_create_from_bytecode", "binary_data"), &RenderingDevice::shader_create_from_bytecode); ClassDB::bind_method(D_METHOD("shader_get_vertex_input_attribute_mask", "shader"), &RenderingDevice::shader_get_vertex_input_attribute_mask); diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index bb028cb84c..2cf1f165dd 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -495,6 +495,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; @@ -668,9 +669,9 @@ public: }; virtual String shader_get_binary_cache_key() const = 0; - virtual Vector<uint8_t> shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv) = 0; + virtual Vector<uint8_t> shader_compile_binary_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv, const String &p_shader_name = "") = 0; - virtual RID shader_create_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv); + virtual RID shader_create_from_spirv(const Vector<ShaderStageSPIRVData> &p_spirv, const String &p_shader_name = ""); virtual RID shader_create_from_bytecode(const Vector<uint8_t> &p_shader_binary) = 0; virtual uint32_t shader_get_vertex_input_attribute_mask(RID p_shader) = 0; @@ -1200,8 +1201,8 @@ protected: RID _vertex_array_create(uint32_t p_vertex_count, VertexFormatID p_vertex_format, const TypedArray<RID> &p_src_buffers); Ref<RDShaderSPIRV> _shader_compile_spirv_from_source(const Ref<RDShaderSource> &p_source, bool p_allow_cache = true); - Vector<uint8_t> _shader_compile_binary_from_spirv(const Ref<RDShaderSPIRV> &p_bytecode); - RID _shader_create_from_spirv(const Ref<RDShaderSPIRV> &p_spirv); + Vector<uint8_t> _shader_compile_binary_from_spirv(const Ref<RDShaderSPIRV> &p_bytecode, const String &p_shader_name = ""); + RID _shader_create_from_spirv(const Ref<RDShaderSPIRV> &p_spirv, const String &p_shader_name = ""); RID _uniform_set_create(const Array &p_uniforms, RID p_shader, uint32_t p_shader_set); 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/shader_language.cpp b/servers/rendering/shader_language.cpp index 20fcb1396d..4218214fda 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -2877,6 +2877,27 @@ bool ShaderLanguage::is_scalar_type(DataType p_type) { return p_type == TYPE_BOOL || p_type == TYPE_INT || p_type == TYPE_UINT || p_type == TYPE_FLOAT; } +bool ShaderLanguage::is_float_type(DataType p_type) { + switch (p_type) { + case TYPE_FLOAT: + case TYPE_VEC2: + case TYPE_VEC3: + case TYPE_VEC4: + case TYPE_MAT2: + case TYPE_MAT3: + case TYPE_MAT4: + case TYPE_SAMPLER2D: + case TYPE_SAMPLER2DARRAY: + case TYPE_SAMPLER3D: + case TYPE_SAMPLERCUBE: + case TYPE_SAMPLERCUBEARRAY: { + return true; + } + default: { + return false; + } + } +} bool ShaderLanguage::is_sampler_type(DataType p_type) { return p_type == TYPE_SAMPLER2D || p_type == TYPE_ISAMPLER2D || diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h index 9e0a63f0f7..18525e054e 100644 --- a/servers/rendering/shader_language.h +++ b/servers/rendering/shader_language.h @@ -774,6 +774,7 @@ public: static DataType get_scalar_type(DataType p_type); static int get_cardinality(DataType p_type); static bool is_scalar_type(DataType p_type); + static bool is_float_type(DataType p_type); static bool is_sampler_type(DataType p_type); static Variant constant_value_to_variant(const Vector<ShaderLanguage::ConstantNode::Value> &p_value, DataType p_type, ShaderLanguage::ShaderNode::Uniform::Hint p_hint = ShaderLanguage::ShaderNode::Uniform::HINT_NONE); static PropertyInfo uniform_to_property_info(const ShaderNode::Uniform &p_uniform); diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp index 4488069698..0bfcccef28 100644 --- a/servers/rendering/shader_types.cpp +++ b/servers/rendering/shader_types.cpp @@ -342,7 +342,6 @@ ShaderTypes::ShaderTypes() { shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["VELOCITY"] = ShaderLanguage::TYPE_VEC3; shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["MASS"] = ShaderLanguage::TYPE_FLOAT; shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["ACTIVE"] = ShaderLanguage::TYPE_BOOL; - shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["RESTART"] = constt(ShaderLanguage::TYPE_BOOL); shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["CUSTOM"] = ShaderLanguage::TYPE_VEC4; shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["TRANSFORM"] = ShaderLanguage::TYPE_MAT4; shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["LIFETIME"] = constt(ShaderLanguage::TYPE_FLOAT); diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 222ea9e622..78604dfe8c 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2138,6 +2138,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 +2256,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); @@ -2795,6 +2802,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..8fbf231d9b 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; @@ -1542,6 +1553,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..c05ff78718 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", "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..fc1d82a964 100644 --- a/servers/xr/xr_interface.cpp +++ b/servers/xr/xr_interface.cpp @@ -36,21 +36,19 @@ void XRInterface::_bind_methods() { 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..e1519d1463 --- /dev/null +++ b/servers/xr/xr_interface_extension.cpp @@ -0,0 +1,241 @@ +/*************************************************************************/ +/* 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_compositor.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); + + ClassDB::bind_method(D_METHOD("add_blit", "render_target", "rect", "use_layer", "layer", "apply_lens_distortion", "eye_center", "k1", "k2", "upscale", "aspect_ratio"), &XRInterfaceExtension::add_blit); + + 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); + + 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); +} + +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, Rect2i p_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.rect = p_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); +} diff --git a/modules/gdnative/xr/xr_interface_gdnative.h b/servers/xr/xr_interface_extension.h index 42e9206c1f..ab4d90bfe6 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,78 @@ /* 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, Rect2i p_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); }; -#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_curve.h b/tests/test_curve.h index 7eeee86f32..e079905e35 100644 --- a/tests/test_curve.h +++ b/tests/test_curve.h @@ -216,6 +216,41 @@ TEST_CASE("[Curve] Custom curve with linear tangents") { Math::is_equal_approx(curve->interpolate_baked(0.7), (real_t)0.8), "Custom free curve should return the expected baked value at offset 0.7 after removing point at invalid index 10."); } + +TEST_CASE("[Curve2D] Linear sampling should return exact value") { + Ref<Curve2D> curve = memnew(Curve2D); + int len = 2048; + + curve->add_point(Vector2(0, 0)); + curve->add_point(Vector2((float)len, 0)); + + float baked_length = curve->get_baked_length(); + CHECK((float)len == baked_length); + + for (int i = 0; i < len; i++) { + float expected = (float)i; + Vector2 pos = curve->interpolate_baked(expected); + CHECK_MESSAGE(pos.x == expected, "interpolate_baked should return exact value"); + } +} + +TEST_CASE("[Curve3D] Linear sampling should return exact value") { + Ref<Curve3D> curve = memnew(Curve3D); + int len = 2048; + + curve->add_point(Vector3(0, 0, 0)); + curve->add_point(Vector3((float)len, 0, 0)); + + float baked_length = curve->get_baked_length(); + CHECK((float)len == baked_length); + + for (int i = 0; i < len; i++) { + float expected = (float)i; + Vector3 pos = curve->interpolate_baked(expected); + CHECK_MESSAGE(pos.x == expected, "interpolate_baked should return exact value"); + } +} + } // namespace TestCurve #endif // TEST_CURVE_H 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_geometry_3d.h b/tests/test_geometry_3d.h index 2b2a424b2b..40cb8bc07a 100644 --- a/tests/test_geometry_3d.h +++ b/tests/test_geometry_3d.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef TEST_3D_GEOMETRY_H -#define TEST_3D_GEOMETRY_H +#ifndef TEST_GEOMETRY_3D_H +#define TEST_GEOMETRY_3D_H #include "core/math/geometry_3d.h" #include "core/math/plane.h" @@ -38,7 +38,7 @@ #include "tests/test_macros.h" #include "vector" -namespace Test3DGeometry { +namespace TestGeometry3D { TEST_CASE("[Geometry3D] Closest Points Between Segments") { struct Case { Vector3 p_1, p_2, p_3, p_4; @@ -57,6 +57,7 @@ TEST_CASE("[Geometry3D] Closest Points Between Segments") { CHECK(current_case.got_2.is_equal_approx(current_case.want_2)); } } + TEST_CASE("[Geometry3D] Closest Distance Between Segments") { struct Case { Vector3 p_1, p_2, p_3, p_4; @@ -73,6 +74,7 @@ TEST_CASE("[Geometry3D] Closest Distance Between Segments") { CHECK(out == current_case.want); } } + TEST_CASE("[Geometry3D] Build Box Planes") { const Vector3 extents = Vector3(5, 5, 20); Vector<Plane> box = Geometry3D::build_box_planes(extents); @@ -90,6 +92,7 @@ TEST_CASE("[Geometry3D] Build Box Planes") { CHECK(extents.z == box[5].d); CHECK(box[5].normal == Vector3(0, 0, -1)); } + TEST_CASE("[Geometry3D] Build Capsule Planes") { struct Case { real_t radius, height; @@ -109,6 +112,7 @@ TEST_CASE("[Geometry3D] Build Capsule Planes") { CHECK(capsule.size() == current_case.want_size); } } + TEST_CASE("[Geometry3D] Build Cylinder Planes") { struct Case { real_t radius, height; @@ -127,6 +131,7 @@ TEST_CASE("[Geometry3D] Build Cylinder Planes") { CHECK(planes.size() == current_case.want_size); } } + TEST_CASE("[Geometry3D] Build Sphere Planes") { struct Case { real_t radius; @@ -145,6 +150,7 @@ TEST_CASE("[Geometry3D] Build Sphere Planes") { CHECK(planes.size() == 63); } } + TEST_CASE("[Geometry3D] Build Convex Mesh") { struct Case { Vector<Plane> object; @@ -166,6 +172,7 @@ TEST_CASE("[Geometry3D] Build Convex Mesh") { CHECK(mesh.vertices.size() == current_case.want_vertices); } } + TEST_CASE("[Geometry3D] Clip Polygon") { struct Case { Plane clipping_plane; @@ -190,6 +197,7 @@ TEST_CASE("[Geometry3D] Clip Polygon") { } } } + TEST_CASE("[Geometry3D] Compute Convex Mesh Points") { struct Case { Vector<Plane> mesh; @@ -215,6 +223,7 @@ TEST_CASE("[Geometry3D] Compute Convex Mesh Points") { CHECK(vectors == current_case.want); } } + TEST_CASE("[Geometry3D] Get Closest Point To Segment") { struct Case { Vector3 point; @@ -235,6 +244,7 @@ TEST_CASE("[Geometry3D] Get Closest Point To Segment") { CHECK(output.is_equal_approx(current_case.want)); } } + TEST_CASE("[Geometry3D] Plane and Box Overlap") { struct Case { Vector3 normal, max_box; @@ -254,6 +264,7 @@ TEST_CASE("[Geometry3D] Plane and Box Overlap") { CHECK(overlap == current_case.want); } } + TEST_CASE("[Geometry3D] Is Point in Projected Triangle") { struct Case { Vector3 point, v_1, v_2, v_3; @@ -272,6 +283,7 @@ TEST_CASE("[Geometry3D] Is Point in Projected Triangle") { CHECK(output == current_case.want); } } + TEST_CASE("[Geometry3D] Does Ray Intersect Triangle") { struct Case { Vector3 from, direction, v_1, v_2, v_3; @@ -291,6 +303,7 @@ TEST_CASE("[Geometry3D] Does Ray Intersect Triangle") { CHECK(output == current_case.want); } } + TEST_CASE("[Geometry3D] Does Segment Intersect Convex") { struct Case { Vector3 from, to; @@ -311,6 +324,7 @@ TEST_CASE("[Geometry3D] Does Segment Intersect Convex") { CHECK(output == current_case.want); } } + TEST_CASE("[Geometry3D] Segment Intersects Cylinder") { struct Case { Vector3 from, to; @@ -330,6 +344,7 @@ TEST_CASE("[Geometry3D] Segment Intersects Cylinder") { CHECK(output == current_case.want); } } + TEST_CASE("[Geometry3D] Segment Intersects Cylinder") { struct Case { Vector3 from, to, sphere_pos; @@ -350,6 +365,7 @@ TEST_CASE("[Geometry3D] Segment Intersects Cylinder") { CHECK(output == current_case.want); } } + TEST_CASE("[Geometry3D] Segment Intersects Triangle") { struct Case { Vector3 from, to, v_1, v_2, v_3, *result; @@ -368,6 +384,7 @@ TEST_CASE("[Geometry3D] Segment Intersects Triangle") { CHECK(output == current_case.want); } } + TEST_CASE("[Geometry3D] Triangle and Box Overlap") { struct Case { Vector3 box_centre; @@ -389,6 +406,7 @@ TEST_CASE("[Geometry3D] Triangle and Box Overlap") { CHECK(output == current_case.want); } } + TEST_CASE("[Geometry3D] Triangle and Sphere Intersect") { struct Case { Vector<Vector3> triangle; @@ -413,5 +431,6 @@ TEST_CASE("[Geometry3D] Triangle and Sphere Intersect") { CHECK(output == current_case.want); } } -} // namespace Test3DGeometry -#endif +} // namespace TestGeometry3D + +#endif // TEST_GEOMETRY_3D_H diff --git a/tests/test_string.h b/tests/test_string.h index 1982d8de60..79fdb7bb56 100644 --- a/tests/test_string.h +++ b/tests/test_string.h @@ -350,6 +350,9 @@ TEST_CASE("[String] Insertion") { } TEST_CASE("[String] Number to string") { + CHECK(String::num(0) == "0"); + CHECK(String::num(0.0) == "0"); // No trailing zeros. + CHECK(String::num(-0.0) == "-0"); // Includes sign even for zero. CHECK(String::num(3.141593) == "3.141593"); CHECK(String::num(3.141593, 3) == "3.142"); CHECK(String::num_real(3.141593) == "3.141593"); @@ -357,6 +360,27 @@ TEST_CASE("[String] Number to string") { CHECK(String::num_int64(3141593) == "3141593"); CHECK(String::num_int64(0xA141593, 16) == "a141593"); CHECK(String::num_int64(0xA141593, 16, true) == "A141593"); + CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros. + + // Checks doubles with many decimal places. + CHECK(String::num(0.0000012345432123454321, -1) == "0.00000123454321"); // -1 uses 14 as sane default. + CHECK(String::num(0.0000012345432123454321) == "0.00000123454321"); // -1 is the default value. + CHECK(String::num(-0.0000012345432123454321) == "-0.00000123454321"); + CHECK(String::num(-10000.0000012345432123454321) == "-10000.0000012345"); + CHECK(String::num(0.0000000000012345432123454321) == "0.00000000000123"); + CHECK(String::num(0.0000000000012345432123454321, 3) == "0"); + + // Note: When relevant (remainder > 0.5), the last digit gets rounded up, + // which can also lead to not include a trailing zero, e.g. "...89" -> "...9". + CHECK(String::num(0.0000056789876567898765) == "0.00000567898766"); // Should round last digit. + CHECK(String::num(10000.000005678999999999) == "10000.000005679"); // We cut at ...789|99 which is rounded to ...79, so only 13 decimals. + CHECK(String::num(42.12999999, 6) == "42.13"); // Also happens with lower decimals count. + + // 32 is MAX_DECIMALS. We can't reliably store that many so we can't compare against a string, + // but we can check that the string length is 34 (32 + 2 for "0."). + CHECK(String::num(0.00000123456789987654321123456789987654321, 32).length() == 34); + CHECK(String::num(0.00000123456789987654321123456789987654321, 42).length() == 34); // Should enforce MAX_DECIMALS. + CHECK(String::num(10000.00000123456789987654321123456789987654321, 42).length() == 38); // 32 decimals + "10000.". } TEST_CASE("[String] String to integer") { 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/tests/test_variant.h b/tests/test_variant.h index dfc72b512c..598fe488d7 100644 --- a/tests/test_variant.h +++ b/tests/test_variant.h @@ -39,7 +39,7 @@ namespace TestVariant { TEST_CASE("[Variant] Writer and parser integer") { - int64_t a32 = 2147483648; // 2^31, so out of bounds for 32-bit signed int [-2^31,-2^31-1]. + int64_t a32 = 2147483648; // 2^31, so out of bounds for 32-bit signed int [-2^31, +2^31-1]. String a32_str; VariantWriter::write_to_string(a32, a32_str); @@ -76,34 +76,35 @@ TEST_CASE("[Variant] Writer and parser integer") { CHECK_MESSAGE(b64_int_parsed == 9223372036854775807, "The result should be clamped to max value."); } -TEST_CASE("[Variant] Writer and parser float") { - // Assuming real_t is double. - real_t a64 = 340282346638528859811704183484516925440.0; // std::numeric_limits<real_t>::max() +TEST_CASE("[Variant] Writer and parser Variant::FLOAT") { + // Variant::FLOAT is always 64-bit (C++ double). + // This is the maximum non-infinity double-precision float. + double a64 = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0; String a64_str; VariantWriter::write_to_string(a64, a64_str); - CHECK_MESSAGE(a64_str == "3.40282e+38", "Writes in scientific notation."); + CHECK_MESSAGE(a64_str == "1.79769e+308", "Writes in scientific notation."); CHECK_MESSAGE(a64_str != "inf", "Should not overflow."); CHECK_MESSAGE(a64_str != "nan", "The result should be defined."); - VariantParser::StreamString ss; String errs; int line; - Variant b64_parsed; - real_t b64_float_parsed; - - ss.s = a64_str; - VariantParser::parse(&ss, b64_parsed, errs, line); - b64_float_parsed = b64_parsed; + Variant variant_parsed; + double float_parsed; - CHECK_MESSAGE(b64_float_parsed == 340282001837565597733306976381245063168.0, "Should parse back."); + VariantParser::StreamString bss; + bss.s = a64_str; + VariantParser::parse(&bss, variant_parsed, errs, line); + float_parsed = variant_parsed; // Loses precision, but that's alright. - - ss.s = "1.0e+100"; // Float version of Googol! - VariantParser::parse(&ss, b64_parsed, errs, line); - b64_float_parsed = b64_parsed; - - CHECK_MESSAGE(b64_float_parsed == 340282001837565597733306976381245063168.0, "Should not overflow."); + CHECK_MESSAGE(float_parsed == 1.79769e+308, "Should parse back."); + + // Approximation of Googol with a double-precision float. + VariantParser::StreamString css; + css.s = "1.0e+100"; + VariantParser::parse(&css, variant_parsed, errs, line); + float_parsed = variant_parsed; + CHECK_MESSAGE(float_parsed == 1.0e+100, "Should match the double literal."); } TEST_CASE("[Variant] Assignment To Bool from Int,Float,String,Vec2,Vec2i,Vec3,Vec3i and Color") { diff --git a/thirdparty/README.md b/thirdparty/README.md index 1c310eae05..dd2f509516 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -482,6 +482,19 @@ Collection of single-file libraries used in Godot components. * 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/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()); + +} |