diff options
385 files changed, 5131 insertions, 7958 deletions
diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index 4967fe379f..04bdf971ca 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -14,7 +14,7 @@ concurrency: jobs: build-macos: - runs-on: "macos-latest" + runs-on: "macos-12" name: ${{ matrix.name }} strategy: fail-fast: false diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 7f4a3b4b9d..c1711a9e81 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -360,12 +360,6 @@ Copyright: 1998-2010, Gilles Vollant 2009-2010, Mathias Svensson License: Zlib -Files: ./thirdparty/misc/clipper.cpp - ./thirdparty/misc/clipper.hpp -Comment: Clipper -Copyright: 2010-2017, Angus Johnson -License: BSL-1.0 - Files: ./thirdparty/misc/cubemap_coeffs.h Comment: Fast Filtering of Reflection Probes Copyright: 2016, Activision Publishing, Inc. diff --git a/SConstruct b/SConstruct index dbcd9dfd68..18511ff5ee 100644 --- a/SConstruct +++ b/SConstruct @@ -13,6 +13,7 @@ import time from types import ModuleType from collections import OrderedDict from importlib.util import spec_from_file_location, module_from_spec +from SCons import __version__ as scons_raw_version # Explicitly resolve the helper modules, this is done to avoid clash with # modules of the same name that might be randomly added (e.g. someone adding @@ -130,6 +131,7 @@ if "TERM" in os.environ: # Used for colored output. env.disabled_modules = [] env.module_version_string = "" env.msvc = False +env.scons_version = env._get_major_minor_revision(scons_raw_version) env.__class__.disable_module = methods.disable_module @@ -168,8 +170,11 @@ if profile: opts = Variables(customs, ARGUMENTS) # Target build options -opts.Add("platform", "Target platform (%s)" % ("|".join(platform_list),), "") -opts.Add("p", "Platform (alias for 'platform')", "") +if env.scons_version >= (4, 3): + opts.Add(["platform", "p"], "Target platform (%s)" % "|".join(platform_list), "") +else: + opts.Add("platform", "Target platform (%s)" % "|".join(platform_list), "") + opts.Add("p", "Alias for 'platform'", "") opts.Add(EnumVariable("target", "Compilation target", "editor", ("editor", "template_release", "template_debug"))) opts.Add(EnumVariable("arch", "CPU architecture", "auto", ["auto"] + architectures, architecture_aliases)) opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False)) @@ -285,13 +290,12 @@ if env["import_env_vars"]: # Platform selection: validate input, and add options. -selected_platform = "" +selected_platform = env["platform"] -if env["platform"] != "": - selected_platform = env["platform"] -elif env["p"] != "": +if env.scons_version < (4, 3) and not selected_platform: selected_platform = env["p"] -else: + +if selected_platform == "": # Missing `platform` argument, try to detect platform automatically if ( sys.platform.startswith("linux") @@ -977,21 +981,16 @@ if env["vsproj"]: env.vs_incs = [] env.vs_srcs = [] -# CompileDB and Ninja are only available with certain SCons versions which -# not everybody might have yet, so we have to check. -from SCons import __version__ as scons_raw_version - -scons_ver = env._get_major_minor_revision(scons_raw_version) -if env["compiledb"] and scons_ver < (4, 0, 0): +if env["compiledb"] and env.scons_version < (4, 0, 0): # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later. print("The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version) Exit(255) -if scons_ver >= (4, 0, 0): +if env.scons_version >= (4, 0, 0): env.Tool("compilation_db") env.Alias("compiledb", env.CompilationDatabase()) if env["ninja"]: - if scons_ver < (4, 2, 0): + if env.scons_version < (4, 2, 0): print("The `ninja=yes` option requires SCons 4.2 or later, but your version is %s." % scons_raw_version) Exit(255) diff --git a/core/SCsub b/core/SCsub index f7b733a221..ec4658e8ca 100644 --- a/core/SCsub +++ b/core/SCsub @@ -61,7 +61,6 @@ thirdparty_misc_sources = [ # C++ sources "pcg.cpp", "polypartition.cpp", - "clipper.cpp", "smolv.cpp", ] thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_misc_sources] diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 03c31bee28..467b696eae 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -152,12 +152,10 @@ void ResourceLoader::_bind_methods() { ////// ResourceSaver ////// Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, BitField<SaverFlags> p_flags) { - ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, "Can't save empty resource to path '" + p_path + "'."); return ::ResourceSaver::save(p_resource, p_path, p_flags); } Vector<String> ResourceSaver::get_recognized_extensions(const Ref<Resource> &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); Vector<String> ret; @@ -1444,6 +1442,15 @@ Error ClassDB::class_set_property(Object *p_object, const StringName &p_property return OK; } +Variant ClassDB::class_get_property_default_value(const StringName &p_class, const StringName &p_property) const { + bool valid; + Variant ret = ::ClassDB::class_get_default_property_value(p_class, p_property, &valid); + if (valid) { + return ret; + } + return Variant(); +} + bool ClassDB::class_has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance) const { return ::ClassDB::has_method(p_class, p_method, p_no_inheritance); } @@ -1533,6 +1540,10 @@ StringName ClassDB::class_get_integer_constant_enum(const StringName &p_class, c return ::ClassDB::get_integer_constant_enum(p_class, p_name, p_no_inheritance); } +bool ClassDB::is_class_enum_bitfield(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance) const { + return ::ClassDB::is_enum_bitfield(p_class, p_enum, p_no_inheritance); +} + bool ClassDB::is_class_enabled(const StringName &p_class) const { return ::ClassDB::is_class_enabled(p_class); } @@ -1549,7 +1560,7 @@ void ClassDB::get_argument_options(const StringName &p_function, int p_idx, List pf == "class_has_method" || pf == "class_get_method_list" || pf == "class_get_integer_constant_list" || pf == "class_has_integer_constant" || pf == "class_get_integer_constant" || pf == "class_has_enum" || pf == "class_get_enum_list" || pf == "class_get_enum_constants" || pf == "class_get_integer_constant_enum" || - pf == "is_class_enabled"); + pf == "is_class_enabled" || pf == "is_class_enum_bitfield"); } if (first_argument_is_class || pf == "is_parent_class") { for (const String &E : get_class_list()) { @@ -1578,6 +1589,8 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("class_get_property", "object", "property"), &ClassDB::class_get_property); ::ClassDB::bind_method(D_METHOD("class_set_property", "object", "property", "value"), &ClassDB::class_set_property); + ::ClassDB::bind_method(D_METHOD("class_get_property_default_value", "class", "property"), &ClassDB::class_get_property_default_value); + ::ClassDB::bind_method(D_METHOD("class_has_method", "class", "method", "no_inheritance"), &ClassDB::class_has_method, DEFVAL(false)); ::ClassDB::bind_method(D_METHOD("class_get_method_argument_count", "class", "method", "no_inheritance"), &ClassDB::class_get_method_argument_count, DEFVAL(false)); @@ -1594,6 +1607,8 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("class_get_enum_constants", "class", "enum", "no_inheritance"), &ClassDB::class_get_enum_constants, DEFVAL(false)); ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_enum", "class", "name", "no_inheritance"), &ClassDB::class_get_integer_constant_enum, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("is_class_enum_bitfield", "class", "enum", "no_inheritance"), &ClassDB::is_class_enum_bitfield, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("is_class_enabled", "class"), &ClassDB::is_class_enabled); } diff --git a/core/core_bind.h b/core/core_bind.h index 3c0cdc25ce..148e0ad83e 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -447,6 +447,8 @@ public: Variant class_get_property(Object *p_object, const StringName &p_property) const; Error class_set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const; + Variant class_get_property_default_value(const StringName &p_class, const StringName &p_property) const; + bool class_has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const; int class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const; @@ -462,6 +464,8 @@ public: PackedStringArray class_get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const; StringName class_get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const; + bool is_class_enum_bitfield(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const; + bool is_class_enabled(const StringName &p_class) const; #ifdef TOOLS_ENABLED diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 546c40e9cb..3ea5791282 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -46,6 +46,41 @@ String GDExtension::get_extension_list_config_file() { return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg"); } +Vector<SharedObject> GDExtension::find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature) { + Vector<SharedObject> dependencies_shared_objects; + if (p_config->has_section("dependencies")) { + List<String> config_dependencies; + p_config->get_section_keys("dependencies", &config_dependencies); + + for (const String &dependency : config_dependencies) { + Vector<String> dependency_tags = dependency.split("."); + bool all_tags_met = true; + for (int i = 0; i < dependency_tags.size(); i++) { + String tag = dependency_tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met) { + Dictionary dependency_value = p_config->get_value("dependencies", dependency); + for (const Variant *key = dependency_value.next(nullptr); key; key = dependency_value.next(key)) { + String dependency_path = *key; + String target_path = dependency_value[*key]; + if (dependency_path.is_relative_path()) { + dependency_path = p_path.get_base_dir().path_join(dependency_path); + } + dependencies_shared_objects.push_back(SharedObject(dependency_path, dependency_tags, target_path)); + } + break; + } + } + } + + return dependencies_shared_objects; +} + String GDExtension::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) { // First, check the explicit libraries. if (p_config->has_section("libraries")) { @@ -727,15 +762,31 @@ GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const String return *function; } -Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol) { +Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies) { String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path); + + Vector<String> abs_dependencies_paths; + if (p_dependencies != nullptr && !p_dependencies->is_empty()) { + for (const SharedObject &dependency : *p_dependencies) { + abs_dependencies_paths.push_back(ProjectSettings::get_singleton()->globalize_path(dependency.path)); + } + } + String actual_lib_path; - Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, true, &actual_lib_path, Engine::get_singleton()->is_editor_hint()); + OS::GDExtensionData data = { + true, // also_set_library_path + &actual_lib_path, // r_resolved_path + Engine::get_singleton()->is_editor_hint(), // generate_temp_files + &abs_dependencies_paths, // library_dependencies + }; + Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data); if (actual_lib_path.get_file() != abs_path.get_file()) { // If temporary files are generated, let's change the library path to point at the original, // because that's what we want to check to see if it's changed. library_path = actual_lib_path.get_base_dir().path_join(p_path.get_file()); + } else { + library_path = p_path; } ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path); @@ -970,7 +1021,8 @@ Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path, FileAccess::get_modified_time(library_path)); #endif - err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol); + Vector<SharedObject> library_dependencies = GDExtension::find_extension_dependencies(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); }); + err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol, &library_dependencies); if (err != OK) { // Unreference the extension so that this loading can be considered a failure. p_extension.unref(); diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 1f48edecf7..23b1f51208 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -37,6 +37,7 @@ #include "core/io/config_file.h" #include "core/io/resource_loader.h" #include "core/object/ref_counted.h" +#include "core/os/shared_object.h" class GDExtensionMethodBind; @@ -123,8 +124,9 @@ public: static String get_extension_list_config_file(); static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr); + static Vector<SharedObject> find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature); - Error open_library(const String &p_path, const String &p_entry_symbol); + Error open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies = nullptr); void close_library(); enum InitializationLevel { diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index d2a5103953..1cf388b33a 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -867,6 +867,7 @@ void FileAccess::_bind_methods() { ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_bytes", "path"), &FileAccess::_get_file_as_bytes); ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_string", "path"), &FileAccess::_get_file_as_string); + ClassDB::bind_method(D_METHOD("resize", "length"), &FileAccess::resize); ClassDB::bind_method(D_METHOD("flush"), &FileAccess::flush); ClassDB::bind_method(D_METHOD("get_path"), &FileAccess::get_path); ClassDB::bind_method(D_METHOD("get_path_absolute"), &FileAccess::get_path_absolute); diff --git a/core/io/file_access.h b/core/io/file_access.h index 0b631f68b1..2ab84db4b6 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -166,6 +166,7 @@ public: virtual Error get_error() const = 0; ///< get last error + virtual Error resize(int64_t p_length) = 0; virtual void flush() = 0; virtual void store_8(uint8_t p_dest) = 0; ///< store a byte virtual void store_16(uint16_t p_dest); ///< store 16 bits uint diff --git a/core/io/file_access_compressed.h b/core/io/file_access_compressed.h index bf57eaa07c..f706c82f8e 100644 --- a/core/io/file_access_compressed.h +++ b/core/io/file_access_compressed.h @@ -88,6 +88,7 @@ public: virtual Error get_error() const override; ///< get last error + virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; virtual void store_8(uint8_t p_dest) override; ///< store a byte diff --git a/core/io/file_access_encrypted.h b/core/io/file_access_encrypted.h index 489d213b8f..42afe49a5e 100644 --- a/core/io/file_access_encrypted.h +++ b/core/io/file_access_encrypted.h @@ -78,6 +78,7 @@ public: virtual Error get_error() const override; ///< get last error + virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; virtual void store_8(uint8_t p_dest) override; ///< store a byte virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes diff --git a/core/io/file_access_memory.h b/core/io/file_access_memory.h index d9efb354c3..e9fbc26d75 100644 --- a/core/io/file_access_memory.h +++ b/core/io/file_access_memory.h @@ -61,6 +61,7 @@ public: virtual Error get_error() const override; ///< get last error + virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; virtual void store_8(uint8_t p_byte) override; ///< store a byte virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index 0deacfebab..594ac8f089 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -177,6 +177,7 @@ public: virtual Error get_error() const override; + virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; virtual void store_8(uint8_t p_dest) override; diff --git a/core/io/file_access_zip.h b/core/io/file_access_zip.h index e9ea74e01d..88b63e93e2 100644 --- a/core/io/file_access_zip.h +++ b/core/io/file_access_zip.h @@ -100,6 +100,7 @@ public: virtual Error get_error() const override; ///< get last error + virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; virtual void store_8(uint8_t p_dest) override; ///< store a byte diff --git a/core/io/json.cpp b/core/io/json.cpp index 5a1fe45f70..61051727c1 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -344,10 +344,10 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to r_token.value = number; return OK; - } else if (is_ascii_char(p_str[index])) { + } else if (is_ascii_alphabet_char(p_str[index])) { String id; - while (is_ascii_char(p_str[index])) { + while (is_ascii_alphabet_char(p_str[index])) { id += p_str[index]; index++; } diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp index e4022b2073..1dc1245355 100644 --- a/core/io/resource_saver.cpp +++ b/core/io/resource_saver.cpp @@ -98,6 +98,7 @@ void ResourceFormatSaver::_bind_methods() { } Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) { + ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, "Can't save empty resource to path '" + p_path + "'."); String path = p_path; if (path.is_empty()) { path = p_resource->get_path(); @@ -174,6 +175,7 @@ void ResourceSaver::set_save_callback(ResourceSavedCallback p_callback) { } void ResourceSaver::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) { + ERR_FAIL_COND_MSG(p_resource.is_null(), "It's not a reference to a valid Resource object."); for (int i = 0; i < saver_count; i++) { saver[i]->get_recognized_extensions(p_resource, p_extensions); } diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 84ac878172..34ed1c2d85 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -293,7 +293,7 @@ Vector3 Basis::get_scale_abs() const { Vector3(rows[0][2], rows[1][2], rows[2][2]).length()); } -Vector3 Basis::get_scale_local() const { +Vector3 Basis::get_scale_global() const { real_t det_sign = SIGN(determinant()); return det_sign * Vector3(rows[0].length(), rows[1].length(), rows[2].length()); } diff --git a/core/math/basis.h b/core/math/basis.h index 79f3bda8f8..918cbc18d4 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -103,7 +103,7 @@ struct _NO_DISCARD_ Basis { Vector3 get_scale() const; Vector3 get_scale_abs() const; - Vector3 get_scale_local() const; + Vector3 get_scale_global() const; void set_axis_angle_scale(const Vector3 &p_axis, real_t p_angle, const Vector3 &p_scale); void set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale, EulerOrder p_order = EulerOrder::YXZ); diff --git a/core/math/delaunay_3d.h b/core/math/delaunay_3d.h index 45571fb570..25bd4e8d89 100644 --- a/core/math/delaunay_3d.h +++ b/core/math/delaunay_3d.h @@ -46,7 +46,8 @@ class Delaunay3D { struct Simplex; enum { - ACCEL_GRID_SIZE = 16 + ACCEL_GRID_SIZE = 16, + QUANTIZATION_MAX = 1 << 16 // A power of two smaller than the 23 bit significand of a float. }; struct GridPos { Vector3i pos; @@ -173,38 +174,25 @@ class Delaunay3D { R128 radius2 = rel2_x * rel2_x + rel2_y * rel2_y + rel2_z * rel2_z; - return radius2 < (p_simplex.circum_r2 - R128(0.00001)); + return radius2 < (p_simplex.circum_r2 - R128(0.0000000001)); + // When this tolerance is too big, it can result in overlapping simplices. + // When it's too small, large amounts of planar simplices are created. } static bool simplex_is_coplanar(const Vector3 *p_points, const Simplex &p_simplex) { - Plane p(p_points[p_simplex.points[0]], p_points[p_simplex.points[1]], p_points[p_simplex.points[2]]); - if (ABS(p.distance_to(p_points[p_simplex.points[3]])) < CMP_EPSILON) { - return true; + // Checking every possible distance like this is overkill, but only checking + // one is not enough. If the simplex is almost planar then the vectors p1-p2 + // and p1-p3 can be practically collinear, which makes Plane unreliable. + for (uint32_t i = 0; i < 4; i++) { + Plane p(p_points[p_simplex.points[i]], p_points[p_simplex.points[(i + 1) % 4]], p_points[p_simplex.points[(i + 2) % 4]]); + // This tolerance should not be smaller than the one used with + // Plane::has_point() when creating the LightmapGI probe BSP tree. + if (ABS(p.distance_to(p_points[p_simplex.points[(i + 3) % 4]])) < 0.001) { + return true; + } } - Projection cm; - - cm.columns[0][0] = p_points[p_simplex.points[0]].x; - cm.columns[0][1] = p_points[p_simplex.points[1]].x; - cm.columns[0][2] = p_points[p_simplex.points[2]].x; - cm.columns[0][3] = p_points[p_simplex.points[3]].x; - - cm.columns[1][0] = p_points[p_simplex.points[0]].y; - cm.columns[1][1] = p_points[p_simplex.points[1]].y; - cm.columns[1][2] = p_points[p_simplex.points[2]].y; - cm.columns[1][3] = p_points[p_simplex.points[3]].y; - - cm.columns[2][0] = p_points[p_simplex.points[0]].z; - cm.columns[2][1] = p_points[p_simplex.points[1]].z; - cm.columns[2][2] = p_points[p_simplex.points[2]].z; - cm.columns[2][3] = p_points[p_simplex.points[3]].z; - - cm.columns[3][0] = 1.0; - cm.columns[3][1] = 1.0; - cm.columns[3][2] = 1.0; - cm.columns[3][3] = 1.0; - - return ABS(cm.determinant()) <= CMP_EPSILON; + return false; } public: @@ -215,9 +203,10 @@ public: static Vector<OutputSimplex> tetrahedralize(const Vector<Vector3> &p_points) { uint32_t point_count = p_points.size(); Vector3 *points = (Vector3 *)memalloc(sizeof(Vector3) * (point_count + 4)); + const Vector3 *src_points = p_points.ptr(); + Vector3 proportions; { - const Vector3 *src_points = p_points.ptr(); AABB rect; for (uint32_t i = 0; i < point_count; i++) { Vector3 point = src_points[i]; @@ -226,17 +215,25 @@ public: } else { rect.expand_to(point); } - points[i] = point; } + real_t longest_axis = rect.size[rect.get_longest_axis_index()]; + proportions = Vector3(longest_axis, longest_axis, longest_axis) / rect.size; + for (uint32_t i = 0; i < point_count; i++) { - points[i] = (points[i] - rect.position) / rect.size; + // Scale points to the unit cube to better utilize R128 precision + // and quantize to stabilize triangulation over a wide range of + // distances. + points[i] = Vector3(Vector3i((src_points[i] - rect.position) / longest_axis * QUANTIZATION_MAX)) / QUANTIZATION_MAX; } - const real_t delta_max = Math::sqrt(2.0) * 20.0; + const real_t delta_max = Math::sqrt(2.0) * 100.0; Vector3 center = Vector3(0.5, 0.5, 0.5); - // any simplex that contains everything is good + // The larger the root simplex is, the more likely it is that the + // triangulation is convex. If it's not absolutely huge, there can + // be missing simplices that are not created for the outermost faces + // of the point cloud if the point density is very low there. points[point_count + 0] = center + Vector3(0, 1, 0) * delta_max; points[point_count + 1] = center + Vector3(0, -1, 1) * delta_max; points[point_count + 2] = center + Vector3(1, -1, -1) * delta_max; @@ -271,7 +268,7 @@ public: for (uint32_t i = 0; i < point_count; i++) { bool unique = true; for (uint32_t j = i + 1; j < point_count; j++) { - if (points[i].is_equal_approx(points[j])) { + if (points[i] == points[j]) { unique = false; break; } @@ -280,7 +277,7 @@ public: continue; } - Vector3i grid_pos = Vector3i(points[i] * ACCEL_GRID_SIZE); + Vector3i grid_pos = Vector3i(points[i] * proportions * ACCEL_GRID_SIZE); grid_pos = grid_pos.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1)); for (List<Simplex *>::Element *E = acceleration_grid[grid_pos.x][grid_pos.y][grid_pos.z].front(); E;) { @@ -300,6 +297,9 @@ public: Triangle t = Triangle(simplex->points[triangle_order[k][0]], simplex->points[triangle_order[k][1]], simplex->points[triangle_order[k][2]]); uint32_t *p = triangles_inserted.lookup_ptr(t); if (p) { + // This Delaunay implementation uses the Bowyer-Watson algorithm. + // The rule is that you don't reuse any triangles that were + // shared by any of the retriangulated simplices. triangles[*p].bad = true; } else { triangles_inserted.insert(t, triangles.size()); @@ -307,7 +307,6 @@ public: } } - //remove simplex and continue simplex_list.erase(simplex->SE); for (const GridPos &gp : simplex->grid_positions) { @@ -334,8 +333,8 @@ public: const real_t radius2 = Math::sqrt(double(new_simplex->circum_r2)) + 0.0001; Vector3 extents = Vector3(radius2, radius2, radius2); - Vector3i from = Vector3i((center - extents) * ACCEL_GRID_SIZE); - Vector3i to = Vector3i((center + extents) * ACCEL_GRID_SIZE); + Vector3i from = Vector3i((center - extents) * proportions * ACCEL_GRID_SIZE); + Vector3i to = Vector3i((center + extents) * proportions * ACCEL_GRID_SIZE); from = from.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1)); to = to.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1)); @@ -370,7 +369,7 @@ public: break; } } - if (invalid || simplex_is_coplanar(points, *simplex)) { + if (invalid || simplex_is_coplanar(src_points, *simplex)) { memdelete(simplex); continue; } diff --git a/core/math/geometry_2d.cpp b/core/math/geometry_2d.cpp index 602e95bc13..d60619b27f 100644 --- a/core/math/geometry_2d.cpp +++ b/core/math/geometry_2d.cpp @@ -30,12 +30,12 @@ #include "geometry_2d.h" -#include "thirdparty/misc/clipper.hpp" +#include "thirdparty/clipper2/include/clipper2/clipper.h" #include "thirdparty/misc/polypartition.h" #define STB_RECT_PACK_IMPLEMENTATION #include "thirdparty/misc/stb_rect_pack.h" -#define SCALE_FACTOR 100000.0 // Based on CMP_EPSILON. +#define PRECISION 5 // Based on CMP_EPSILON. Vector<Vector<Vector2>> Geometry2D::decompose_polygon_in_convex(const Vector<Point2> &polygon) { Vector<Vector<Vector2>> decomp; @@ -196,58 +196,59 @@ void Geometry2D::make_atlas(const Vector<Size2i> &p_rects, Vector<Point2i> &r_re } Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open) { - using namespace ClipperLib; + using namespace Clipper2Lib; - ClipType op = ctUnion; + ClipType op = ClipType::Union; switch (p_op) { case OPERATION_UNION: - op = ctUnion; + op = ClipType::Union; break; case OPERATION_DIFFERENCE: - op = ctDifference; + op = ClipType::Difference; break; case OPERATION_INTERSECTION: - op = ctIntersection; + op = ClipType::Intersection; break; case OPERATION_XOR: - op = ctXor; + op = ClipType::Xor; break; } - Path path_a, path_b; - // Need to scale points (Clipper's requirement for robust computation). + PathD path_a(p_polypath_a.size()); for (int i = 0; i != p_polypath_a.size(); ++i) { - path_a << IntPoint(p_polypath_a[i].x * (real_t)SCALE_FACTOR, p_polypath_a[i].y * (real_t)SCALE_FACTOR); + path_a[i] = PointD(p_polypath_a[i].x, p_polypath_a[i].y); } + PathD path_b(p_polypath_b.size()); for (int i = 0; i != p_polypath_b.size(); ++i) { - path_b << IntPoint(p_polypath_b[i].x * (real_t)SCALE_FACTOR, p_polypath_b[i].y * (real_t)SCALE_FACTOR); + path_b[i] = PointD(p_polypath_b[i].x, p_polypath_b[i].y); } - Clipper clp; - clp.AddPath(path_a, ptSubject, !is_a_open); // Forward compatible with Clipper 10.0.0. - clp.AddPath(path_b, ptClip, true); // Polylines cannot be set as clip. - Paths paths; + ClipperD clp(PRECISION); // Scale points up internally to attain the desired precision. + clp.PreserveCollinear(false); // Remove redundant vertices. + if (is_a_open) { + clp.AddOpenSubject({ path_a }); + } else { + clp.AddSubject({ path_a }); + } + clp.AddClip({ path_b }); + + PathsD paths; if (is_a_open) { - PolyTree tree; // Needed to populate polylines. - clp.Execute(op, tree); - OpenPathsFromPolyTree(tree, paths); + PolyTreeD tree; // Needed to populate polylines. + clp.Execute(op, FillRule::EvenOdd, tree, paths); } else { - clp.Execute(op, paths); // Works on closed polygons only. + clp.Execute(op, FillRule::EvenOdd, paths); // Works on closed polygons only. } - // Have to scale points down now. + Vector<Vector<Point2>> polypaths; + for (PathsD::size_type i = 0; i < paths.size(); ++i) { + const PathD &path = paths[i]; - for (Paths::size_type i = 0; i < paths.size(); ++i) { Vector<Vector2> polypath; - - const Path &scaled_path = paths[i]; - - for (Paths::size_type j = 0; j < scaled_path.size(); ++j) { - polypath.push_back(Point2( - static_cast<real_t>(scaled_path[j].X) / (real_t)SCALE_FACTOR, - static_cast<real_t>(scaled_path[j].Y) / (real_t)SCALE_FACTOR)); + for (PathsD::size_type j = 0; j < path.size(); ++j) { + polypath.push_back(Point2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y))); } polypaths.push_back(polypath); } @@ -255,67 +256,61 @@ Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation } Vector<Vector<Point2>> Geometry2D::_polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) { - using namespace ClipperLib; + using namespace Clipper2Lib; - JoinType jt = jtSquare; + JoinType jt = JoinType::Square; switch (p_join_type) { case JOIN_SQUARE: - jt = jtSquare; + jt = JoinType::Square; break; case JOIN_ROUND: - jt = jtRound; + jt = JoinType::Round; break; case JOIN_MITER: - jt = jtMiter; + jt = JoinType::Miter; break; } - EndType et = etClosedPolygon; + EndType et = EndType::Polygon; switch (p_end_type) { case END_POLYGON: - et = etClosedPolygon; + et = EndType::Polygon; break; case END_JOINED: - et = etClosedLine; + et = EndType::Joined; break; case END_BUTT: - et = etOpenButt; + et = EndType::Butt; break; case END_SQUARE: - et = etOpenSquare; + et = EndType::Square; break; case END_ROUND: - et = etOpenRound; + et = EndType::Round; break; } - ClipperOffset co(2.0, 0.25f * (real_t)SCALE_FACTOR); // Defaults from ClipperOffset. - Path path; - // Need to scale points (Clipper's requirement for robust computation). + PathD polypath(p_polypath.size()); for (int i = 0; i != p_polypath.size(); ++i) { - path << IntPoint(p_polypath[i].x * (real_t)SCALE_FACTOR, p_polypath[i].y * (real_t)SCALE_FACTOR); + polypath[i] = PointD(p_polypath[i].x, p_polypath[i].y); } - co.AddPath(path, jt, et); - Paths paths; - co.Execute(paths, p_delta * (real_t)SCALE_FACTOR); // Inflate/deflate. + // Inflate/deflate. + PathsD paths = InflatePaths({ polypath }, p_delta, jt, et, 2.0, PRECISION, 0.0); + // Here the miter_limit = 2.0 and arc_tolerance = 0.0 are Clipper2 defaults, + // and the PRECISION is used to scale points up internally, to attain the desired precision. - // Have to scale points down now. Vector<Vector<Point2>> polypaths; + for (PathsD::size_type i = 0; i < paths.size(); ++i) { + const PathD &path = paths[i]; - for (Paths::size_type i = 0; i < paths.size(); ++i) { - Vector<Vector2> polypath; - - const Path &scaled_path = paths[i]; - - for (Paths::size_type j = 0; j < scaled_path.size(); ++j) { - polypath.push_back(Point2( - static_cast<real_t>(scaled_path[j].X) / (real_t)SCALE_FACTOR, - static_cast<real_t>(scaled_path[j].Y) / (real_t)SCALE_FACTOR)); + Vector<Vector2> polypath2; + for (PathsD::size_type j = 0; j < path.size(); ++j) { + polypath2.push_back(Point2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y))); } - polypaths.push_back(polypath); + polypaths.push_back(polypath2); } return polypaths; } diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 80a2703c2f..7ef1ce74ed 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -182,8 +182,20 @@ public: // Construct a placeholder. Object *obj = native_parent->creation_func(); + + // ClassDB::set_object_extension_instance() won't be called for placeholders. + // We need need to make sure that all the things it would have done (even if + // done in a different way to support placeholders) will also be done here. + obj->_extension = ClassDB::get_placeholder_extension(ti->name); obj->_extension_instance = memnew(PlaceholderExtensionInstance(ti->name)); + +#ifdef TOOLS_ENABLED + if (obj->_extension->track_instance) { + obj->_extension->track_instance(obj->_extension->tracking_userdata, obj); + } +#endif + return obj; } @@ -506,14 +518,7 @@ Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require extension = get_placeholder_extension(ti->name); } #endif - Object *obj = (Object *)extension->create_instance(extension->class_userdata); - -#ifdef TOOLS_ENABLED - if (extension->track_instance) { - extension->track_instance(extension->tracking_userdata, obj); - } -#endif - return obj; + return (Object *)extension->create_instance(extension->class_userdata); } else { #ifdef TOOLS_ENABLED if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) { @@ -638,6 +643,12 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName & p_object->_extension = ti->gdextension; p_object->_extension_instance = p_instance; + +#ifdef TOOLS_ENABLED + if (p_object->_extension->track_instance) { + p_object->_extension->track_instance(p_object->_extension->tracking_userdata, p_object); + } +#endif } bool ClassDB::can_instantiate(const StringName &p_class) { diff --git a/core/object/object.cpp b/core/object/object.cpp index 06f6e8e9e6..f8d2feb5a8 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1100,6 +1100,20 @@ bool Object::_has_user_signal(const StringName &p_name) const { return signal_map[p_name].user.name.length() > 0; } +void Object::_remove_user_signal(const StringName &p_name) { + SignalData *s = signal_map.getptr(p_name); + ERR_FAIL_NULL_MSG(s, "Provided signal does not exist."); + ERR_FAIL_COND_MSG(!s->removable, "Signal is not removable (not added with add_user_signal)."); + for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) { + Object *target = slot_kv.key.get_object(); + if (likely(target)) { + target->connections.erase(slot_kv.value.cE); + } + } + + signal_map.erase(p_name); +} + Error Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (unlikely(p_argcount < 1)) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; @@ -1248,6 +1262,10 @@ void Object::_add_user_signal(const String &p_name, const Array &p_args) { } add_user_signal(mi); + + if (signal_map.has(p_name)) { + signal_map.getptr(p_name)->removable = true; + } } TypedArray<Dictionary> Object::_get_signal_list() const { @@ -1661,6 +1679,7 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("add_user_signal", "signal", "arguments"), &Object::_add_user_signal, DEFVAL(Array())); ClassDB::bind_method(D_METHOD("has_user_signal", "signal"), &Object::_has_user_signal); + ClassDB::bind_method(D_METHOD("remove_user_signal", "signal"), &Object::_remove_user_signal); { MethodInfo mi; diff --git a/core/object/object.h b/core/object/object.h index d9551ecd01..915c3a8c25 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -619,6 +619,7 @@ private: MethodInfo user; HashMap<Callable, Slot, HashableHasher<Callable>> slot_map; + bool removable = false; }; HashMap<StringName, SignalData> signal_map; @@ -646,6 +647,7 @@ private: void _add_user_signal(const String &p_name, const Array &p_args = Array()); bool _has_user_signal(const StringName &p_name) const; + void _remove_user_signal(const StringName &p_name); Error _emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error); TypedArray<Dictionary> _get_signal_list() const; TypedArray<Dictionary> _get_signal_connection_list(const StringName &p_signal) const; diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index 660e13e819..bd3199ca0a 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -51,7 +51,7 @@ void Script::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POSTINITIALIZE: { if (EngineDebugger::is_active()) { - EngineDebugger::get_script_debugger()->set_break_language(get_language()); + callable_mp(this, &Script::_set_debugger_break_language).call_deferred(); } } break; } @@ -103,6 +103,12 @@ Dictionary Script::_get_script_constant_map() { return ret; } +void Script::_set_debugger_break_language() { + if (EngineDebugger::is_active()) { + EngineDebugger::get_script_debugger()->set_break_language(get_language()); + } +} + int Script::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const { MethodInfo mi = get_method_info(p_method); diff --git a/core/object/script_language.h b/core/object/script_language.h index c6c6f3de9f..223f114150 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -124,6 +124,8 @@ protected: TypedArray<Dictionary> _get_script_signal_list(); Dictionary _get_script_constant_map(); + void _set_debugger_break_language(); + public: virtual void reload_from_file() override; diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index e0b8730a67..9c9e0fa899 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -72,6 +72,9 @@ void WorkerThreadPool::_process_task(Task *p_task) { p_task->pool_thread_index = pool_thread_index; prev_task = curr_thread.current_task; curr_thread.current_task = p_task; + if (p_task->pending_notify_yield_over) { + curr_thread.yield_is_over = true; + } task_mutex.unlock(); } #endif @@ -491,7 +494,11 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { ERR_FAIL_MSG("Invalid Task ID."); } Task *task = *taskp; - if (task->completed) { + if (task->pool_thread_index == -1) { // Completed or not started yet. + if (!task->completed) { + // This avoids a race condition where a task is created and yield-over called before it's processed. + task->pending_notify_yield_over = true; + } task_mutex.unlock(); return; } diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h index 64f24df79f..a9cf260a0f 100644 --- a/core/object/worker_thread_pool.h +++ b/core/object/worker_thread_pool.h @@ -81,7 +81,8 @@ private: void *native_func_userdata = nullptr; String description; Semaphore done_semaphore; // For user threads awaiting. - bool completed = false; + bool completed : 1; + bool pending_notify_yield_over : 1; Group *group = nullptr; SelfList<Task> task_elem; uint32_t waiting_pool = 0; @@ -92,6 +93,8 @@ private: void free_template_userdata(); Task() : + completed(false), + pending_notify_yield_over(false), task_elem(this) {} }; diff --git a/core/os/os.h b/core/os/os.h index 06be0e2b41..069a3876af 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -153,7 +153,14 @@ public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); - virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) { return ERR_UNAVAILABLE; } + struct GDExtensionData { + bool also_set_library_path = false; + String *r_resolved_path = nullptr; + bool generate_temp_files = false; + PackedStringArray *library_dependencies = nullptr; + }; + + virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) { return ERR_UNAVAILABLE; } virtual Error close_dynamic_library(void *p_library_handle) { return ERR_UNAVAILABLE; } virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) { return ERR_UNAVAILABLE; } diff --git a/editor/export/editor_export_shared_object.h b/core/os/shared_object.h index 54e1314a42..88423bed13 100644 --- a/editor/export/editor_export_shared_object.h +++ b/core/os/shared_object.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* editor_export_shared_object.h */ +/* shared_object.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef EDITOR_EXPORT_SHARED_OBJECT_H -#define EDITOR_EXPORT_SHARED_OBJECT_H +#ifndef SHARED_OBJECT_H +#define SHARED_OBJECT_H #include "core/string/ustring.h" #include "core/templates/vector.h" @@ -48,4 +48,4 @@ struct SharedObject { SharedObject() {} }; -#endif // EDITOR_EXPORT_SHARED_OBJECT_H +#endif // SHARED_OBJECT_H diff --git a/core/string/char_utils.h b/core/string/char_utils.h index aa9bc198ca..fc2fbb95a1 100644 --- a/core/string/char_utils.h +++ b/core/string/char_utils.h @@ -92,7 +92,7 @@ static _FORCE_INLINE_ bool is_binary_digit(char32_t c) { return (c == '0' || c == '1'); } -static _FORCE_INLINE_ bool is_ascii_char(char32_t c) { +static _FORCE_INLINE_ bool is_ascii_alphabet_char(char32_t c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } diff --git a/core/string/translation.cpp b/core/string/translation.cpp index 613edd11cd..344fe42fa0 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -993,7 +993,7 @@ String TranslationServer::add_padding(const String &p_message, int p_length) con } const char32_t *TranslationServer::get_accented_version(char32_t p_character) const { - if (!is_ascii_char(p_character)) { + if (!is_ascii_alphabet_char(p_character)) { return nullptr; } diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 155a5b2781..8e702ce8bb 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -2373,184 +2373,183 @@ Variant::operator IPAddress() const { return IPAddress(operator String()); } -Variant::Variant(bool p_bool) { - type = BOOL; +Variant::Variant(bool p_bool) : + type(BOOL) { _data._bool = p_bool; } -Variant::Variant(int64_t p_int64) { - type = INT; +Variant::Variant(int64_t p_int64) : + type(INT) { _data._int = p_int64; } -Variant::Variant(int32_t p_int32) { - type = INT; +Variant::Variant(int32_t p_int32) : + type(INT) { _data._int = p_int32; } -Variant::Variant(int16_t p_int16) { - type = INT; +Variant::Variant(int16_t p_int16) : + type(INT) { _data._int = p_int16; } -Variant::Variant(int8_t p_int8) { - type = INT; +Variant::Variant(int8_t p_int8) : + type(INT) { _data._int = p_int8; } -Variant::Variant(uint64_t p_uint64) { - type = INT; +Variant::Variant(uint64_t p_uint64) : + type(INT) { _data._int = p_uint64; } -Variant::Variant(uint32_t p_uint32) { - type = INT; +Variant::Variant(uint32_t p_uint32) : + type(INT) { _data._int = p_uint32; } -Variant::Variant(uint16_t p_uint16) { - type = INT; +Variant::Variant(uint16_t p_uint16) : + type(INT) { _data._int = p_uint16; } -Variant::Variant(uint8_t p_uint8) { - type = INT; +Variant::Variant(uint8_t p_uint8) : + type(INT) { _data._int = p_uint8; } -Variant::Variant(float p_float) { - type = FLOAT; +Variant::Variant(float p_float) : + type(FLOAT) { _data._float = p_float; } -Variant::Variant(double p_double) { - type = FLOAT; +Variant::Variant(double p_double) : + type(FLOAT) { _data._float = p_double; } -Variant::Variant(const ObjectID &p_id) { - type = INT; +Variant::Variant(const ObjectID &p_id) : + type(INT) { _data._int = p_id; } -Variant::Variant(const StringName &p_string) { - type = STRING_NAME; +Variant::Variant(const StringName &p_string) : + type(STRING_NAME) { memnew_placement(_data._mem, StringName(p_string)); } -Variant::Variant(const String &p_string) { - type = STRING; +Variant::Variant(const String &p_string) : + type(STRING) { memnew_placement(_data._mem, String(p_string)); } -Variant::Variant(const char *const p_cstring) { - type = STRING; +Variant::Variant(const char *const p_cstring) : + type(STRING) { memnew_placement(_data._mem, String((const char *)p_cstring)); } -Variant::Variant(const char32_t *p_wstring) { - type = STRING; +Variant::Variant(const char32_t *p_wstring) : + type(STRING) { memnew_placement(_data._mem, String(p_wstring)); } -Variant::Variant(const Vector3 &p_vector3) { - type = VECTOR3; +Variant::Variant(const Vector3 &p_vector3) : + type(VECTOR3) { memnew_placement(_data._mem, Vector3(p_vector3)); } -Variant::Variant(const Vector3i &p_vector3i) { - type = VECTOR3I; +Variant::Variant(const Vector3i &p_vector3i) : + type(VECTOR3I) { memnew_placement(_data._mem, Vector3i(p_vector3i)); } -Variant::Variant(const Vector4 &p_vector4) { - type = VECTOR4; +Variant::Variant(const Vector4 &p_vector4) : + type(VECTOR4) { memnew_placement(_data._mem, Vector4(p_vector4)); } -Variant::Variant(const Vector4i &p_vector4i) { - type = VECTOR4I; +Variant::Variant(const Vector4i &p_vector4i) : + type(VECTOR4I) { memnew_placement(_data._mem, Vector4i(p_vector4i)); } -Variant::Variant(const Vector2 &p_vector2) { - type = VECTOR2; +Variant::Variant(const Vector2 &p_vector2) : + type(VECTOR2) { memnew_placement(_data._mem, Vector2(p_vector2)); } -Variant::Variant(const Vector2i &p_vector2i) { - type = VECTOR2I; +Variant::Variant(const Vector2i &p_vector2i) : + type(VECTOR2I) { memnew_placement(_data._mem, Vector2i(p_vector2i)); } -Variant::Variant(const Rect2 &p_rect2) { - type = RECT2; +Variant::Variant(const Rect2 &p_rect2) : + type(RECT2) { memnew_placement(_data._mem, Rect2(p_rect2)); } -Variant::Variant(const Rect2i &p_rect2i) { - type = RECT2I; +Variant::Variant(const Rect2i &p_rect2i) : + type(RECT2I) { memnew_placement(_data._mem, Rect2i(p_rect2i)); } -Variant::Variant(const Plane &p_plane) { - type = PLANE; +Variant::Variant(const Plane &p_plane) : + type(PLANE) { memnew_placement(_data._mem, Plane(p_plane)); } -Variant::Variant(const ::AABB &p_aabb) { - type = AABB; +Variant::Variant(const ::AABB &p_aabb) : + type(AABB) { _data._aabb = (::AABB *)Pools::_bucket_small.alloc(); memnew_placement(_data._aabb, ::AABB(p_aabb)); } -Variant::Variant(const Basis &p_matrix) { - type = BASIS; +Variant::Variant(const Basis &p_matrix) : + type(BASIS) { _data._basis = (Basis *)Pools::_bucket_medium.alloc(); memnew_placement(_data._basis, Basis(p_matrix)); } -Variant::Variant(const Quaternion &p_quaternion) { - type = QUATERNION; +Variant::Variant(const Quaternion &p_quaternion) : + type(QUATERNION) { memnew_placement(_data._mem, Quaternion(p_quaternion)); } -Variant::Variant(const Transform3D &p_transform) { - type = TRANSFORM3D; +Variant::Variant(const Transform3D &p_transform) : + type(TRANSFORM3D) { _data._transform3d = (Transform3D *)Pools::_bucket_medium.alloc(); memnew_placement(_data._transform3d, Transform3D(p_transform)); } -Variant::Variant(const Projection &pp_projection) { - type = PROJECTION; +Variant::Variant(const Projection &pp_projection) : + type(PROJECTION) { _data._projection = (Projection *)Pools::_bucket_large.alloc(); memnew_placement(_data._projection, Projection(pp_projection)); } -Variant::Variant(const Transform2D &p_transform) { - type = TRANSFORM2D; +Variant::Variant(const Transform2D &p_transform) : + type(TRANSFORM2D) { _data._transform2d = (Transform2D *)Pools::_bucket_small.alloc(); memnew_placement(_data._transform2d, Transform2D(p_transform)); } -Variant::Variant(const Color &p_color) { - type = COLOR; +Variant::Variant(const Color &p_color) : + type(COLOR) { memnew_placement(_data._mem, Color(p_color)); } -Variant::Variant(const NodePath &p_node_path) { - type = NODE_PATH; +Variant::Variant(const NodePath &p_node_path) : + type(NODE_PATH) { memnew_placement(_data._mem, NodePath(p_node_path)); } -Variant::Variant(const ::RID &p_rid) { - type = RID; +Variant::Variant(const ::RID &p_rid) : + type(RID) { memnew_placement(_data._mem, ::RID(p_rid)); } -Variant::Variant(const Object *p_object) { - type = OBJECT; - +Variant::Variant(const Object *p_object) : + type(OBJECT) { memnew_placement(_data._mem, ObjData); if (p_object) { @@ -2571,76 +2570,74 @@ Variant::Variant(const Object *p_object) { } } -Variant::Variant(const Callable &p_callable) { - type = CALLABLE; +Variant::Variant(const Callable &p_callable) : + type(CALLABLE) { memnew_placement(_data._mem, Callable(p_callable)); } -Variant::Variant(const Signal &p_callable) { - type = SIGNAL; +Variant::Variant(const Signal &p_callable) : + type(SIGNAL) { memnew_placement(_data._mem, Signal(p_callable)); } -Variant::Variant(const Dictionary &p_dictionary) { - type = DICTIONARY; +Variant::Variant(const Dictionary &p_dictionary) : + type(DICTIONARY) { memnew_placement(_data._mem, Dictionary(p_dictionary)); } -Variant::Variant(const Array &p_array) { - type = ARRAY; +Variant::Variant(const Array &p_array) : + type(ARRAY) { memnew_placement(_data._mem, Array(p_array)); } -Variant::Variant(const PackedByteArray &p_byte_array) { - type = PACKED_BYTE_ARRAY; - +Variant::Variant(const PackedByteArray &p_byte_array) : + type(PACKED_BYTE_ARRAY) { _data.packed_array = PackedArrayRef<uint8_t>::create(p_byte_array); } -Variant::Variant(const PackedInt32Array &p_int32_array) { - type = PACKED_INT32_ARRAY; +Variant::Variant(const PackedInt32Array &p_int32_array) : + type(PACKED_INT32_ARRAY) { _data.packed_array = PackedArrayRef<int32_t>::create(p_int32_array); } -Variant::Variant(const PackedInt64Array &p_int64_array) { - type = PACKED_INT64_ARRAY; +Variant::Variant(const PackedInt64Array &p_int64_array) : + type(PACKED_INT64_ARRAY) { _data.packed_array = PackedArrayRef<int64_t>::create(p_int64_array); } -Variant::Variant(const PackedFloat32Array &p_float32_array) { - type = PACKED_FLOAT32_ARRAY; +Variant::Variant(const PackedFloat32Array &p_float32_array) : + type(PACKED_FLOAT32_ARRAY) { _data.packed_array = PackedArrayRef<float>::create(p_float32_array); } -Variant::Variant(const PackedFloat64Array &p_float64_array) { - type = PACKED_FLOAT64_ARRAY; +Variant::Variant(const PackedFloat64Array &p_float64_array) : + type(PACKED_FLOAT64_ARRAY) { _data.packed_array = PackedArrayRef<double>::create(p_float64_array); } -Variant::Variant(const PackedStringArray &p_string_array) { - type = PACKED_STRING_ARRAY; +Variant::Variant(const PackedStringArray &p_string_array) : + type(PACKED_STRING_ARRAY) { _data.packed_array = PackedArrayRef<String>::create(p_string_array); } -Variant::Variant(const PackedVector2Array &p_vector2_array) { - type = PACKED_VECTOR2_ARRAY; +Variant::Variant(const PackedVector2Array &p_vector2_array) : + type(PACKED_VECTOR2_ARRAY) { _data.packed_array = PackedArrayRef<Vector2>::create(p_vector2_array); } -Variant::Variant(const PackedVector3Array &p_vector3_array) { - type = PACKED_VECTOR3_ARRAY; +Variant::Variant(const PackedVector3Array &p_vector3_array) : + type(PACKED_VECTOR3_ARRAY) { _data.packed_array = PackedArrayRef<Vector3>::create(p_vector3_array); } -Variant::Variant(const PackedColorArray &p_color_array) { - type = PACKED_COLOR_ARRAY; +Variant::Variant(const PackedColorArray &p_color_array) : + type(PACKED_COLOR_ARRAY) { _data.packed_array = PackedArrayRef<Color>::create(p_color_array); } /* helpers */ -Variant::Variant(const Vector<::RID> &p_array) { - type = ARRAY; - +Variant::Variant(const Vector<::RID> &p_array) : + type(ARRAY) { Array *rid_array = memnew_placement(_data._mem, Array); rid_array->resize(p_array.size()); @@ -2650,9 +2647,8 @@ Variant::Variant(const Vector<::RID> &p_array) { } } -Variant::Variant(const Vector<Plane> &p_array) { - type = ARRAY; - +Variant::Variant(const Vector<Plane> &p_array) : + type(ARRAY) { Array *plane_array = memnew_placement(_data._mem, Array); plane_array->resize(p_array.size()); @@ -2662,7 +2658,8 @@ Variant::Variant(const Vector<Plane> &p_array) { } } -Variant::Variant(const Vector<Face3> &p_face_array) { +Variant::Variant(const Vector<Face3> &p_face_array) : + type(NIL) { PackedVector3Array vertices; int face_count = p_face_array.size(); vertices.resize(face_count * 3); @@ -2678,13 +2675,11 @@ Variant::Variant(const Vector<Face3> &p_face_array) { } } - type = NIL; - *this = vertices; } -Variant::Variant(const Vector<Variant> &p_array) { - type = NIL; +Variant::Variant(const Vector<Variant> &p_array) : + type(NIL) { Array arr; arr.resize(p_array.size()); for (int i = 0; i < p_array.size(); i++) { @@ -2693,8 +2688,8 @@ Variant::Variant(const Vector<Variant> &p_array) { *this = arr; } -Variant::Variant(const Vector<StringName> &p_array) { - type = NIL; +Variant::Variant(const Vector<StringName> &p_array) : + type(NIL) { PackedStringArray v; int len = p_array.size(); v.resize(len); @@ -2863,12 +2858,13 @@ void Variant::operator=(const Variant &p_variant) { } } -Variant::Variant(const IPAddress &p_address) { - type = STRING; +Variant::Variant(const IPAddress &p_address) : + type(STRING) { memnew_placement(_data._mem, String(p_address)); } -Variant::Variant(const Variant &p_variant) { +Variant::Variant(const Variant &p_variant) : + type(NIL) { reference(p_variant); } diff --git a/core/variant/variant.h b/core/variant/variant.h index 10f8dc3c7f..e40df3171f 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -165,7 +165,7 @@ private: // Variant takes 20 bytes when real_t is float, and 36 if double // it only allocates extra memory for aabb/matrix. - Type type = NIL; + Type type; struct ObjData { ObjectID id; @@ -483,8 +483,8 @@ public: Variant(const IPAddress &p_address); #define VARIANT_ENUM_CLASS_CONSTRUCTOR(m_enum) \ - Variant(m_enum p_value) { \ - type = INT; \ + Variant(m_enum p_value) : \ + type(INT) { \ _data._int = (int64_t)p_value; \ } @@ -788,7 +788,8 @@ public: static void unregister_types(); Variant(const Variant &p_variant); - _FORCE_INLINE_ Variant() {} + _FORCE_INLINE_ Variant() : + type(NIL) {} _FORCE_INLINE_ ~Variant() { clear(); } diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index dcb94b16b1..50f8007efa 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -489,11 +489,11 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri r_token.value = num.as_int(); } return OK; - } else if (is_ascii_char(cchar) || is_underscore(cchar)) { + } else if (is_ascii_alphabet_char(cchar) || is_underscore(cchar)) { StringBuffer<> id; bool first = true; - while (is_ascii_char(cchar) || is_underscore(cchar) || (!first && is_digit(cchar))) { + while (is_ascii_alphabet_char(cchar) || is_underscore(cchar) || (!first && is_digit(cchar))) { id += cchar; cchar = p_stream->get_char(); first = false; @@ -1789,7 +1789,7 @@ static String rtos_fix(double p_value) { } } -Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_string_func, void *p_store_string_ud, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud, int p_recursion_count) { +Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_string_func, void *p_store_string_ud, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud, int p_recursion_count, bool p_compat) { switch (p_variant.get_type()) { case Variant::NIL: { p_store_string_func(p_store_string_ud, "null"); @@ -2009,7 +2009,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str } p_store_string_func(p_store_string_ud, "\"" + E.name + "\":"); - write(obj->get(E.name), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count); + write(obj->get(E.name), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); } } @@ -2035,9 +2035,9 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str p_store_string_func(p_store_string_ud, "{\n"); for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count); + write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); p_store_string_func(p_store_string_ud, ": "); - write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count); + write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); if (E->next()) { p_store_string_func(p_store_string_ud, ",\n"); } else { @@ -2096,7 +2096,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str } else { p_store_string_func(p_store_string_ud, ", "); } - write(var, p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count); + write(var, p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); } p_store_string_func(p_store_string_ud, "]"); @@ -2110,7 +2110,16 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str case Variant::PACKED_BYTE_ARRAY: { p_store_string_func(p_store_string_ud, "PackedByteArray("); Vector<uint8_t> data = p_variant; - if (data.size() > 0) { + if (p_compat) { + int len = data.size(); + const uint8_t *ptr = data.ptr(); + for (int i = 0; i < len; i++) { + if (i > 0) { + p_store_string_func(p_store_string_ud, ", "); + } + p_store_string_func(p_store_string_ud, itos(ptr[i])); + } + } else if (data.size() > 0) { p_store_string_func(p_store_string_ud, "\""); p_store_string_func(p_store_string_ud, CryptoCore::b64_encode_str(data.ptr(), data.size())); p_store_string_func(p_store_string_ud, "\""); @@ -2255,8 +2264,8 @@ static Error _write_to_str(void *ud, const String &p_string) { return OK; } -Error VariantWriter::write_to_string(const Variant &p_variant, String &r_string, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud) { +Error VariantWriter::write_to_string(const Variant &p_variant, String &r_string, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud, bool p_compat) { r_string = String(); - return write(p_variant, _write_to_str, &r_string, p_encode_res_func, p_encode_res_ud); + return write(p_variant, _write_to_str, &r_string, p_encode_res_func, p_encode_res_ud, 0, p_compat); } diff --git a/core/variant/variant_parser.h b/core/variant/variant_parser.h index 2f8974849f..b0ac07170d 100644 --- a/core/variant/variant_parser.h +++ b/core/variant/variant_parser.h @@ -161,8 +161,8 @@ public: typedef Error (*StoreStringFunc)(void *ud, const String &p_string); typedef String (*EncodeResourceFunc)(void *ud, const Ref<Resource> &p_resource); - static Error write(const Variant &p_variant, StoreStringFunc p_store_string_func, void *p_store_string_ud, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud, int p_recursion_count = 0); - static Error write_to_string(const Variant &p_variant, String &r_string, EncodeResourceFunc p_encode_res_func = nullptr, void *p_encode_res_ud = nullptr); + static Error write(const Variant &p_variant, StoreStringFunc p_store_string_func, void *p_store_string_ud, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud, int p_recursion_count = 0, bool p_compat = true); + static Error write_to_string(const Variant &p_variant, String &r_string, EncodeResourceFunc p_encode_res_func = nullptr, void *p_encode_res_ud = nullptr, bool p_compat = true); }; #endif // VARIANT_PARSER_H diff --git a/doc/classes/AcceptDialog.xml b/doc/classes/AcceptDialog.xml index 7fbdd4272d..392364425b 100644 --- a/doc/classes/AcceptDialog.xml +++ b/doc/classes/AcceptDialog.xml @@ -100,6 +100,12 @@ </signal> </signals> <theme_items> + <theme_item name="buttons_min_height" data_type="constant" type="int" default="0"> + The minimum height of each button in the bottom row (such as OK/Cancel) in pixels. This can be increased to make buttons with short texts easier to click/tap. + </theme_item> + <theme_item name="buttons_min_width" data_type="constant" type="int" default="0"> + The minimum width of each button in the bottom row (such as OK/Cancel) in pixels. This can be increased to make buttons with short texts easier to click/tap. + </theme_item> <theme_item name="buttons_separation" data_type="constant" type="int" default="10"> The size of the vertical space between the dialog's content and the button row. </theme_item> diff --git a/doc/classes/AnimationNode.xml b/doc/classes/AnimationNode.xml index 960bbe68ad..4e093cb887 100644 --- a/doc/classes/AnimationNode.xml +++ b/doc/classes/AnimationNode.xml @@ -7,7 +7,7 @@ Base resource for [AnimationTree] nodes. In general, it's not used directly, but you can create custom ones with custom blending formulas. Inherit this when creating animation nodes mainly for use in [AnimationNodeBlendTree], otherwise [AnimationRootNode] should be used instead. You can access the time information as read-only parameter which is processed and stored in the previous frame for all nodes except [AnimationNodeOutput]. - [b]Note:[/b] If more than two inputs exist in the [AnimationNode], which time information takes precedence depends on the type of [AnimationNode]. + [b]Note:[/b] If multiple inputs exist in the [AnimationNode], which time information takes precedence depends on the type of [AnimationNode]. [codeblock] var current_length = $AnimationTree[parameters/AnimationNodeName/current_length] var current_position = $AnimationTree[parameters/AnimationNodeName/current_position] diff --git a/doc/classes/AnimationNodeSync.xml b/doc/classes/AnimationNodeSync.xml index e9e1519672..57c79378c0 100644 --- a/doc/classes/AnimationNodeSync.xml +++ b/doc/classes/AnimationNodeSync.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="AnimationNodeSync" inherits="AnimationNode" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Base class for [AnimationNode]s with more than two input ports that must be synchronized. + Base class for [AnimationNode]s with multiple input ports that must be synchronized. </brief_description> <description> An animation node used to combine, mix, or blend two or more animations together while keeping them synchronized within an [AnimationTree]. diff --git a/doc/classes/ClassDB.xml b/doc/classes/ClassDB.xml index 3f71406091..59ed0b8fe7 100644 --- a/doc/classes/ClassDB.xml +++ b/doc/classes/ClassDB.xml @@ -91,6 +91,14 @@ Returns the value of [param property] of [param object] or its ancestry. </description> </method> + <method name="class_get_property_default_value" qualifiers="const"> + <return type="Variant" /> + <param index="0" name="class" type="StringName" /> + <param index="1" name="property" type="StringName" /> + <description> + Returns the default value of [param property] of [param class] or its ancestor classes. + </description> + </method> <method name="class_get_property_list" qualifiers="const"> <return type="Dictionary[]" /> <param index="0" name="class" type="StringName" /> @@ -192,6 +200,15 @@ Returns whether this [param class] is enabled or not. </description> </method> + <method name="is_class_enum_bitfield" qualifiers="const"> + <return type="bool" /> + <param index="0" name="class" type="StringName" /> + <param index="1" name="enum" type="StringName" /> + <param index="2" name="no_inheritance" type="bool" default="false" /> + <description> + Returns whether [param class] (or its ancestor classes if [param no_inheritance] is [code]false[/code]) has an enum called [param enum] that is a bitfield. + </description> + </method> <method name="is_parent_class" qualifiers="const"> <return type="bool" /> <param index="0" name="class" type="StringName" /> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 20ee65403c..93a7b09fce 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -384,6 +384,9 @@ <member name="editors/3d/selection_box_color" type="Color" setter="" getter=""> The color to use for the selection box that surrounds selected nodes in the 3D editor viewport. The color's alpha channel influences the selection box's opacity. </member> + <member name="editors/3d_gizmos/gizmo_colors/aabb" type="Color" setter="" getter=""> + The color to use for the AABB gizmo that displays the [GeometryInstance3D]'s custom [AABB]. + </member> <member name="editors/3d_gizmos/gizmo_colors/instantiated" type="Color" setter="" getter=""> The color override to use for 3D editor gizmos if the [Node3D] in question is part of an instantiated scene file (from the perspective of the current scene). </member> diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml index 4052e48400..fafc02734a 100644 --- a/doc/classes/FileAccess.xml +++ b/doc/classes/FileAccess.xml @@ -324,6 +324,13 @@ Returns [code]null[/code] if opening the file failed. You can use [method get_open_error] to check the error that occurred. </description> </method> + <method name="resize"> + <return type="int" enum="Error" /> + <param index="0" name="length" type="int" /> + <description> + Resizes the file to a specified length. The file must be open in a mode that permits writing. If the file is extended, NUL characters are appended. If the file is truncated, all data from the end file to the original length of the file is lost. + </description> + </method> <method name="seek"> <return type="void" /> <param index="0" name="position" type="int" /> diff --git a/doc/classes/FileSystemDock.xml b/doc/classes/FileSystemDock.xml index 9028dd4b9f..b3dc51ffaa 100644 --- a/doc/classes/FileSystemDock.xml +++ b/doc/classes/FileSystemDock.xml @@ -51,6 +51,11 @@ Emitted when a file is moved from [param old_file] path to [param new_file] path. </description> </signal> + <signal name="folder_color_changed"> + <description> + Emitted when folders change color. + </description> + </signal> <signal name="folder_moved"> <param index="0" name="old_folder" type="String" /> <param index="1" name="new_folder" type="String" /> diff --git a/doc/classes/MeshInstance3D.xml b/doc/classes/MeshInstance3D.xml index e5187cf7a1..abbb4c4eeb 100644 --- a/doc/classes/MeshInstance3D.xml +++ b/doc/classes/MeshInstance3D.xml @@ -13,6 +13,14 @@ <link title="Third Person Shooter (TPS) Demo">https://godotengine.org/asset-library/asset/2710</link> </tutorials> <methods> + <method name="bake_mesh_from_current_blend_shape_mix"> + <return type="ArrayMesh" /> + <param index="0" name="existing" type="ArrayMesh" default="null" /> + <description> + Takes a snapshot from the current [ArrayMesh] with all blend shapes applied according to their current weights and bakes it to the provided [param existing] mesh. If no [param existing] mesh is provided a new [ArrayMesh] is created, baked and returned. Mesh surface materials are not copied. + [b]Performance:[/b] [Mesh] data needs to be received from the GPU, stalling the [RenderingServer] in the process. + </description> + </method> <method name="create_convex_collision"> <return type="void" /> <param index="0" name="clean" type="bool" default="true" /> diff --git a/doc/classes/NavigationMeshSourceGeometryData2D.xml b/doc/classes/NavigationMeshSourceGeometryData2D.xml index 609877fadc..1d8689420b 100644 --- a/doc/classes/NavigationMeshSourceGeometryData2D.xml +++ b/doc/classes/NavigationMeshSourceGeometryData2D.xml @@ -31,6 +31,20 @@ Adds the outline points of a shape as traversable area. </description> </method> + <method name="append_obstruction_outlines"> + <return type="void" /> + <param index="0" name="obstruction_outlines" type="PackedVector2Array[]" /> + <description> + Appends another array of [param obstruction_outlines] at the end of the existing obstruction outlines array. + </description> + </method> + <method name="append_traversable_outlines"> + <return type="void" /> + <param index="0" name="traversable_outlines" type="PackedVector2Array[]" /> + <description> + Appends another array of [param traversable_outlines] at the end of the existing traversable outlines array. + </description> + </method> <method name="clear"> <return type="void" /> <description> diff --git a/doc/classes/NavigationMeshSourceGeometryData3D.xml b/doc/classes/NavigationMeshSourceGeometryData3D.xml index 322e2e7c66..0b3126a63b 100644 --- a/doc/classes/NavigationMeshSourceGeometryData3D.xml +++ b/doc/classes/NavigationMeshSourceGeometryData3D.xml @@ -43,6 +43,14 @@ Adds a projected obstruction shape to the source geometry. The [param vertices] are considered projected on a xz-axes plane, placed at the global y-axis [param elevation] and extruded by [param height]. If [param carve] is [code]true[/code] the carved shape will not be affected by additional offsets (e.g. agent radius) of the navigation mesh baking process. </description> </method> + <method name="append_arrays"> + <return type="void" /> + <param index="0" name="vertices" type="PackedFloat32Array" /> + <param index="1" name="indices" type="PackedInt32Array" /> + <description> + Appends arrays of [param vertices] and [param indices] at the end of the existing arrays. Adds the existing index as an offset to the appended indices. + </description> + </method> <method name="clear"> <return type="void" /> <description> diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml index a6f6abccbd..a0d03d7a01 100644 --- a/doc/classes/NavigationServer2D.xml +++ b/doc/classes/NavigationServer2D.xml @@ -956,6 +956,23 @@ Path simplification can be helpful to mitigate various path following issues that can arise with certain agent types and script behaviors. E.g. "steering" agents or avoidance in "open fields". </description> </method> + <method name="source_geometry_parser_create"> + <return type="RID" /> + <description> + Creates a new source geometry parser. If a [Callable] is set for the parser with [method source_geometry_parser_set_callback] the callback will be called for every single node that gets parsed whenever [method parse_source_geometry_data] is used. + </description> + </method> + <method name="source_geometry_parser_set_callback"> + <return type="void" /> + <param index="0" name="parser" type="RID" /> + <param index="1" name="callback" type="Callable" /> + <description> + Sets the [param callback] [Callable] for the specific source geometry [param parser]. The [Callable] will receive a call with the following parameters: + - [code]navigation_mesh[/code] - The [NavigationPolygon] reference used to define the parse settings. Do NOT edit or add directly to the navigation mesh. + - [code]source_geometry_data[/code] - The [NavigationMeshSourceGeometryData2D] reference. Add custom source geometry for navigation mesh baking to this object. + - [code]node[/code] - The [Node] that is parsed. + </description> + </method> </methods> <signals> <signal name="map_changed"> diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml index 6fcf033544..ff3e6fd8a6 100644 --- a/doc/classes/NavigationServer3D.xml +++ b/doc/classes/NavigationServer3D.xml @@ -1103,6 +1103,23 @@ Path simplification can be helpful to mitigate various path following issues that can arise with certain agent types and script behaviors. E.g. "steering" agents or avoidance in "open fields". </description> </method> + <method name="source_geometry_parser_create"> + <return type="RID" /> + <description> + Creates a new source geometry parser. If a [Callable] is set for the parser with [method source_geometry_parser_set_callback] the callback will be called for every single node that gets parsed whenever [method parse_source_geometry_data] is used. + </description> + </method> + <method name="source_geometry_parser_set_callback"> + <return type="void" /> + <param index="0" name="parser" type="RID" /> + <param index="1" name="callback" type="Callable" /> + <description> + Sets the [param callback] [Callable] for the specific source geometry [param parser]. The [Callable] will receive a call with the following parameters: + - [code]navigation_mesh[/code] - The [NavigationMesh] reference used to define the parse settings. Do NOT edit or add directly to the navigation mesh. + - [code]source_geometry_data[/code] - The [NavigationMeshSourceGeometryData3D] reference. Add custom source geometry for navigation mesh baking to this object. + - [code]node[/code] - The [Node] that is parsed. + </description> + </method> </methods> <signals> <signal name="avoidance_debug_changed"> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index a130b06dba..7c69bc6ed2 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -189,26 +189,26 @@ for argument in OS.get_cmdline_args(): if argument.contains("="): var key_value = argument.split("=") - arguments[key_value[0].lstrip("--")] = key_value[1] + arguments[key_value[0].trim_prefix("--")] = key_value[1] else: # Options without an argument will be present in the dictionary, # with the value set to an empty string. - arguments[argument.lstrip("--")] = "" + arguments[argument.trim_prefix("--")] = "" [/gdscript] [csharp] - var arguments = new Godot.Collections.Dictionary(); + var arguments = new Dictionary<string, string>(); foreach (var argument in OS.GetCmdlineArgs()) { if (argument.Contains('=')) { string[] keyValue = argument.Split("="); - arguments[keyValue[0].LStrip("--")] = keyValue[1]; + arguments[keyValue[0].TrimPrefix("--")] = keyValue[1]; } else { // Options without an argument will be present in the dictionary, // with the value set to an empty string. - arguments[keyValue[0].LStrip("--")] = ""; + arguments[argument.TrimPrefix("--")] = ""; } } [/csharp] diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index c508591093..68d2d6411c 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -357,7 +357,7 @@ <param index="0" name="signal" type="String" /> <param index="1" name="arguments" type="Array" default="[]" /> <description> - Adds a user-defined [param signal]. Optional arguments for the signal can be added as an [Array] of dictionaries, each defining a [code]name[/code] [String] and a [code]type[/code] [int] (see [enum Variant.Type]). See also [method has_user_signal]. + Adds a user-defined [param signal]. Optional arguments for the signal can be added as an [Array] of dictionaries, each defining a [code]name[/code] [String] and a [code]type[/code] [int] (see [enum Variant.Type]). See also [method has_user_signal] and [method remove_user_signal]. [codeblocks] [gdscript] add_user_signal("hurt", [ @@ -797,7 +797,7 @@ <return type="bool" /> <param index="0" name="signal" type="StringName" /> <description> - Returns [code]true[/code] if the given user-defined [param signal] name exists. Only signals added with [method add_user_signal] are included. + Returns [code]true[/code] if the given user-defined [param signal] name exists. Only signals added with [method add_user_signal] are included. See also [method remove_user_signal]. </description> </method> <method name="is_blocking_signals" qualifiers="const"> @@ -905,6 +905,13 @@ [b]Note:[/b] Metadata that has a name starting with an underscore ([code]_[/code]) is considered editor-only. Editor-only metadata is not displayed in the Inspector and should not be edited, although it can still be found by this method. </description> </method> + <method name="remove_user_signal"> + <return type="void" /> + <param index="0" name="signal" type="StringName" /> + <description> + Removes the given user signal [param signal] from the object. See also [method add_user_signal] and [method has_user_signal]. + </description> + </method> <method name="set"> <return type="void" /> <param index="0" name="property" type="StringName" /> diff --git a/doc/classes/SkeletonIK3D.xml b/doc/classes/SkeletonIK3D.xml index 6de6d9d186..4858a6ce22 100644 --- a/doc/classes/SkeletonIK3D.xml +++ b/doc/classes/SkeletonIK3D.xml @@ -55,6 +55,9 @@ </method> </methods> <members> + <member name="interpolation" type="float" setter="set_interpolation" getter="get_interpolation" deprecated="Use [member SkeletonModifier3D.influence] instead."> + Interpolation value for how much the IK results are applied to the current skeleton bone chain. A value of [code]1.0[/code] will overwrite all skeleton bone transforms completely while a value of [code]0.0[/code] will visually disable the SkeletonIK. + </member> <member name="magnet" type="Vector3" setter="set_magnet_position" getter="get_magnet_position" default="Vector3(0, 0, 0)"> Secondary target position (first is [member target] property or [member target_node]) for the IK chain. Use magnet position (pole target) to control the bending of the IK chain. Only works if the bone chain has more than 2 bones. The middle chain bone position will be linearly interpolated with the magnet position. </member> diff --git a/doc/classes/SurfaceTool.xml b/doc/classes/SurfaceTool.xml index 576587a5df..a8bd068b1c 100644 --- a/doc/classes/SurfaceTool.xml +++ b/doc/classes/SurfaceTool.xml @@ -93,7 +93,7 @@ <method name="commit_to_arrays"> <return type="Array" /> <description> - Commits the data to the same format used by [method ArrayMesh.add_surface_from_arrays]. This way you can further process the mesh data using the [ArrayMesh] API. + Commits the data to the same format used by [method ArrayMesh.add_surface_from_arrays], [method ImporterMesh.add_surface], and [method create_from_arrays]. This way you can further process the mesh data using the [ArrayMesh] or [ImporterMesh] APIs. </description> </method> <method name="create_from"> @@ -104,6 +104,14 @@ Creates a vertex array from an existing [Mesh]. </description> </method> + <method name="create_from_arrays"> + <return type="void" /> + <param index="0" name="arrays" type="Array" /> + <param index="1" name="primitive_type" type="int" enum="Mesh.PrimitiveType" default="3" /> + <description> + Creates this SurfaceTool from existing vertex arrays such as returned by [method commit_to_arrays], [method Mesh.surface_get_arrays], [method Mesh.surface_get_blend_shape_arrays], [method ImporterMesh.get_surface_arrays], and [method ImporterMesh.get_surface_blend_shape_arrays]. [param primitive_type] controls the type of mesh data, defaulting to [constant Mesh.PRIMITIVE_TRIANGLES]. + </description> + </method> <method name="create_from_blend_shape"> <return type="void" /> <param index="0" name="existing" type="Mesh" /> diff --git a/doc/classes/TabBar.xml b/doc/classes/TabBar.xml index a2beef81eb..d080294c1a 100644 --- a/doc/classes/TabBar.xml +++ b/doc/classes/TabBar.xml @@ -111,6 +111,13 @@ Returns the title of the tab at index [param tab_idx]. </description> </method> + <method name="get_tab_tooltip" qualifiers="const"> + <return type="String" /> + <param index="0" name="tab_idx" type="int" /> + <description> + Returns the tooltip text of the tab at index [param tab_idx]. + </description> + </method> <method name="is_tab_disabled" qualifiers="const"> <return type="bool" /> <param index="0" name="tab_idx" type="int" /> @@ -224,6 +231,15 @@ Sets a [param title] for the tab at index [param tab_idx]. </description> </method> + <method name="set_tab_tooltip"> + <return type="void" /> + <param index="0" name="tab_idx" type="int" /> + <param index="1" name="tooltip" type="String" /> + <description> + Sets a [param tooltip] for tab at index [param tab_idx]. + [b]Note:[/b] By default, if the [param tooltip] is empty and the tab text is truncated (not all characters fit into the tab), the title will be displayed as a tooltip. To hide the tooltip, assign [code]" "[/code] as the [param tooltip] text. + </description> + </method> </methods> <members> <member name="clip_tabs" type="bool" setter="set_clip_tabs" getter="get_clip_tabs" default="true"> diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index b0e3f67791..f4d69c3076 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -92,6 +92,13 @@ Returns the title of the tab at index [param tab_idx]. Tab titles default to the name of the indexed child node, but this can be overridden with [method set_tab_title]. </description> </method> + <method name="get_tab_tooltip" qualifiers="const"> + <return type="String" /> + <param index="0" name="tab_idx" type="int" /> + <description> + Returns the tooltip text of the tab at index [param tab_idx]. + </description> + </method> <method name="is_tab_disabled" qualifiers="const"> <return type="bool" /> <param index="0" name="tab_idx" type="int" /> @@ -173,6 +180,15 @@ Sets a custom title for the tab at index [param tab_idx] (tab titles default to the name of the indexed child node). Set it back to the child's name to make the tab default to it again. </description> </method> + <method name="set_tab_tooltip"> + <return type="void" /> + <param index="0" name="tab_idx" type="int" /> + <param index="1" name="tooltip" type="String" /> + <description> + Sets a custom tooltip text for tab at index [param tab_idx]. + [b]Note:[/b] By default, if the [param tooltip] is empty and the tab text is truncated (not all characters fit into the tab), the title will be displayed as a tooltip. To hide the tooltip, assign [code]" "[/code] as the [param tooltip] text. + </description> + </method> </methods> <members> <member name="all_tabs_in_front" type="bool" setter="set_all_tabs_in_front" getter="is_all_tabs_in_front" default="false"> diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index c0cd7f79e7..c30e83a85d 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -1505,6 +1505,7 @@ <return type="PackedInt32Array" /> <param index="0" name="shaped" type="RID" /> <param index="1" name="grapheme_flags" type="int" enum="TextServer.GraphemeFlag" is_bitfield="true" default="264" /> + <param index="2" name="skip_grapheme_flags" type="int" enum="TextServer.GraphemeFlag" is_bitfield="true" default="4" /> <description> Breaks text into words and returns array of character ranges. Use [param grapheme_flags] to set what characters are used for breaking (see [enum GraphemeFlag]). </description> diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml index 06a0daece6..94fd69c863 100644 --- a/doc/classes/TextServerExtension.xml +++ b/doc/classes/TextServerExtension.xml @@ -1664,6 +1664,7 @@ <return type="PackedInt32Array" /> <param index="0" name="shaped" type="RID" /> <param index="1" name="grapheme_flags" type="int" enum="TextServer.GraphemeFlag" is_bitfield="true" /> + <param index="2" name="skip_grapheme_flags" type="int" enum="TextServer.GraphemeFlag" is_bitfield="true" /> <description> [b]Optional.[/b] Breaks text into words and returns array of character ranges. Use [param grapheme_flags] to set what characters are used for breaking (see [enum TextServer.GraphemeFlag]). diff --git a/doc/classes/TileMapLayer.xml b/doc/classes/TileMapLayer.xml index da716a8fe3..0513a7934c 100644 --- a/doc/classes/TileMapLayer.xml +++ b/doc/classes/TileMapLayer.xml @@ -268,7 +268,7 @@ The quadrant size does not apply on a Y-sorted [TileMapLayer], as tiles are be grouped by Y position instead in that case. [b]Note:[/b] As quadrants are created according to the map's coordinate system, the quadrant's "square shape" might not look like square in the [TileMapLayer]'s local coordinate system. </member> - <member name="tile_map_data" type="PackedByteArray" setter="set_tile_map_data_from_array" getter="get_tile_map_data_as_array" default="PackedByteArray("AAA=")"> + <member name="tile_map_data" type="PackedByteArray" setter="set_tile_map_data_from_array" getter="get_tile_map_data_as_array" default="PackedByteArray()"> The raw tile map data as a byte array. </member> <member name="tile_set" type="TileSet" setter="set_tile_set" getter="get_tile_set"> diff --git a/doc/classes/VisualShaderNodeComment.xml b/doc/classes/VisualShaderNodeComment.xml new file mode 100644 index 0000000000..28496a715a --- /dev/null +++ b/doc/classes/VisualShaderNodeComment.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeComment" inherits="VisualShaderNodeFrame" deprecated="This class has no function anymore and only exists for compatibility." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Only exists for compatibility. Use [VisualShaderNodeFrame] as a replacement. + </brief_description> + <description> + This node was replaced by [VisualShaderNodeFrame] and only exists to preserve compatibility. In the [VisualShader] editor it behaves exactly like [VisualShaderNodeFrame]. + </description> + <tutorials> + </tutorials> + <members> + <member name="description" type="String" setter="set_description" getter="get_description" default=""""> + This property only exists to preserve data authored in earlier versions of Godot. It has currently no function. + </member> + </members> +</class> diff --git a/doc/classes/WorkerThreadPool.xml b/doc/classes/WorkerThreadPool.xml index fa1f08b149..2b1bc136cb 100644 --- a/doc/classes/WorkerThreadPool.xml +++ b/doc/classes/WorkerThreadPool.xml @@ -58,6 +58,7 @@ Adds [param action] as a group task to be executed by the worker threads. The [Callable] will be called a number of times based on [param elements], with the first thread calling it with the value [code]0[/code] as a parameter, and each consecutive execution incrementing this value by 1 until it reaches [code]element - 1[/code]. The number of threads the task is distributed to is defined by [param tasks_needed], where the default value [code]-1[/code] means it is distributed to all worker threads. [param high_priority] determines if the task has a high priority or a low priority (default). You can optionally provide a [param description] to help with debugging. Returns a group task ID that can be used by other methods. + [b]Warning:[/b] Every task must be waited for completion using [method wait_for_task_completion] or [method wait_for_group_task_completion] at some point so that any allocated resources inside the task can be cleaned up. </description> </method> <method name="add_task"> @@ -68,6 +69,7 @@ <description> Adds [param action] as a task to be executed by a worker thread. [param high_priority] determines if the task has a high priority or a low priority (default). You can optionally provide a [param description] to help with debugging. Returns a task ID that can be used by other methods. + [b]Warning:[/b] Every task must be waited for completion using [method wait_for_task_completion] or [method wait_for_group_task_completion] at some point so that any allocated resources inside the task can be cleaned up. </description> </method> <method name="get_group_processed_element_count" qualifiers="const"> @@ -83,6 +85,7 @@ <param index="0" name="group_id" type="int" /> <description> Returns [code]true[/code] if the group task with the given ID is completed. + [b]Note:[/b] You should only call this method between adding the group task and awaiting its completion. </description> </method> <method name="is_task_completed" qualifiers="const"> @@ -90,6 +93,7 @@ <param index="0" name="task_id" type="int" /> <description> Returns [code]true[/code] if the task with the given ID is completed. + [b]Note:[/b] You should only call this method between adding the task and awaiting its completion. </description> </method> <method name="wait_for_group_task_completion"> diff --git a/doc/classes/XRBodyModifier3D.xml b/doc/classes/XRBodyModifier3D.xml index 49a226c106..d08b92a56c 100644 --- a/doc/classes/XRBodyModifier3D.xml +++ b/doc/classes/XRBodyModifier3D.xml @@ -4,15 +4,15 @@ A node for driving body meshes from [XRBodyTracker] data. </brief_description> <description> - This node uses body tracking data from a [XRBodyTracker] to animate the skeleton of a body mesh. - This node positions itself at the [constant XRBodyTracker.JOINT_ROOT] position and scales itself to [member XRServer.world_scale]. Adding the body model as a child of this node will result in the model being positioned and scaled correctly for XR experiences. + This node uses body tracking data from an [XRBodyTracker] to pose the skeleton of a body mesh. + Positioning of the body is performed by creating an [XRNode3D] ancestor of the body mesh driven by the same [XRBodyTracker]. The body tracking position-data is scaled by [member Skeleton3D.motion_scale] when applied to the skeleton, which can be used to adjust the tracked body to match the scale of the body model. </description> <tutorials> <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link> </tutorials> <members> - <member name="body_tracker" type="StringName" setter="set_body_tracker" getter="get_body_tracker" default="&"/user/body""> + <member name="body_tracker" type="StringName" setter="set_body_tracker" getter="get_body_tracker" default="&"/user/body_tracker""> The name of the [XRBodyTracker] registered with [XRServer] to obtain the body tracking data from. </member> <member name="body_update" type="int" setter="set_body_update" getter="get_body_update" enum="XRBodyModifier3D.BodyUpdate" is_bitfield="true" default="7"> @@ -21,9 +21,6 @@ <member name="bone_update" type="int" setter="set_bone_update" getter="get_bone_update" enum="XRBodyModifier3D.BoneUpdate" default="0"> Specifies the type of updates to perform on the bones. </member> - <member name="show_when_tracked" type="bool" setter="set_show_when_tracked" getter="get_show_when_tracked" default="true"> - If true then the nodes visibility is determined by whether tracking data is available. - </member> </members> <constants> <constant name="BODY_UPDATE_UPPER_BODY" value="1" enum="BodyUpdate" is_bitfield="true"> diff --git a/doc/classes/XRBodyTracker.xml b/doc/classes/XRBodyTracker.xml index 9c869b4f5f..06a4879953 100644 --- a/doc/classes/XRBodyTracker.xml +++ b/doc/classes/XRBodyTracker.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="XRBodyTracker" inherits="RefCounted" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> +<class name="XRBodyTracker" inherits="XRPositionalTracker" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> A tracked body in XR. </brief_description> @@ -49,6 +49,7 @@ <member name="has_tracking_data" type="bool" setter="set_has_tracking_data" getter="get_has_tracking_data" default="false"> If [code]true[/code], the body tracking data is valid. </member> + <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" overrides="XRTracker" enum="XRServer.TrackerType" default="32" /> </members> <constants> <constant name="BODY_FLAG_UPPER_BODY_SUPPORTED" value="1" enum="BodyFlags" is_bitfield="true"> diff --git a/doc/classes/XRControllerTracker.xml b/doc/classes/XRControllerTracker.xml new file mode 100644 index 0000000000..50727a7633 --- /dev/null +++ b/doc/classes/XRControllerTracker.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="XRControllerTracker" inherits="XRPositionalTracker" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A tracked controller. + </brief_description> + <description> + An instance of this object represents a controller that is tracked. + As controllers are turned on and the [XRInterface] detects them, instances of this object are automatically added to this list of active tracking objects accessible through the [XRServer]. + The [XRController3D] consumes objects of this type and should be used in your project. + </description> + <tutorials> + <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link> + </tutorials> + <members> + <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" overrides="XRTracker" enum="XRServer.TrackerType" default="2" /> + </members> +</class> diff --git a/doc/classes/XRFaceModifier3D.xml b/doc/classes/XRFaceModifier3D.xml index 8caa74cff7..9599051b1b 100644 --- a/doc/classes/XRFaceModifier3D.xml +++ b/doc/classes/XRFaceModifier3D.xml @@ -12,7 +12,7 @@ <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link> </tutorials> <members> - <member name="face_tracker" type="StringName" setter="set_face_tracker" getter="get_face_tracker" default="&"/user/head""> + <member name="face_tracker" type="StringName" setter="set_face_tracker" getter="get_face_tracker" default="&"/user/face_tracker""> The [XRFaceTracker] path. </member> <member name="target" type="NodePath" setter="set_target" getter="get_target" default="NodePath("")"> diff --git a/doc/classes/XRFaceTracker.xml b/doc/classes/XRFaceTracker.xml index 96ed137324..23a9a9cb17 100644 --- a/doc/classes/XRFaceTracker.xml +++ b/doc/classes/XRFaceTracker.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="XRFaceTracker" inherits="RefCounted" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> +<class name="XRFaceTracker" inherits="XRTracker" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> A tracked face. </brief_description> @@ -31,6 +31,7 @@ <member name="blend_shapes" type="PackedFloat32Array" setter="set_blend_shapes" getter="get_blend_shapes" default="PackedFloat32Array()"> The array of face blend shape weights with indices corresponding to the [enum BlendShapeEntry] enum. </member> + <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" overrides="XRTracker" enum="XRServer.TrackerType" default="64" /> </members> <constants> <constant name="FT_EYE_LOOK_OUT_RIGHT" value="0" enum="BlendShapeEntry"> diff --git a/doc/classes/XRHandModifier3D.xml b/doc/classes/XRHandModifier3D.xml index 9ff27bb982..09ff321070 100644 --- a/doc/classes/XRHandModifier3D.xml +++ b/doc/classes/XRHandModifier3D.xml @@ -4,8 +4,8 @@ A node for driving hand meshes from [XRHandTracker] data. </brief_description> <description> - This node uses hand tracking data from a [XRHandTracker] to animate the skeleton of a hand mesh. - This node positions itself at the [constant XRHandTracker.HAND_JOINT_PALM] position and scales itself to [member XRServer.world_scale]. Adding the hand model as a child of this node will result in the model being positioned and scaled correctly for XR experiences. + This node uses hand tracking data from an [XRHandTracker] to pose the skeleton of a hand mesh. + Positioning of hands is performed by creating an [XRNode3D] ancestor of the hand mesh driven by the same [XRHandTracker]. The hand tracking position-data is scaled by [member Skeleton3D.motion_scale] when applied to the skeleton, which can be used to adjust the tracked hand to match the scale of the hand model. </description> <tutorials> @@ -15,7 +15,7 @@ <member name="bone_update" type="int" setter="set_bone_update" getter="get_bone_update" enum="XRHandModifier3D.BoneUpdate" default="0"> Specifies the type of updates to perform on the bones. </member> - <member name="hand_tracker" type="StringName" setter="set_hand_tracker" getter="get_hand_tracker" default="&"/user/left""> + <member name="hand_tracker" type="StringName" setter="set_hand_tracker" getter="get_hand_tracker" default="&"/user/hand_tracker/left""> The name of the [XRHandTracker] registered with [XRServer] to obtain the hand tracking data from. </member> </members> diff --git a/doc/classes/XRHandTracker.xml b/doc/classes/XRHandTracker.xml index 932fec083a..69390df696 100644 --- a/doc/classes/XRHandTracker.xml +++ b/doc/classes/XRHandTracker.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="XRHandTracker" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> +<class name="XRHandTracker" inherits="XRPositionalTracker" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> A tracked hand in XR. </brief_description> @@ -11,6 +11,12 @@ <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link> </tutorials> <methods> + <method name="get_hand" qualifiers="const"> + <return type="int" enum="XRHandTracker.Hand" /> + <description> + Returns the type of hand. + </description> + </method> <method name="get_hand_joint_angular_velocity" qualifiers="const"> <return type="Vector3" /> <param index="0" name="joint" type="int" enum="XRHandTracker.HandJoint" /> @@ -46,6 +52,13 @@ Returns the transform for the given hand joint. </description> </method> + <method name="set_hand"> + <return type="void" /> + <param index="0" name="hand" type="int" enum="XRHandTracker.Hand" /> + <description> + Sets the type of hand. + </description> + </method> <method name="set_hand_joint_angular_velocity"> <return type="void" /> <param index="0" name="joint" type="int" enum="XRHandTracker.HandJoint" /> @@ -88,15 +101,13 @@ </method> </methods> <members> - <member name="hand" type="int" setter="set_hand" getter="get_hand" enum="XRHandTracker.Hand" default="0"> - The type of hand. - </member> <member name="hand_tracking_source" type="int" setter="set_hand_tracking_source" getter="get_hand_tracking_source" enum="XRHandTracker.HandTrackingSource" default="0"> The source of the hand tracking data. </member> <member name="has_tracking_data" type="bool" setter="set_has_tracking_data" getter="get_has_tracking_data" default="false"> If [code]true[/code], the hand tracking data is valid. </member> + <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" overrides="XRTracker" enum="XRServer.TrackerType" default="16" /> </members> <constants> <constant name="HAND_LEFT" value="0" enum="Hand"> diff --git a/doc/classes/XRNode3D.xml b/doc/classes/XRNode3D.xml index 3da1873ed8..dfe5600fcc 100644 --- a/doc/classes/XRNode3D.xml +++ b/doc/classes/XRNode3D.xml @@ -46,6 +46,9 @@ The name of the pose we're bound to. Which poses a tracker supports is not known during design time. Godot defines number of standard pose names such as [code]aim[/code] and [code]grip[/code] but other may be configured within a given [XRInterface]. </member> + <member name="show_when_tracked" type="bool" setter="set_show_when_tracked" getter="get_show_when_tracked" default="false"> + Enables showing the node when tracking starts, and hiding the node when tracking is lost. + </member> <member name="tracker" type="StringName" setter="set_tracker" getter="get_tracker" default="&"""> The name of the tracker we're bound to. Which trackers are available is not known during design time. Godot defines a number of standard trackers such as [code]left_hand[/code] and [code]right_hand[/code] but others may be configured within a given [XRInterface]. diff --git a/doc/classes/XRPositionalTracker.xml b/doc/classes/XRPositionalTracker.xml index bd2432af50..2b2eae0c5e 100644 --- a/doc/classes/XRPositionalTracker.xml +++ b/doc/classes/XRPositionalTracker.xml @@ -1,18 +1,18 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="XRPositionalTracker" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> +<class name="XRPositionalTracker" inherits="XRTracker" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> A tracked object. </brief_description> <description> An instance of this object represents a device that is tracked, such as a controller or anchor point. HMDs aren't represented here as they are handled internally. As controllers are turned on and the [XRInterface] detects them, instances of this object are automatically added to this list of active tracking objects accessible through the [XRServer]. - The [XRController3D] and [XRAnchor3D] both consume objects of this type and should be used in your project. The positional trackers are just under-the-hood objects that make this all work. These are mostly exposed so that GDExtension-based interfaces can interact with them. + The [XRNode3D] and [XRAnchor3D] both consume objects of this type and should be used in your project. The positional trackers are just under-the-hood objects that make this all work. These are mostly exposed so that GDExtension-based interfaces can interact with them. </description> <tutorials> <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link> </tutorials> <methods> - <method name="get_input" qualifiers="const"> + <method name="get_input" qualifiers="const" deprecated="Use through [XRControllerTracker]."> <return type="Variant" /> <param index="0" name="name" type="StringName" /> <description> @@ -40,7 +40,7 @@ Marks this pose as invalid, we don't clear the last reported state but it allows users to decide if trackers need to be hidden if we lose tracking or just remain at their last known position. </description> </method> - <method name="set_input"> + <method name="set_input" deprecated="Use through [XRControllerTracker]."> <return type="void" /> <param index="0" name="name" type="StringName" /> <param index="1" name="value" type="Variant" /> @@ -61,23 +61,12 @@ </method> </methods> <members> - <member name="description" type="String" setter="set_tracker_desc" getter="get_tracker_desc" default=""""> - The description of this tracker. - </member> <member name="hand" type="int" setter="set_tracker_hand" getter="get_tracker_hand" enum="XRPositionalTracker.TrackerHand" default="0"> Defines which hand this tracker relates to. </member> - <member name="name" type="StringName" setter="set_tracker_name" getter="get_tracker_name" default="&"Unknown""> - The unique name of this tracker. The trackers that are available differ between various XR runtimes and can often be configured by the user. Godot maintains a number of reserved names that it expects the [XRInterface] to implement if applicable: - - [code]left_hand[/code] identifies the controller held in the players left hand - - [code]right_hand[/code] identifies the controller held in the players right hand - </member> <member name="profile" type="String" setter="set_tracker_profile" getter="get_tracker_profile" default=""""> The profile associated with this tracker, interface dependent but will indicate the type of controller being tracked. </member> - <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" enum="XRServer.TrackerType" default="128"> - The type of tracker. - </member> </members> <signals> <signal name="button_pressed"> @@ -135,5 +124,8 @@ <constant name="TRACKER_HAND_RIGHT" value="2" enum="TrackerHand"> This tracker is the right hand controller. </constant> + <constant name="TRACKER_HAND_MAX" value="3" enum="TrackerHand"> + Represents the size of the [enum TrackerHand] enum. + </constant> </constants> </class> diff --git a/doc/classes/XRServer.xml b/doc/classes/XRServer.xml index 671cc8f15c..d5714980c3 100644 --- a/doc/classes/XRServer.xml +++ b/doc/classes/XRServer.xml @@ -10,30 +10,6 @@ <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link> </tutorials> <methods> - <method name="add_body_tracker"> - <return type="void" /> - <param index="0" name="tracker_name" type="StringName" /> - <param index="1" name="body_tracker" type="XRBodyTracker" /> - <description> - Registers a new [XRBodyTracker] that tracks the joints of a body. - </description> - </method> - <method name="add_face_tracker"> - <return type="void" /> - <param index="0" name="tracker_name" type="StringName" /> - <param index="1" name="face_tracker" type="XRFaceTracker" /> - <description> - Registers a new [XRFaceTracker] that tracks the blend shapes of a face. - </description> - </method> - <method name="add_hand_tracker"> - <return type="void" /> - <param index="0" name="tracker_name" type="StringName" /> - <param index="1" name="hand_tracker" type="XRHandTracker" /> - <description> - Registers a new [XRHandTracker] that tracks the joints of a hand. - </description> - </method> <method name="add_interface"> <return type="void" /> <param index="0" name="interface" type="XRInterface" /> @@ -43,9 +19,9 @@ </method> <method name="add_tracker"> <return type="void" /> - <param index="0" name="tracker" type="XRPositionalTracker" /> + <param index="0" name="tracker" type="XRTracker" /> <description> - Registers a new [XRPositionalTracker] that tracks a spatial location in real space. + Registers a new [XRTracker] that tracks a physical object. </description> </method> <method name="center_on_hmd"> @@ -74,45 +50,6 @@ Finds an interface by its [param name]. For example, if your project uses capabilities of an AR/VR platform, you can find the interface for that platform by name and initialize it. </description> </method> - <method name="get_body_tracker" qualifiers="const"> - <return type="XRBodyTracker" /> - <param index="0" name="tracker_name" type="StringName" /> - <description> - Returns the [XRBodyTracker] with the given tracker name. - </description> - </method> - <method name="get_body_trackers" qualifiers="const"> - <return type="Dictionary" /> - <description> - Returns a dictionary of the registered body trackers. Each element of the dictionary is a tracker name mapping to the [XRBodyTracker] instance. - </description> - </method> - <method name="get_face_tracker" qualifiers="const"> - <return type="XRFaceTracker" /> - <param index="0" name="tracker_name" type="StringName" /> - <description> - Returns the [XRFaceTracker] with the given tracker name. - </description> - </method> - <method name="get_face_trackers" qualifiers="const"> - <return type="Dictionary" /> - <description> - Returns a dictionary of the registered face trackers. Each element of the dictionary is a tracker name mapping to the [XRFaceTracker] instance. - </description> - </method> - <method name="get_hand_tracker" qualifiers="const"> - <return type="XRHandTracker" /> - <param index="0" name="tracker_name" type="StringName" /> - <description> - Returns the [XRHandTracker] with the given tracker name. - </description> - </method> - <method name="get_hand_trackers" qualifiers="const"> - <return type="Dictionary" /> - <description> - Returns a dictionary of the registered hand trackers. Each element of the dictionary is a tracker name mapping to the [XRHandTracker] instance. - </description> - </method> <method name="get_hmd_transform"> <return type="Transform3D" /> <description> @@ -145,7 +82,7 @@ </description> </method> <method name="get_tracker" qualifiers="const"> - <return type="XRPositionalTracker" /> + <return type="XRTracker" /> <param index="0" name="tracker_name" type="StringName" /> <description> Returns the positional tracker with the given [param tracker_name]. @@ -158,27 +95,6 @@ Returns a dictionary of trackers for [param tracker_types]. </description> </method> - <method name="remove_body_tracker"> - <return type="void" /> - <param index="0" name="tracker_name" type="StringName" /> - <description> - Removes a registered [XRBodyTracker]. - </description> - </method> - <method name="remove_face_tracker"> - <return type="void" /> - <param index="0" name="tracker_name" type="StringName" /> - <description> - Removes a registered [XRFaceTracker]. - </description> - </method> - <method name="remove_hand_tracker"> - <return type="void" /> - <param index="0" name="tracker_name" type="StringName" /> - <description> - Removes a registered [XRHandTracker]. - </description> - </method> <method name="remove_interface"> <return type="void" /> <param index="0" name="interface" type="XRInterface" /> @@ -188,9 +104,9 @@ </method> <method name="remove_tracker"> <return type="void" /> - <param index="0" name="tracker" type="XRPositionalTracker" /> + <param index="0" name="tracker" type="XRTracker" /> <description> - Removes this positional [param tracker]. + Removes this [param tracker]. </description> </method> </methods> @@ -207,66 +123,6 @@ </member> </members> <signals> - <signal name="body_tracker_added"> - <param index="0" name="tracker_name" type="StringName" /> - <param index="1" name="body_tracker" type="XRBodyTracker" /> - <description> - Emitted when a new body tracker is added. - </description> - </signal> - <signal name="body_tracker_removed"> - <param index="0" name="tracker_name" type="StringName" /> - <description> - Emitted when a body tracker is removed. - </description> - </signal> - <signal name="body_tracker_updated"> - <param index="0" name="tracker_name" type="StringName" /> - <param index="1" name="body_tracker" type="XRBodyTracker" /> - <description> - Emitted when an existing body tracker is updated. - </description> - </signal> - <signal name="face_tracker_added"> - <param index="0" name="tracker_name" type="StringName" /> - <param index="1" name="face_tracker" type="XRFaceTracker" /> - <description> - Emitted when a new face tracker is added. - </description> - </signal> - <signal name="face_tracker_removed"> - <param index="0" name="tracker_name" type="StringName" /> - <description> - Emitted when a face tracker is removed. - </description> - </signal> - <signal name="face_tracker_updated"> - <param index="0" name="tracker_name" type="StringName" /> - <param index="1" name="face_tracker" type="XRFaceTracker" /> - <description> - Emitted when an existing face tracker is updated. - </description> - </signal> - <signal name="hand_tracker_added"> - <param index="0" name="tracker_name" type="StringName" /> - <param index="1" name="hand_tracker" type="XRHandTracker" /> - <description> - Emitted when a new hand tracker is added. - </description> - </signal> - <signal name="hand_tracker_removed"> - <param index="0" name="tracker_name" type="StringName" /> - <description> - Emitted when a hand tracker is removed. - </description> - </signal> - <signal name="hand_tracker_updated"> - <param index="0" name="tracker_name" type="StringName" /> - <param index="1" name="hand_tracker" type="XRHandTracker" /> - <description> - Emitted when an existing hand tracker is updated. - </description> - </signal> <signal name="interface_added"> <param index="0" name="interface_name" type="StringName" /> <description> @@ -314,6 +170,15 @@ <constant name="TRACKER_ANCHOR" value="8" enum="TrackerType"> The tracker tracks the location and size of an AR anchor. </constant> + <constant name="TRACKER_HAND" value="16" enum="TrackerType"> + The tracker tracks the location and joints of a hand. + </constant> + <constant name="TRACKER_BODY" value="32" enum="TrackerType"> + The tracker tracks the location and joints of a body. + </constant> + <constant name="TRACKER_FACE" value="64" enum="TrackerType"> + The tracker tracks the expressions of a face. + </constant> <constant name="TRACKER_ANY_KNOWN" value="127" enum="TrackerType"> Used internally to filter trackers of any known type. </constant> diff --git a/doc/classes/XRTracker.xml b/doc/classes/XRTracker.xml new file mode 100644 index 0000000000..00a44bd03e --- /dev/null +++ b/doc/classes/XRTracker.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="XRTracker" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A tracked object. + </brief_description> + <description> + This object is the base of all XR trackers. + </description> + <tutorials> + <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link> + </tutorials> + <members> + <member name="description" type="String" setter="set_tracker_desc" getter="get_tracker_desc" default=""""> + The description of this tracker. + </member> + <member name="name" type="StringName" setter="set_tracker_name" getter="get_tracker_name" default="&"Unknown""> + The unique name of this tracker. The trackers that are available differ between various XR runtimes and can often be configured by the user. Godot maintains a number of reserved names that it expects the [XRInterface] to implement if applicable: + - [code]head[/code] identifies the [XRPositionalTracker] of the players head + - [code]left_hand[/code] identifies the [XRControllerTracker] in the players left hand + - [code]right_hand[/code] identifies the [XRControllerTracker] in the players right hand + - [code]/user/hand_tracker/left[/code] identifies the [XRHandTracker] for the players left hand + - [code]/user/hand_tracker/right[/code] identifies the [XRHandTracker] for the players right hand + - [code]/user/body_tracker[/code] identifies the [XRBodyTracker] for the players body + - [code]/user/face_tracker[/code] identifies the [XRFaceTracker] for the players face + </member> + <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" enum="XRServer.TrackerType" default="128"> + The type of tracker. + </member> + </members> +</class> diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index 9fa95a93f8..5fabeb94f5 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -1922,7 +1922,7 @@ void RasterizerCanvasGLES3::render_sdf(RID p_render_target, LightOccluderInstanc while (instance) { OccluderPolygon *oc = occluder_polygon_owner.get_or_null(instance->occluder); - if (!oc || oc->sdf_vertex_array == 0) { + if (!oc || oc->sdf_vertex_array == 0 || !instance->sdf_collision) { instance = instance->next; continue; } diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index a35d8bfdde..210507c2c6 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -286,6 +286,23 @@ Error FileAccessUnix::get_error() const { return last_error; } +Error FileAccessUnix::resize(int64_t p_length) { + ERR_FAIL_NULL_V_MSG(f, FAILED, "File must be opened before use."); + int res = ::ftruncate(fileno(f), p_length); + switch (res) { + case 0: + return OK; + case EBADF: + return ERR_FILE_CANT_OPEN; + case EFBIG: + return ERR_OUT_OF_MEMORY; + case EINVAL: + return ERR_INVALID_PARAMETER; + default: + return FAILED; + } +} + void FileAccessUnix::flush() { ERR_FAIL_NULL_MSG(f, "File must be opened before use."); fflush(f); diff --git a/drivers/unix/file_access_unix.h b/drivers/unix/file_access_unix.h index 553fbcf355..c0286dbff3 100644 --- a/drivers/unix/file_access_unix.h +++ b/drivers/unix/file_access_unix.h @@ -75,6 +75,7 @@ public: virtual Error get_error() const override; ///< get last error + virtual Error resize(int64_t p_length) override; virtual void flush() override; virtual void store_8(uint8_t p_dest) override; ///< store a byte virtual void store_16(uint16_t p_dest) override; diff --git a/drivers/unix/file_access_unix_pipe.h b/drivers/unix/file_access_unix_pipe.h index d14f897d8f..8e7988791b 100644 --- a/drivers/unix/file_access_unix_pipe.h +++ b/drivers/unix/file_access_unix_pipe.h @@ -70,6 +70,7 @@ public: virtual Error get_error() const override; ///< get last error + virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override {} virtual void store_8(uint8_t p_src) override; ///< store a byte virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 1a8cd53486..0a79c8014b 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -784,7 +784,7 @@ String OS_Unix::get_locale() const { return locale; } -Error OS_Unix::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) { +Error OS_Unix::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) { String path = p_path; if (FileAccess::exists(path) && path.is_relative_path()) { @@ -808,8 +808,8 @@ Error OS_Unix::open_dynamic_library(const String &p_path, void *&p_library_handl p_library_handle = dlopen(path.utf8().get_data(), GODOT_DLOPEN_MODE); ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror())); - if (r_resolved_path != nullptr) { - *r_resolved_path = path; + if (p_data != nullptr && p_data->r_resolved_path != nullptr) { + *p_data->r_resolved_path = path; } return OK; diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index a107e7a0e3..df269a59d3 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -62,7 +62,7 @@ public: virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override; - virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override; + virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override; virtual Error close_dynamic_library(void *p_library_handle) override; virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override; diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index dd8bceb573..726e0fdc5a 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -41,6 +41,7 @@ #include <windows.h> #include <errno.h> +#include <io.h> #include <sys/stat.h> #include <sys/types.h> #include <tchar.h> @@ -369,6 +370,24 @@ Error FileAccessWindows::get_error() const { return last_error; } +Error FileAccessWindows::resize(int64_t p_length) { + ERR_FAIL_NULL_V_MSG(f, FAILED, "File must be opened before use."); + errno_t res = _chsize_s(_fileno(f), p_length); + switch (res) { + case 0: + return OK; + case EACCES: + case EBADF: + return ERR_FILE_CANT_OPEN; + case ENOSPC: + return ERR_OUT_OF_MEMORY; + case EINVAL: + return ERR_INVALID_PARAMETER; + default: + return FAILED; + } +} + void FileAccessWindows::flush() { ERR_FAIL_NULL(f); diff --git a/drivers/windows/file_access_windows.h b/drivers/windows/file_access_windows.h index 173423fb06..a25bbcfb3a 100644 --- a/drivers/windows/file_access_windows.h +++ b/drivers/windows/file_access_windows.h @@ -77,6 +77,7 @@ public: virtual Error get_error() const override; ///< get last error + virtual Error resize(int64_t p_length) override; virtual void flush() override; virtual void store_8(uint8_t p_dest) override; ///< store a byte virtual void store_16(uint16_t p_dest) override; diff --git a/drivers/windows/file_access_windows_pipe.h b/drivers/windows/file_access_windows_pipe.h index e6abe61fa3..b885ef78e6 100644 --- a/drivers/windows/file_access_windows_pipe.h +++ b/drivers/windows/file_access_windows_pipe.h @@ -69,6 +69,7 @@ public: virtual Error get_error() const override; ///< get last error + virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override {} virtual void store_8(uint8_t p_src) override; ///< store a byte virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index de5f9ecf89..f57e9cb5f8 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -47,6 +47,7 @@ #include "scene/gui/check_box.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" +#include "scene/gui/margin_container.h" #include "scene/gui/option_button.h" #include "scene/gui/popup_menu.h" #include "scene/gui/spin_box.h" @@ -872,7 +873,13 @@ ConnectDialog::~ConnectDialog() { Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const { // If it's not a doc tooltip, fallback to the default one. - return p_text.contains("::") ? nullptr : memnew(EditorHelpTooltip(p_text)); + if (p_text.contains("::")) { + return nullptr; + } + + EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text)); + EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<ConnectionsDockTree *>(this)); + return memnew(Control); // Make the standard tooltip invisible. } struct _ConnectionsDockMethodInfoSort { @@ -1458,8 +1465,8 @@ void ConnectionsDock::update_tree() { section_item = tree->create_item(root); section_item->set_text(0, class_name); - // `|` separators used in `EditorHelpTooltip` for formatting. - section_item->set_tooltip_text(0, "class|" + doc_class_name + "||"); + // `|` separators used in `EditorHelpBit`. + section_item->set_tooltip_text(0, "class|" + doc_class_name + "|"); section_item->set_icon(0, class_icon); section_item->set_selectable(0, false); section_item->set_editable(0, false); @@ -1490,8 +1497,8 @@ void ConnectionsDock::update_tree() { sinfo["args"] = argnames; signal_item->set_metadata(0, sinfo); signal_item->set_icon(0, get_editor_theme_icon(SNAME("Signal"))); - // `|` separators used in `EditorHelpTooltip` for formatting. - signal_item->set_tooltip_text(0, "signal|" + doc_class_name + "|" + String(signal_name) + "|" + signame.trim_prefix(mi.name)); + // `|` separators used in `EditorHelpBit`. + signal_item->set_tooltip_text(0, "signal|" + doc_class_name + "|" + String(signal_name)); // List existing connections. List<Object::Connection> existing_connections; diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index f644d3335a..78f1b76e23 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -191,7 +191,7 @@ public: ////////////////////////////////////////// -// Custom `Tree` needed to use `EditorHelpTooltip` to display signal documentation. +// Custom `Tree` needed to use `EditorHelpBit` to display signal documentation. class ConnectionsDockTree : public Tree { virtual Control *make_custom_tooltip(const String &p_text) const; }; diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index f7914d3aaa..b00f059b36 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -202,8 +202,7 @@ void CreateDialog::_update_search() { select_type(_top_result(candidates, search_text)); } else { favorite->set_disabled(true); - help_bit->set_text(vformat(TTR("No results for \"%s\"."), search_text)); - help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5)); + help_bit->set_custom_text(String(), String(), vformat(TTR("No results for \"%s\"."), search_text.replace("[", "[lb]"))); get_ok_button()->set_disabled(true); search_options->deselect_all(); } @@ -502,17 +501,7 @@ void CreateDialog::select_type(const String &p_type, bool p_center_on_item) { to_select->select(0); search_options->scroll_to_item(to_select, p_center_on_item); - String text = help_bit->get_class_description(p_type); - if (!text.is_empty()) { - // Display both class name and description, since the help bit may be displayed - // far away from the location (especially if the dialog was resized to be taller). - help_bit->set_text(vformat("[b]%s[/b]: %s", p_type, text)); - help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); - } else { - // Use nested `vformat()` as translators shouldn't interfere with BBCode tags. - help_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", p_type))); - help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5)); - } + help_bit->parse_symbol("class|" + p_type + "|"); favorite->set_disabled(false); favorite->set_pressed(favorite_list.has(p_type)); @@ -837,6 +826,7 @@ CreateDialog::CreateDialog() { vbc->add_margin_child(TTR("Matches:"), search_options, true); help_bit = memnew(EditorHelpBit); + help_bit->set_content_height_limits(64 * EDSCALE, 64 * EDSCALE); help_bit->connect("request_hide", callable_mp(this, &CreateDialog::_hide_requested)); vbc->add_margin_child(TTR("Description:"), help_bit); diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp index c6ecaba230..c1db674cbe 100644 --- a/editor/editor_build_profile.cpp +++ b/editor/editor_build_profile.cpp @@ -593,6 +593,10 @@ void EditorBuildProfileManager::_action_confirm() { } } +void EditorBuildProfileManager::_hide_requested() { + _cancel_pressed(); // From AcceptDialog. +} + void EditorBuildProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) { TreeItem *class_item = class_list->create_item(p_parent); class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); @@ -646,21 +650,10 @@ void EditorBuildProfileManager::_class_list_item_selected() { Variant md = item->get_metadata(0); if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { - String text = description_bit->get_class_description(md); - if (!text.is_empty()) { - // Display both class name and description, since the help bit may be displayed - // far away from the location (especially if the dialog was resized to be taller). - description_bit->set_text(vformat("[b]%s[/b]: %s", md, text)); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); - } else { - // Use nested `vformat()` as translators shouldn't interfere with BBCode tags. - description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md))); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5)); - } + description_bit->parse_symbol("class|" + md.operator String() + "|"); } else if (md.get_type() == Variant::INT) { String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption((int)md)); - description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(build_option_description))); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); + description_bit->set_custom_text(TTR(item->get_text(0)), String(), TTRGET(build_option_description)); } } @@ -864,7 +857,8 @@ EditorBuildProfileManager::EditorBuildProfileManager() { main_vbc->add_margin_child(TTR("Configure Engine Compilation Profile:"), class_list, true); description_bit = memnew(EditorHelpBit); - description_bit->set_custom_minimum_size(Size2(0, 80) * EDSCALE); + description_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE); + description_bit->connect("request_hide", callable_mp(this, &EditorBuildProfileManager::_hide_requested)); main_vbc->add_margin_child(TTR("Description:"), description_bit, false); confirm_dialog = memnew(ConfirmationDialog); diff --git a/editor/editor_build_profile.h b/editor/editor_build_profile.h index 649784afc3..a947365c7f 100644 --- a/editor/editor_build_profile.h +++ b/editor/editor_build_profile.h @@ -150,6 +150,7 @@ class EditorBuildProfileManager : public AcceptDialog { void _profile_action(int p_action); void _action_confirm(); + void _hide_requested(); void _update_edited_profile(); void _fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected); diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp index 541bcd5e02..86b7b3eb2f 100644 --- a/editor/editor_feature_profile.cpp +++ b/editor/editor_feature_profile.cpp @@ -493,6 +493,10 @@ void EditorFeatureProfileManager::_profile_selected(int p_what) { _update_selected_profile(); } +void EditorFeatureProfileManager::_hide_requested() { + _cancel_pressed(); // From AcceptDialog. +} + void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) { TreeItem *class_item = class_list->create_item(p_parent); class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); @@ -555,22 +559,10 @@ void EditorFeatureProfileManager::_class_list_item_selected() { Variant md = item->get_metadata(0); if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { - String text = description_bit->get_class_description(md); - if (!text.is_empty()) { - // Display both class name and description, since the help bit may be displayed - // far away from the location (especially if the dialog was resized to be taller). - description_bit->set_text(vformat("[b]%s[/b]: %s", md, text)); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); - } else { - // Use nested `vformat()` as translators shouldn't interfere with BBCode tags. - description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md))); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5)); - } + description_bit->parse_symbol("class|" + md.operator String() + "|"); } else if (md.get_type() == Variant::INT) { String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature((int)md)); - description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(feature_description))); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); - + description_bit->set_custom_text(TTR(item->get_text(0)), String(), TTRGET(feature_description)); return; } else { return; @@ -991,8 +983,9 @@ EditorFeatureProfileManager::EditorFeatureProfileManager() { property_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); description_bit = memnew(EditorHelpBit); + description_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE); + description_bit->connect("request_hide", callable_mp(this, &EditorFeatureProfileManager::_hide_requested)); property_list_vbc->add_margin_child(TTR("Description:"), description_bit, false); - description_bit->set_custom_minimum_size(Size2(0, 80) * EDSCALE); property_list = memnew(Tree); property_list_vbc->add_margin_child(TTR("Extra Options:"), property_list, true); diff --git a/editor/editor_feature_profile.h b/editor/editor_feature_profile.h index 25ee1c9ba4..2fa6ae9813 100644 --- a/editor/editor_feature_profile.h +++ b/editor/editor_feature_profile.h @@ -142,6 +142,7 @@ class EditorFeatureProfileManager : public AcceptDialog { void _profile_action(int p_action); void _profile_selected(int p_what); + void _hide_requested(); String current_profile; void _update_profile_list(const String &p_select_profile = String()); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 1f7505633b..5cc09b7104 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -30,6 +30,7 @@ #include "editor_help.h" +#include "core/config/project_settings.h" #include "core/core_constants.h" #include "core/extension/gdextension.h" #include "core/input/input.h" @@ -193,7 +194,6 @@ void EditorHelp::_update_theme_item_cache() { class_desc->add_theme_font_override("normal_font", theme_cache.doc_font); class_desc->add_theme_font_size_override("normal_font_size", theme_cache.doc_font_size); - class_desc->add_theme_color_override("selection_color", get_theme_color(SNAME("selection_color"), SNAME("EditorHelp"))); class_desc->add_theme_constant_override("line_separation", get_theme_constant(SNAME("line_separation"), SNAME("EditorHelp"))); class_desc->add_theme_constant_override("table_h_separation", get_theme_constant(SNAME("table_h_separation"), SNAME("EditorHelp"))); class_desc->add_theme_constant_override("table_v_separation", get_theme_constant(SNAME("table_v_separation"), SNAME("EditorHelp"))); @@ -222,29 +222,35 @@ void EditorHelp::_class_list_select(const String &p_select) { } void EditorHelp::_class_desc_select(const String &p_select) { - if (p_select.begins_with("$")) { // enum - String select = p_select.substr(1, p_select.length()); - String class_name; - int rfind = select.rfind("."); - if (rfind != -1) { - class_name = select.substr(0, rfind); - select = select.substr(rfind + 1); + if (p_select.begins_with("$")) { // Enum. + const String link = p_select.substr(1); + + String enum_class_name; + String enum_name; + if (CoreConstants::is_global_enum(link)) { + enum_class_name = "@GlobalScope"; + enum_name = link; } else { - class_name = "@GlobalScope"; + const int dot_pos = link.rfind("."); + if (dot_pos >= 0) { + enum_class_name = link.left(dot_pos); + enum_name = link.substr(dot_pos + 1); + } else { + enum_class_name = edited_class; + enum_name = link; + } } - emit_signal(SNAME("go_to_help"), "class_enum:" + class_name + ":" + select); - return; - } else if (p_select.begins_with("#")) { - emit_signal(SNAME("go_to_help"), "class_name:" + p_select.substr(1, p_select.length())); - return; - } else if (p_select.begins_with("@")) { - int tag_end = p_select.find_char(' '); - String tag = p_select.substr(1, tag_end - 1); - String link = p_select.substr(tag_end + 1, p_select.length()).lstrip(" "); + emit_signal(SNAME("go_to_help"), "class_enum:" + enum_class_name + ":" + enum_name); + } else if (p_select.begins_with("#")) { // Class. + emit_signal(SNAME("go_to_help"), "class_name:" + p_select.substr(1)); + } else if (p_select.begins_with("@")) { // Member. + const int tag_end = p_select.find_char(' '); + const String tag = p_select.substr(1, tag_end - 1); + const String link = p_select.substr(tag_end + 1).lstrip(" "); String topic; - HashMap<String, int> *table = nullptr; + const HashMap<String, int> *table = nullptr; if (tag == "method") { topic = "class_method"; @@ -311,14 +317,14 @@ void EditorHelp::_class_desc_select(const String &p_select) { } if (link.contains(".")) { - int class_end = link.find_char('.'); - emit_signal(SNAME("go_to_help"), topic + ":" + link.substr(0, class_end) + ":" + link.substr(class_end + 1, link.length())); + const int class_end = link.find_char('.'); + emit_signal(SNAME("go_to_help"), topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1)); } } - } else if (p_select.begins_with("http")) { + } else if (p_select.begins_with("http:") || p_select.begins_with("https:")) { OS::get_singleton()->shell_open(p_select); - } else if (p_select.begins_with("^")) { - DisplayServer::get_singleton()->clipboard_set(p_select.trim_prefix("^")); + } else if (p_select.begins_with("^")) { // Copy button. + DisplayServer::get_singleton()->clipboard_set(p_select.substr(1)); } } @@ -341,13 +347,15 @@ void EditorHelp::_class_desc_resized(bool p_force_update_theme) { } } -void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is_bitfield) { +static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_is_bitfield, RichTextLabel *p_rt, const Control *p_owner_node, const String &p_class) { + const Color type_color = p_owner_node->get_theme_color(SNAME("type_color"), SNAME("EditorHelp")); + if (p_type.is_empty() || p_type == "void") { - class_desc->push_color(Color(theme_cache.type_color, 0.5)); - class_desc->push_hint(TTR("No return value.")); - class_desc->add_text("void"); - class_desc->pop(); // hint - class_desc->pop(); // color + p_rt->push_color(Color(type_color, 0.5)); + p_rt->push_hint(TTR("No return value.")); + p_rt->add_text("void"); + p_rt->pop(); // hint + p_rt->pop(); // color return; } @@ -359,12 +367,12 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is String display_t; // For display purposes. if (is_enum_type) { link_t = p_enum; // The link for enums is always the full enum description - display_t = _contextualize_class_specifier(p_enum, edited_class); + display_t = _contextualize_class_specifier(p_enum, p_class); } else { - display_t = _contextualize_class_specifier(p_type, edited_class); + display_t = _contextualize_class_specifier(p_type, p_class); } - class_desc->push_color(theme_cache.type_color); + p_rt->push_color(type_color); bool add_array = false; if (can_ref) { if (link_t.ends_with("[]")) { @@ -372,37 +380,41 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is link_t = link_t.trim_suffix("[]"); display_t = display_t.trim_suffix("[]"); - class_desc->push_meta("#Array", RichTextLabel::META_UNDERLINE_ON_HOVER); // class - class_desc->add_text("Array"); - class_desc->pop(); // meta - class_desc->add_text("["); + p_rt->push_meta("#Array", RichTextLabel::META_UNDERLINE_ON_HOVER); // class + p_rt->add_text("Array"); + p_rt->pop(); // meta + p_rt->add_text("["); } else if (is_bitfield) { - class_desc->push_color(Color(theme_cache.type_color, 0.5)); - class_desc->push_hint(TTR("This value is an integer composed as a bitmask of the following flags.")); - class_desc->add_text("BitField"); - class_desc->pop(); // hint - class_desc->add_text("["); - class_desc->pop(); // color + p_rt->push_color(Color(type_color, 0.5)); + p_rt->push_hint(TTR("This value is an integer composed as a bitmask of the following flags.")); + p_rt->add_text("BitField"); + p_rt->pop(); // hint + p_rt->add_text("["); + p_rt->pop(); // color } if (is_enum_type) { - class_desc->push_meta("$" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // enum + p_rt->push_meta("$" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // enum } else { - class_desc->push_meta("#" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // class + p_rt->push_meta("#" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // class } } - class_desc->add_text(display_t); + p_rt->add_text(display_t); if (can_ref) { - class_desc->pop(); // meta + p_rt->pop(); // meta if (add_array) { - class_desc->add_text("]"); + p_rt->add_text("]"); } else if (is_bitfield) { - class_desc->push_color(Color(theme_cache.type_color, 0.5)); - class_desc->add_text("]"); - class_desc->pop(); // color + p_rt->push_color(Color(type_color, 0.5)); + p_rt->add_text("]"); + p_rt->pop(); // color } } - class_desc->pop(); // color + p_rt->pop(); // color +} + +void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is_bitfield) { + _add_type_to_rt(p_type, p_enum, p_is_bitfield, class_desc, this, edited_class); } void EditorHelp::_add_type_icon(const String &p_type, int p_size, const String &p_fallback) { @@ -717,10 +729,10 @@ void EditorHelp::_update_method_list(MethodType p_method_type, const Vector<DocD String group_prefix; for (int i = 0; i < m.size(); i++) { - const String new_prefix = m[i].name.substr(0, 3); + const String new_prefix = m[i].name.left(3); bool is_new_group = false; - if (i < m.size() - 1 && new_prefix == m[i + 1].name.substr(0, 3) && new_prefix != group_prefix) { + if (i < m.size() - 1 && new_prefix == m[i + 1].name.left(3) && new_prefix != group_prefix) { is_new_group = i > 0; group_prefix = new_prefix; } else if (!group_prefix.is_empty() && new_prefix != group_prefix) { @@ -748,7 +760,7 @@ void EditorHelp::_update_method_list(MethodType p_method_type, const Vector<DocD } void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc, MethodType p_method_type, const Vector<DocData::MethodDoc> &p_methods) { -#define DTR_DOC(m_string) (p_classdoc.is_script_doc ? (m_string) : DTR(m_string)) +#define HANDLE_DOC(m_string) ((p_classdoc.is_script_doc ? (m_string) : DTR(m_string)).strip_edges()) class_desc->add_newline(); class_desc->add_newline(); @@ -807,7 +819,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc TTRC("This constructor may be changed or removed in future versions."), TTRC("This operator may be changed or removed in future versions."), }; - DEPRECATED_DOC_MSG(DTR_DOC(method.deprecated_message), TTRGET(messages_by_type[p_method_type])); + DEPRECATED_DOC_MSG(HANDLE_DOC(method.deprecated_message), TTRGET(messages_by_type[p_method_type])); } if (method.is_experimental) { @@ -822,7 +834,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc TTRC("This constructor may be changed or removed in future versions."), TTRC("This operator may be changed or removed in future versions."), }; - EXPERIMENTAL_DOC_MSG(DTR_DOC(method.experimental_message), TTRGET(messages_by_type[p_method_type])); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(method.experimental_message), TTRGET(messages_by_type[p_method_type])); } if (!method.errors_returned.is_empty()) { @@ -856,7 +868,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc class_desc->pop(); // list } - const String descr = DTR_DOC(method.description).strip_edges(); + const String descr = HANDLE_DOC(method.description); const bool is_documented = method.is_deprecated || method.is_experimental || !descr.is_empty(); if (!descr.is_empty()) { if (has_prev_text) { @@ -903,7 +915,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc } } -#undef DTR_DOC +#undef HANDLE_DOC } void EditorHelp::_update_doc() { @@ -922,7 +934,7 @@ void EditorHelp::_update_doc() { DocData::ClassDoc cd = doc->class_list[edited_class]; // Make a copy, so we can sort without worrying. -#define DTR_DOC(m_string) (cd.is_script_doc ? (m_string) : DTR(m_string)) +#define HANDLE_DOC(m_string) ((cd.is_script_doc ? (m_string) : DTR(m_string)).strip_edges()) // Class name @@ -940,12 +952,12 @@ void EditorHelp::_update_doc() { if (cd.is_deprecated) { class_desc->add_newline(); - DEPRECATED_DOC_MSG(DTR_DOC(cd.deprecated_message), TTR("This class may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(cd.deprecated_message), TTR("This class may be changed or removed in future versions.")); } if (cd.is_experimental) { class_desc->add_newline(); - EXPERIMENTAL_DOC_MSG(DTR_DOC(cd.experimental_message), TTR("This class may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(cd.experimental_message), TTR("This class may be changed or removed in future versions.")); } // Inheritance tree @@ -1003,7 +1015,7 @@ void EditorHelp::_update_doc() { bool has_description = false; // Brief description - const String brief_class_descr = DTR_DOC(cd.brief_description).strip_edges(); + const String brief_class_descr = HANDLE_DOC(cd.brief_description); if (!brief_class_descr.is_empty()) { has_description = true; @@ -1022,7 +1034,7 @@ void EditorHelp::_update_doc() { } // Class description - const String class_descr = DTR_DOC(cd.description).strip_edges(); + const String class_descr = HANDLE_DOC(cd.description); if (!class_descr.is_empty()) { has_description = true; @@ -1106,9 +1118,9 @@ void EditorHelp::_update_doc() { class_desc->push_color(theme_cache.symbol_color); for (const DocData::TutorialDoc &tutorial : cd.tutorials) { - const String link = DTR_DOC(tutorial.link).strip_edges(); + const String link = HANDLE_DOC(tutorial.link); - String link_text = DTR_DOC(tutorial.title).strip_edges(); + String link_text = HANDLE_DOC(tutorial.title); if (link_text.is_empty()) { const int sep_pos = link.find("//"); if (sep_pos >= 0) { @@ -1441,7 +1453,7 @@ void EditorHelp::_update_doc() { _push_normal_font(); class_desc->push_color(theme_cache.comment_color); - const String descr = DTR_DOC(theme_item.description).strip_edges(); + const String descr = HANDLE_DOC(theme_item.description); if (!descr.is_empty()) { _add_text(descr); } else { @@ -1538,13 +1550,13 @@ void EditorHelp::_update_doc() { _push_normal_font(); class_desc->push_color(theme_cache.comment_color); - const String descr = DTR_DOC(signal.description).strip_edges(); + const String descr = HANDLE_DOC(signal.description); const bool is_multiline = descr.find_char('\n') > 0; bool has_prev_text = false; if (signal.is_deprecated) { has_prev_text = true; - DEPRECATED_DOC_MSG(DTR_DOC(signal.deprecated_message), TTR("This signal may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(signal.deprecated_message), TTR("This signal may be changed or removed in future versions.")); } if (signal.is_experimental) { @@ -1555,7 +1567,7 @@ void EditorHelp::_update_doc() { } } has_prev_text = true; - EXPERIMENTAL_DOC_MSG(DTR_DOC(signal.experimental_message), TTR("This signal may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(signal.experimental_message), TTR("This signal may be changed or removed in future versions.")); } if (!descr.is_empty()) { @@ -1669,7 +1681,7 @@ void EditorHelp::_update_doc() { // Enum description. if (key != "@unnamed_enums" && cd.enums.has(key)) { - const String descr = DTR_DOC(cd.enums[key].description).strip_edges(); + const String descr = HANDLE_DOC(cd.enums[key].description); const bool is_multiline = descr.find_char('\n') > 0; if (cd.enums[key].is_deprecated || cd.enums[key].is_experimental || !descr.is_empty()) { class_desc->add_newline(); @@ -1682,7 +1694,7 @@ void EditorHelp::_update_doc() { if (cd.enums[key].is_deprecated) { has_prev_text = true; - DEPRECATED_DOC_MSG(DTR_DOC(cd.enums[key].deprecated_message), TTR("This enumeration may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(cd.enums[key].deprecated_message), TTR("This enumeration may be changed or removed in future versions.")); } if (cd.enums[key].is_experimental) { @@ -1693,7 +1705,7 @@ void EditorHelp::_update_doc() { } } has_prev_text = true; - EXPERIMENTAL_DOC_MSG(DTR_DOC(cd.enums[key].experimental_message), TTR("This enumeration may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(cd.enums[key].experimental_message), TTR("This enumeration may be changed or removed in future versions.")); } if (!descr.is_empty()) { @@ -1718,7 +1730,7 @@ void EditorHelp::_update_doc() { bool prev_is_multiline = true; // Use a large margin for the first item. for (const DocData::ConstantDoc &enum_value : E.value) { - const String descr = DTR_DOC(enum_value.description).strip_edges(); + const String descr = HANDLE_DOC(enum_value.description); const bool is_multiline = descr.find_char('\n') > 0; class_desc->add_newline(); @@ -1766,7 +1778,7 @@ void EditorHelp::_update_doc() { if (enum_value.is_deprecated) { has_prev_text = true; - DEPRECATED_DOC_MSG(DTR_DOC(enum_value.deprecated_message), TTR("This constant may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(enum_value.deprecated_message), TTR("This constant may be changed or removed in future versions.")); } if (enum_value.is_experimental) { @@ -1777,7 +1789,7 @@ void EditorHelp::_update_doc() { } } has_prev_text = true; - EXPERIMENTAL_DOC_MSG(DTR_DOC(enum_value.experimental_message), TTR("This constant may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(enum_value.experimental_message), TTR("This constant may be changed or removed in future versions.")); } if (!descr.is_empty()) { @@ -1817,7 +1829,7 @@ void EditorHelp::_update_doc() { bool prev_is_multiline = true; // Use a large margin for the first item. for (const DocData::ConstantDoc &constant : constants) { - const String descr = DTR_DOC(constant.description).strip_edges(); + const String descr = HANDLE_DOC(constant.description); const bool is_multiline = descr.find_char('\n') > 0; class_desc->add_newline(); @@ -1871,7 +1883,7 @@ void EditorHelp::_update_doc() { if (constant.is_deprecated) { has_prev_text = true; - DEPRECATED_DOC_MSG(DTR_DOC(constant.deprecated_message), TTR("This constant may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(constant.deprecated_message), TTR("This constant may be changed or removed in future versions.")); } if (constant.is_experimental) { @@ -1882,7 +1894,7 @@ void EditorHelp::_update_doc() { } } has_prev_text = true; - EXPERIMENTAL_DOC_MSG(DTR_DOC(constant.experimental_message), TTR("This constant may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(constant.experimental_message), TTR("This constant may be changed or removed in future versions.")); } if (!descr.is_empty()) { @@ -2000,7 +2012,7 @@ void EditorHelp::_update_doc() { _push_normal_font(); class_desc->push_color(theme_cache.comment_color); - const String descr = DTR_DOC(annotation.description).strip_edges(); + const String descr = HANDLE_DOC(annotation.description); if (!descr.is_empty()) { _add_text(descr); } else { @@ -2185,7 +2197,7 @@ void EditorHelp::_update_doc() { if (prop.is_deprecated) { has_prev_text = true; - DEPRECATED_DOC_MSG(DTR_DOC(prop.deprecated_message), TTR("This property may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(prop.deprecated_message), TTR("This property may be changed or removed in future versions.")); } if (prop.is_experimental) { @@ -2194,10 +2206,10 @@ void EditorHelp::_update_doc() { class_desc->add_newline(); } has_prev_text = true; - EXPERIMENTAL_DOC_MSG(DTR_DOC(prop.experimental_message), TTR("This property may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(prop.experimental_message), TTR("This property may be changed or removed in future versions.")); } - const String descr = DTR_DOC(prop.description).strip_edges(); + const String descr = HANDLE_DOC(prop.description); if (!descr.is_empty()) { if (has_prev_text) { class_desc->add_newline(); @@ -2251,7 +2263,7 @@ void EditorHelp::_update_doc() { // Free the scroll. scroll_locked = false; -#undef DTR_DOC +#undef HANDLE_DOC } void EditorHelp::_request_help(const String &p_string) { @@ -2331,7 +2343,7 @@ void EditorHelp::_help_callback(const String &p_topic) { } } -static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control *p_owner_node, const String &p_class) { +static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const Control *p_owner_node, const String &p_class) { const DocTools *doc = EditorHelp::get_doc_data(); bool is_native = false; @@ -2452,7 +2464,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control const String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1); if (tag.begins_with("/")) { - bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1, tag.length()); + bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1); if (!tag_ok) { p_rt->add_text("["); @@ -2467,8 +2479,8 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control } } else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("annotation ") || tag.begins_with("theme_item ")) { const int tag_end = tag.find_char(' '); - const String link_tag = tag.substr(0, tag_end); - const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" "); + const String link_tag = tag.left(tag_end); + const String link_target = tag.substr(tag_end + 1).lstrip(" "); Color target_color = link_color; RichTextLabel::MetaUnderline underline_mode = RichTextLabel::META_UNDERLINE_ON_HOVER; @@ -2520,7 +2532,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control pos = brk_end + 1; } else if (tag.begins_with("param ")) { const int tag_end = tag.find_char(' '); - const String param_name = tag.substr(tag_end + 1, tag.length()).lstrip(" "); + const String param_name = tag.substr(tag_end + 1).lstrip(" "); // Use monospace font with translucent background color to make code easier to distinguish from other text. p_rt->push_font(doc_code_font); @@ -2741,7 +2753,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("url=")) { - String url = tag.substr(4, tag.length()); + String url = tag.substr(4); p_rt->push_meta(url); pos = brk_end + 1; @@ -2751,13 +2763,13 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control int height = 0; bool size_in_percent = false; if (tag.length() > 4) { - Vector<String> subtags = tag.substr(4, tag.length()).split(" "); + Vector<String> subtags = tag.substr(4).split(" "); HashMap<String, String> bbcode_options; for (int i = 0; i < subtags.size(); i++) { const String &expr = subtags[i]; int value_pos = expr.find_char('='); if (value_pos > -1) { - bbcode_options[expr.substr(0, value_pos)] = expr.substr(value_pos + 1).unquote(); + bbcode_options[expr.left(value_pos)] = expr.substr(value_pos + 1).unquote(); } } HashMap<String, String>::Iterator width_option = bbcode_options.find("width"); @@ -2787,14 +2799,14 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control pos = end; tag_stack.push_front("img"); } else if (tag.begins_with("color=")) { - String col = tag.substr(6, tag.length()); + String col = tag.substr(6); Color color = Color::from_string(col, Color()); p_rt->push_color(color); pos = brk_end + 1; tag_stack.push_front("color"); } else if (tag.begins_with("font=")) { - String font_path = tag.substr(5, tag.length()); + String font_path = tag.substr(5); Ref<Font> font = ResourceLoader::load(font_path, "Font"); if (font.is_valid()) { p_rt->push_font(font); @@ -3120,68 +3132,50 @@ DocTools *EditorHelp::get_doc_data() { /// EditorHelpBit /// -void EditorHelpBit::_go_to_help(const String &p_what) { - EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); - ScriptEditor::get_singleton()->goto_help(p_what); - emit_signal(SNAME("request_hide")); -} +#define HANDLE_DOC(m_string) ((is_native ? DTR(m_string) : (m_string)).strip_edges()) -void EditorHelpBit::_meta_clicked(const String &p_select) { - if (p_select.begins_with("$")) { // enum - String select = p_select.substr(1, p_select.length()); - String class_name; - int rfind = select.rfind("."); - if (rfind != -1) { - class_name = select.substr(0, rfind); - select = select.substr(rfind + 1); - } else { - class_name = "@GlobalScope"; - } - _go_to_help("class_enum:" + class_name + ":" + select); - return; - } else if (p_select.begins_with("#")) { - _go_to_help("class_name:" + p_select.substr(1, p_select.length())); - return; - } else if (p_select.begins_with("@")) { - String m = p_select.substr(1, p_select.length()); - - if (m.contains(".")) { - _go_to_help("class_method:" + m.get_slice(".", 0) + ":" + m.get_slice(".", 0)); // Must go somewhere else. - } - } -} - -String EditorHelpBit::get_class_description(const StringName &p_class_name) const { +EditorHelpBit::HelpData EditorHelpBit::_get_class_help_data(const StringName &p_class_name) { if (doc_class_cache.has(p_class_name)) { return doc_class_cache[p_class_name]; } - String description; + HelpData result; const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); if (E) { // Non-native class shouldn't be cached, nor translated. const bool is_native = !E->value.is_script_doc; - description = is_native ? DTR(E->value.brief_description) : E->value.brief_description; + + result.description = HANDLE_DOC(E->value.brief_description); + if (E->value.is_deprecated) { + if (E->value.deprecated_message.is_empty()) { + result.deprecated_message = TTR("This class may be changed or removed in future versions."); + } else { + result.deprecated_message = HANDLE_DOC(E->value.deprecated_message); + } + } + if (E->value.is_experimental) { + if (E->value.experimental_message.is_empty()) { + result.experimental_message = TTR("This class may be changed or removed in future versions."); + } else { + result.experimental_message = HANDLE_DOC(E->value.experimental_message); + } + } if (is_native) { - doc_class_cache[p_class_name] = description; + doc_class_cache[p_class_name] = result; } } - return description; + return result; } -String EditorHelpBit::get_property_description(const StringName &p_class_name, const StringName &p_property_name) const { - if (!custom_description.is_empty()) { - return custom_description; - } - +EditorHelpBit::HelpData EditorHelpBit::_get_property_help_data(const StringName &p_class_name, const StringName &p_property_name) { if (doc_property_cache.has(p_class_name) && doc_property_cache[p_class_name].has(p_property_name)) { return doc_property_cache[p_class_name][p_property_name]; } - String description; + HelpData result; const DocTools *dd = EditorHelp::get_doc_data(); const HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name); @@ -3190,7 +3184,22 @@ String EditorHelpBit::get_property_description(const StringName &p_class_name, c const bool is_native = !E->value.is_script_doc; for (const DocData::PropertyDoc &property : E->value.properties) { - String description_current = is_native ? DTR(property.description) : property.description; + HelpData current; + current.description = HANDLE_DOC(property.description); + if (property.is_deprecated) { + if (property.deprecated_message.is_empty()) { + current.deprecated_message = TTR("This property may be changed or removed in future versions."); + } else { + current.deprecated_message = HANDLE_DOC(property.deprecated_message); + } + } + if (property.is_experimental) { + if (property.experimental_message.is_empty()) { + current.experimental_message = TTR("This property may be changed or removed in future versions."); + } else { + current.experimental_message = HANDLE_DOC(property.experimental_message); + } + } String enum_class_name; String enum_name; @@ -3215,18 +3224,19 @@ String EditorHelpBit::get_property_description(const StringName &p_class_name, c if (constant.enumeration == enum_name && !constant.name.ends_with("_MAX")) { // Prettify the enum value display, so that "<ENUM_NAME>_<ITEM>" becomes "Item". const String item_name = EditorPropertyNameProcessor::get_singleton()->process_name(constant.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED).trim_prefix(enum_prefix); - String item_descr = (is_native ? DTR(constant.description) : constant.description).strip_edges(); + String item_descr = HANDLE_DOC(constant.description); if (item_descr.is_empty()) { - item_descr = ("[i]" + DTR("No description available.") + "[/i]"); + item_descr = "[color=<EditorHelpBitCommentColor>][i]" + TTR("No description available.") + "[/i][/color]"; } - description_current += vformat("\n[b]%s:[/b] %s", item_name, item_descr); + current.description += vformat("\n[b]%s:[/b] %s", item_name, item_descr); } } + current.description = current.description.lstrip("\n"); } } if (property.name == p_property_name) { - description = description_current; + result = current; if (!is_native) { break; @@ -3234,20 +3244,20 @@ String EditorHelpBit::get_property_description(const StringName &p_class_name, c } if (is_native) { - doc_property_cache[p_class_name][property.name] = description_current; + doc_property_cache[p_class_name][property.name] = current; } } } - return description; + return result; } -String EditorHelpBit::get_method_description(const StringName &p_class_name, const StringName &p_method_name) const { +EditorHelpBit::HelpData EditorHelpBit::_get_method_help_data(const StringName &p_class_name, const StringName &p_method_name) { if (doc_method_cache.has(p_class_name) && doc_method_cache[p_class_name].has(p_method_name)) { return doc_method_cache[p_class_name][p_method_name]; } - String description; + HelpData result; const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); if (E) { @@ -3255,10 +3265,30 @@ String EditorHelpBit::get_method_description(const StringName &p_class_name, con const bool is_native = !E->value.is_script_doc; for (const DocData::MethodDoc &method : E->value.methods) { - String description_current = is_native ? DTR(method.description) : method.description; + HelpData current; + current.description = HANDLE_DOC(method.description); + if (method.is_deprecated) { + if (method.deprecated_message.is_empty()) { + current.deprecated_message = TTR("This method may be changed or removed in future versions."); + } else { + current.deprecated_message = HANDLE_DOC(method.deprecated_message); + } + } + if (method.is_experimental) { + if (method.experimental_message.is_empty()) { + current.experimental_message = TTR("This method may be changed or removed in future versions."); + } else { + current.experimental_message = HANDLE_DOC(method.experimental_message); + } + } + current.doc_type = { method.return_type, method.return_enum, method.return_is_bitfield }; + for (const DocData::ArgumentDoc &argument : method.arguments) { + const DocType argument_type = { argument.type, argument.enumeration, argument.is_bitfield }; + current.arguments.push_back({ argument.name, argument_type, argument.default_value }); + } if (method.name == p_method_name) { - description = description_current; + result = current; if (!is_native) { break; @@ -3266,20 +3296,20 @@ String EditorHelpBit::get_method_description(const StringName &p_class_name, con } if (is_native) { - doc_method_cache[p_class_name][method.name] = description_current; + doc_method_cache[p_class_name][method.name] = current; } } } - return description; + return result; } -String EditorHelpBit::get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const { +EditorHelpBit::HelpData EditorHelpBit::_get_signal_help_data(const StringName &p_class_name, const StringName &p_signal_name) { if (doc_signal_cache.has(p_class_name) && doc_signal_cache[p_class_name].has(p_signal_name)) { return doc_signal_cache[p_class_name][p_signal_name]; } - String description; + HelpData result; const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); if (E) { @@ -3287,10 +3317,29 @@ String EditorHelpBit::get_signal_description(const StringName &p_class_name, con const bool is_native = !E->value.is_script_doc; for (const DocData::MethodDoc &signal : E->value.signals) { - String description_current = is_native ? DTR(signal.description) : signal.description; + HelpData current; + current.description = HANDLE_DOC(signal.description); + if (signal.is_deprecated) { + if (signal.deprecated_message.is_empty()) { + current.deprecated_message = TTR("This signal may be changed or removed in future versions."); + } else { + current.deprecated_message = HANDLE_DOC(signal.deprecated_message); + } + } + if (signal.is_experimental) { + if (signal.experimental_message.is_empty()) { + current.experimental_message = TTR("This signal may be changed or removed in future versions."); + } else { + current.experimental_message = HANDLE_DOC(signal.experimental_message); + } + } + for (const DocData::ArgumentDoc &argument : signal.arguments) { + const DocType argument_type = { argument.type, argument.enumeration, argument.is_bitfield }; + current.arguments.push_back({ argument.name, argument_type, argument.default_value }); + } if (signal.name == p_signal_name) { - description = description_current; + result = current; if (!is_native) { break; @@ -3298,20 +3347,20 @@ String EditorHelpBit::get_signal_description(const StringName &p_class_name, con } if (is_native) { - doc_signal_cache[p_class_name][signal.name] = description_current; + doc_signal_cache[p_class_name][signal.name] = current; } } } - return description; + return result; } -String EditorHelpBit::get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const { +EditorHelpBit::HelpData EditorHelpBit::_get_theme_item_help_data(const StringName &p_class_name, const StringName &p_theme_item_name) { if (doc_theme_item_cache.has(p_class_name) && doc_theme_item_cache[p_class_name].has(p_theme_item_name)) { return doc_theme_item_cache[p_class_name][p_theme_item_name]; } - String description; + HelpData result; bool found = false; const DocTools *dd = EditorHelp::get_doc_data(); @@ -3321,142 +3370,477 @@ String EditorHelpBit::get_theme_item_description(const StringName &p_class_name, const bool is_native = !E->value.is_script_doc; for (const DocData::ThemeItemDoc &theme_item : E->value.theme_properties) { - String description_current = is_native ? DTR(theme_item.description) : theme_item.description; + HelpData current; + current.description = HANDLE_DOC(theme_item.description); if (theme_item.name == p_theme_item_name) { - description = description_current; + result = current; found = true; - if (!is_native) { break; } } if (is_native) { - doc_theme_item_cache[p_class_name][theme_item.name] = description_current; + doc_theme_item_cache[p_class_name][theme_item.name] = current; } } if (found || E->value.inherits.is_empty()) { break; } + // Check for inherited theme items. E = dd->class_list.find(E->value.inherits); } - return description; + return result; +} + +#undef HANDLE_DOC + +void EditorHelpBit::_add_type_to_title(const DocType &p_doc_type) { + _add_type_to_rt(p_doc_type.type, p_doc_type.enumeration, p_doc_type.is_bitfield, title, this, symbol_class_name); +} + +void EditorHelpBit::_update_labels() { + const Ref<Font> doc_bold_font = get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts)); + + if (!symbol_visible_type.is_empty() || !symbol_name.is_empty()) { + title->clear(); + + title->push_font(doc_bold_font); + + if (!symbol_visible_type.is_empty()) { + title->push_color(get_theme_color(SNAME("title_color"), SNAME("EditorHelp"))); + title->add_text(symbol_visible_type); + title->pop(); // color + } + + if (!symbol_visible_type.is_empty() && !symbol_name.is_empty()) { + title->add_text(" "); + } + + if (!symbol_name.is_empty()) { + title->push_underline(); + title->add_text(symbol_name); + title->pop(); // underline + } + + title->pop(); // font + + if (symbol_type == "method" || symbol_type == "signal") { + const Color symbol_color = get_theme_color(SNAME("symbol_color"), SNAME("EditorHelp")); + const Color value_color = get_theme_color(SNAME("value_color"), SNAME("EditorHelp")); + + title->push_font(get_theme_font(SNAME("doc_source"), EditorStringName(EditorFonts))); + title->push_font_size(get_theme_font_size(SNAME("doc_source_size"), EditorStringName(EditorFonts)) * 0.9); + + title->push_color(symbol_color); + title->add_text("("); + title->pop(); // color + + for (int i = 0; i < help_data.arguments.size(); i++) { + const ArgumentData &argument = help_data.arguments[i]; + + if (i > 0) { + title->push_color(symbol_color); + title->add_text(", "); + title->pop(); // color + } + + title->add_text(argument.name); + + title->push_color(symbol_color); + title->add_text(": "); + title->pop(); // color + + _add_type_to_title(argument.doc_type); + + if (!argument.default_value.is_empty()) { + title->push_color(symbol_color); + title->add_text(" = "); + title->pop(); // color + + title->push_color(value_color); + title->add_text(argument.default_value); + title->pop(); // color + } + } + + title->push_color(symbol_color); + title->add_text(")"); + title->pop(); // color + + if (symbol_type == "method") { + title->push_color(symbol_color); + title->add_text(" -> "); + title->pop(); // color + + _add_type_to_title(help_data.doc_type); + } + + title->pop(); // font_size + title->pop(); // font + } + + title->show(); + } else { + title->hide(); + } + + content->clear(); + + bool has_prev_text = false; + + if (!help_data.deprecated_message.is_empty()) { + has_prev_text = true; + + Ref<Texture2D> error_icon = get_editor_theme_icon(SNAME("StatusError")); + content->add_image(error_icon, error_icon->get_width(), error_icon->get_height()); + content->add_text(" "); + content->push_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor))); + content->push_font(doc_bold_font); + content->add_text(TTR("Deprecated:")); + content->pop(); // font + content->pop(); // color + content->add_text(" "); + _add_text_to_rt(help_data.deprecated_message, content, this, symbol_class_name); + } + + if (!help_data.experimental_message.is_empty()) { + if (has_prev_text) { + content->add_newline(); + content->add_newline(); + } + has_prev_text = true; + + Ref<Texture2D> warning_icon = get_editor_theme_icon(SNAME("NodeWarning")); + content->add_image(warning_icon, warning_icon->get_width(), warning_icon->get_height()); + content->add_text(" "); + content->push_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor))); + content->push_font(doc_bold_font); + content->add_text(TTR("Experimental:")); + content->pop(); // font + content->pop(); // color + content->add_text(" "); + _add_text_to_rt(help_data.experimental_message, content, this, symbol_class_name); + } + + if (!help_data.description.is_empty()) { + if (has_prev_text) { + content->add_newline(); + content->add_newline(); + } + has_prev_text = true; + + const Color comment_color = get_theme_color(SNAME("comment_color"), SNAME("EditorHelp")); + _add_text_to_rt(help_data.description.replace("<EditorHelpBitCommentColor>", comment_color.to_html()), content, this, symbol_class_name); + } + + if (is_inside_tree()) { + update_content_height(); + } +} + +void EditorHelpBit::_go_to_help(const String &p_what) { + EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); + ScriptEditor::get_singleton()->goto_help(p_what); + emit_signal(SNAME("request_hide")); +} + +void EditorHelpBit::_meta_clicked(const String &p_select) { + if (p_select.begins_with("$")) { // Enum. + const String link = p_select.substr(1); + + String enum_class_name; + String enum_name; + if (CoreConstants::is_global_enum(link)) { + enum_class_name = "@GlobalScope"; + enum_name = link; + } else { + const int dot_pos = link.rfind("."); + if (dot_pos >= 0) { + enum_class_name = link.left(dot_pos); + enum_name = link.substr(dot_pos + 1); + } else { + enum_class_name = symbol_class_name; + enum_name = link; + } + } + + _go_to_help("class_enum:" + enum_class_name + ":" + enum_name); + } else if (p_select.begins_with("#")) { // Class. + _go_to_help("class_name:" + p_select.substr(1)); + } else if (p_select.begins_with("@")) { // Member. + const int tag_end = p_select.find_char(' '); + const String tag = p_select.substr(1, tag_end - 1); + const String link = p_select.substr(tag_end + 1).lstrip(" "); + + String topic; + if (tag == "method") { + topic = "class_method"; + } else if (tag == "constructor") { + topic = "class_method"; + } else if (tag == "operator") { + topic = "class_method"; + } else if (tag == "member") { + topic = "class_property"; + } else if (tag == "enum") { + topic = "class_enum"; + } else if (tag == "signal") { + topic = "class_signal"; + } else if (tag == "constant") { + topic = "class_constant"; + } else if (tag == "annotation") { + topic = "class_annotation"; + } else if (tag == "theme_item") { + topic = "class_theme_item"; + } else { + return; + } + + if (link.contains(".")) { + const int class_end = link.find_char('.'); + _go_to_help(topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1)); + } else { + _go_to_help(topic + ":" + symbol_class_name + ":" + link); + } + } else if (p_select.begins_with("http:") || p_select.begins_with("https:")) { + OS::get_singleton()->shell_open(p_select); + } else if (p_select.begins_with("^")) { // Copy button. + DisplayServer::get_singleton()->clipboard_set(p_select.substr(1)); + } } void EditorHelpBit::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_text", "text"), &EditorHelpBit::set_text); ADD_SIGNAL(MethodInfo("request_hide")); } void EditorHelpBit::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_THEME_CHANGED: { - rich_text->add_theme_color_override("selection_color", get_theme_color(SNAME("selection_color"), SNAME("EditorHelp"))); - rich_text->clear(); - _add_text_to_rt(text, rich_text, this, doc_class_name); - rich_text->reset_size(); // Force recalculating size after parsing bbcode. - } break; + case NOTIFICATION_THEME_CHANGED: + _update_labels(); + break; } } -void EditorHelpBit::set_text(const String &p_text) { - text = p_text; - rich_text->clear(); - _add_text_to_rt(text, rich_text, this, doc_class_name); +void EditorHelpBit::parse_symbol(const String &p_symbol) { + const PackedStringArray slices = p_symbol.split("|", true, 2); + ERR_FAIL_COND_MSG(slices.size() < 3, "Invalid doc id. The expected format is 'item_type|class_name|item_name'."); + + const String &item_type = slices[0]; + const String &class_name = slices[1]; + const String &item_name = slices[2]; + + String visible_type; + String name = item_name; + + if (item_type == "class") { + visible_type = TTR("Class:"); + name = class_name; + help_data = _get_class_help_data(class_name); + } else if (item_type == "property") { + if (name.begins_with("metadata/")) { + visible_type = TTR("Metadata:"); + name = name.trim_prefix("metadata/"); + } else if (class_name == "ProjectSettings" || class_name == "EditorSettings") { + visible_type = TTR("Setting:"); + } else { + visible_type = TTR("Property:"); + } + help_data = _get_property_help_data(class_name, item_name); + } else if (item_type == "internal_property") { + visible_type = TTR("Internal Property:"); + help_data = HelpData(); + help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("This property can only be set in the Inspector.") + "[/i][/color]"; + } else if (item_type == "method") { + visible_type = TTR("Method:"); + help_data = _get_method_help_data(class_name, item_name); + } else if (item_type == "signal") { + visible_type = TTR("Signal:"); + help_data = _get_signal_help_data(class_name, item_name); + } else if (item_type == "theme_item") { + visible_type = TTR("Theme Property:"); + help_data = _get_theme_item_help_data(class_name, item_name); + } else { + ERR_FAIL_MSG("Invalid tooltip type '" + item_type + "'. Valid types are 'class', 'property', 'internal_property', 'method', 'signal', and 'theme_item'."); + } + + symbol_class_name = class_name; + symbol_type = item_type; + symbol_visible_type = visible_type; + symbol_name = name; + + if (help_data.description.is_empty()) { + help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("No description available.") + "[/i][/color]"; + } + + if (is_inside_tree()) { + _update_labels(); + } } -EditorHelpBit::EditorHelpBit() { - rich_text = memnew(RichTextLabel); - add_child(rich_text); - rich_text->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); - rich_text->set_fit_content(true); - set_custom_minimum_size(Size2(0, 50 * EDSCALE)); +void EditorHelpBit::set_custom_text(const String &p_type, const String &p_name, const String &p_description) { + symbol_class_name = String(); + symbol_type = String(); + symbol_visible_type = p_type; + symbol_name = p_name; + + help_data = HelpData(); + help_data.description = p_description; + + if (is_inside_tree()) { + _update_labels(); + } } -/// EditorHelpTooltip /// +void EditorHelpBit::prepend_description(const String &p_text) { + if (help_data.description.is_empty()) { + help_data.description = p_text; + } else { + help_data.description = p_text + "\n" + help_data.description; + } -void EditorHelpTooltip::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_POSTINITIALIZE: { - if (!tooltip_text.is_empty()) { - parse_tooltip(tooltip_text); - } - } break; + if (is_inside_tree()) { + _update_labels(); } } -// `p_text` is expected to be something like these: -// - `class|Control||`; -// - `property|Control|size|`; -// - `signal|Control|gui_input|(event: InputEvent)`. -void EditorHelpTooltip::parse_tooltip(const String &p_text) { - tooltip_text = p_text; +void EditorHelpBit::set_content_height_limits(float p_min, float p_max) { + ERR_FAIL_COND(p_min > p_max); + content_min_height = p_min; + content_max_height = p_max; - PackedStringArray slices = p_text.split("|", true, 3); - ERR_FAIL_COND_MSG(slices.size() < 4, "Invalid tooltip formatting. The expect string should be formatted as 'type|class|property|args'."); + if (is_inside_tree()) { + update_content_height(); + } +} - const String &type = slices[0]; - const String &class_name = slices[1]; - const String &property_name = slices[2]; - const String &property_args = slices[3]; +void EditorHelpBit::update_content_height() { + float content_height = content->get_content_height(); + const Ref<StyleBox> style = content->get_theme_stylebox("normal"); + if (style.is_valid()) { + content_height += style->get_content_margin(SIDE_TOP) + style->get_content_margin(SIDE_BOTTOM); + } + content->set_custom_minimum_size(Size2(content->get_custom_minimum_size().x, CLAMP(content_height, content_min_height, content_max_height))); +} - doc_class_name = class_name; +EditorHelpBit::EditorHelpBit(const String &p_symbol) { + add_theme_constant_override("separation", 0); + + title = memnew(RichTextLabel); + title->set_theme_type_variation("EditorHelpBitTitle"); + title->set_fit_content(true); + title->set_selection_enabled(true); + //title->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip. + title->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); + title->hide(); + add_child(title); + + content_min_height = 48 * EDSCALE; + content_max_height = 360 * EDSCALE; + + content = memnew(RichTextLabel); + content->set_theme_type_variation("EditorHelpBitContent"); + content->set_custom_minimum_size(Size2(512 * EDSCALE, content_min_height)); + content->set_selection_enabled(true); + //content->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip. + content->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); + add_child(content); + + if (!p_symbol.is_empty()) { + parse_symbol(p_symbol); + } +} - String formatted_text; +/// EditorHelpBitTooltip /// - // Exclude internal properties, they are not documented. - if (type == "internal_property") { - formatted_text = "[i]" + TTR("This property can only be set in the Inspector.") + "[/i]"; - set_text(formatted_text); - return; +void EditorHelpBitTooltip::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_WM_MOUSE_ENTER: + timer->stop(); + break; + case NOTIFICATION_WM_MOUSE_EXIT: + timer->start(); + break; } +} - String title; - String description; +// Forwards non-mouse input to the parent viewport. +void EditorHelpBitTooltip::_input_from_window(const Ref<InputEvent> &p_event) { + if (p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) { + hide(); // Will be deleted on its timer. + } else { + const Ref<InputEventMouse> mouse_event = p_event; + if (mouse_event.is_null()) { + get_parent_viewport()->push_input(p_event); + } + } +} + +void EditorHelpBitTooltip::show_tooltip(EditorHelpBit *p_help_bit, Control *p_target) { + ERR_FAIL_NULL(p_help_bit); + EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target)); + p_help_bit->connect("request_hide", callable_mp(static_cast<Window *>(tooltip), &Window::hide)); // Will be deleted on its timer. + tooltip->add_child(p_help_bit); + p_target->get_viewport()->add_child(tooltip); + p_help_bit->update_content_height(); + tooltip->popup_under_cursor(); +} - if (type == "class") { - title = class_name; - description = get_class_description(class_name); - formatted_text = TTR("Class:"); +// Copy-paste from `Viewport::_gui_show_tooltip()`. +void EditorHelpBitTooltip::popup_under_cursor() { + Point2 mouse_pos = get_mouse_position(); + Point2 tooltip_offset = GLOBAL_GET("display/mouse_cursor/tooltip_position_offset"); + Rect2 r(mouse_pos + tooltip_offset, get_contents_minimum_size()); + r.size = r.size.min(get_max_size()); + + Window *window = get_parent_visible_window(); + Rect2i vr; + if (is_embedded()) { + vr = get_embedder()->get_visible_rect(); } else { - title = property_name; + vr = window->get_usable_parent_rect(); + } - if (type == "property") { - description = get_property_description(class_name, property_name); - if (property_name.begins_with("metadata/")) { - formatted_text = TTR("Metadata:"); - } else { - formatted_text = TTR("Property:"); - } - } else if (type == "method") { - description = get_method_description(class_name, property_name); - formatted_text = TTR("Method:"); - } else if (type == "signal") { - description = get_signal_description(class_name, property_name); - formatted_text = TTR("Signal:"); - } else if (type == "theme_item") { - description = get_theme_item_description(class_name, property_name); - formatted_text = TTR("Theme Property:"); - } else { - ERR_FAIL_MSG("Invalid tooltip type '" + type + "'. Valid types are 'class', 'property', 'method', 'signal', and 'theme_item'."); + if (r.size.x + r.position.x > vr.size.x + vr.position.x) { + // Place it in the opposite direction. If it fails, just hug the border. + r.position.x = mouse_pos.x - r.size.x - tooltip_offset.x; + + if (r.position.x < vr.position.x) { + r.position.x = vr.position.x + vr.size.x - r.size.x; + } + } else if (r.position.x < vr.position.x) { + r.position.x = vr.position.x; + } + + if (r.size.y + r.position.y > vr.size.y + vr.position.y) { + // Same as above. + r.position.y = mouse_pos.y - r.size.y - tooltip_offset.y; + + if (r.position.y < vr.position.y) { + r.position.y = vr.position.y + vr.size.y - r.size.y; } + } else if (r.position.y < vr.position.y) { + r.position.y = vr.position.y; } - // Metadata special handling replaces "Property:" with "Metadata": above. - formatted_text += " [u][b]" + title.trim_prefix("metadata/") + "[/b][/u]" + property_args.replace("[", "[lb]") + "\n"; - formatted_text += description.is_empty() ? "[i]" + TTR("No description available.") + "[/i]" : description; - set_text(formatted_text); + set_flag(Window::FLAG_NO_FOCUS, true); + popup(r); } -EditorHelpTooltip::EditorHelpTooltip(const String &p_text, const String &p_custom_description) { - tooltip_text = p_text; - custom_description = p_custom_description; +EditorHelpBitTooltip::EditorHelpBitTooltip(Control *p_target) { + set_theme_type_variation("TooltipPanel"); + + timer = memnew(Timer); + timer->set_wait_time(0.2); + timer->connect("timeout", callable_mp(static_cast<Node *>(this), &Node::queue_free)); + add_child(timer); - get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0)); + ERR_FAIL_NULL(p_target); + p_target->connect("mouse_entered", callable_mp(timer, &Timer::stop)); + p_target->connect("mouse_exited", callable_mp(timer, &Timer::start).bind(-1)); } #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) diff --git a/editor/editor_help.h b/editor/editor_help.h index f8686b964a..078b42b439 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -35,9 +35,9 @@ #include "editor/code_editor.h" #include "editor/doc_tools.h" #include "editor/editor_plugin.h" -#include "scene/gui/margin_container.h" #include "scene/gui/menu_button.h" #include "scene/gui/panel_container.h" +#include "scene/gui/popup.h" #include "scene/gui/rich_text_label.h" #include "scene/gui/split_container.h" #include "scene/gui/tab_container.h" @@ -251,53 +251,91 @@ public: ~EditorHelp(); }; -class EditorHelpBit : public MarginContainer { - GDCLASS(EditorHelpBit, MarginContainer); +class EditorHelpBit : public VBoxContainer { + GDCLASS(EditorHelpBit, VBoxContainer); - inline static HashMap<StringName, String> doc_class_cache; - inline static HashMap<StringName, HashMap<StringName, String>> doc_property_cache; - inline static HashMap<StringName, HashMap<StringName, String>> doc_method_cache; - inline static HashMap<StringName, HashMap<StringName, String>> doc_signal_cache; - inline static HashMap<StringName, HashMap<StringName, String>> doc_theme_item_cache; + struct DocType { + String type; + String enumeration; + bool is_bitfield = false; + }; + + struct ArgumentData { + String name; + DocType doc_type; + String default_value; + }; + + struct HelpData { + String description; + String deprecated_message; + String experimental_message; + DocType doc_type; // For method return type. + Vector<ArgumentData> arguments; // For methods and signals. + }; + + inline static HashMap<StringName, HelpData> doc_class_cache; + inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_property_cache; + inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_method_cache; + inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_signal_cache; + inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_theme_item_cache; + + RichTextLabel *title = nullptr; + RichTextLabel *content = nullptr; - RichTextLabel *rich_text = nullptr; + String symbol_class_name; + String symbol_type; + String symbol_visible_type; + String symbol_name; + + HelpData help_data; + + float content_min_height = 0.0; + float content_max_height = 0.0; + + static HelpData _get_class_help_data(const StringName &p_class_name); + static HelpData _get_property_help_data(const StringName &p_class_name, const StringName &p_property_name); + static HelpData _get_method_help_data(const StringName &p_class_name, const StringName &p_method_name); + static HelpData _get_signal_help_data(const StringName &p_class_name, const StringName &p_signal_name); + static HelpData _get_theme_item_help_data(const StringName &p_class_name, const StringName &p_theme_item_name); + + void _add_type_to_title(const DocType &p_doc_type); + void _update_labels(); void _go_to_help(const String &p_what); void _meta_clicked(const String &p_select); - String text; - protected: - String doc_class_name; - String custom_description; - static void _bind_methods(); void _notification(int p_what); public: - String get_class_description(const StringName &p_class_name) const; - String get_property_description(const StringName &p_class_name, const StringName &p_property_name) const; - String get_method_description(const StringName &p_class_name, const StringName &p_method_name) const; - String get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const; - String get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const; + void parse_symbol(const String &p_symbol); + void set_custom_text(const String &p_type, const String &p_name, const String &p_description); + void prepend_description(const String &p_text); - RichTextLabel *get_rich_text() { return rich_text; } - void set_text(const String &p_text); + void set_content_height_limits(float p_min, float p_max); + void update_content_height(); - EditorHelpBit(); + EditorHelpBit(const String &p_symbol = String()); }; -class EditorHelpTooltip : public EditorHelpBit { - GDCLASS(EditorHelpTooltip, EditorHelpBit); +// Standard tooltips do not allow you to hover over them. +// This class is intended as a temporary workaround. +class EditorHelpBitTooltip : public PopupPanel { + GDCLASS(EditorHelpBitTooltip, PopupPanel); - String tooltip_text; + Timer *timer = nullptr; protected: void _notification(int p_what); + virtual void _input_from_window(const Ref<InputEvent> &p_event) override; public: - void parse_tooltip(const String &p_text); + static void show_tooltip(EditorHelpBit *p_help_bit, Control *p_target); + + void popup_under_cursor(); - EditorHelpTooltip(const String &p_text = String(), const String &p_custom_description = String()); + EditorHelpBitTooltip(Control *p_target); }; #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index aed1462eb6..50cc89c618 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -44,6 +44,7 @@ #include "editor/plugins/script_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "editor/themes/editor_theme_manager.h" +#include "scene/gui/margin_container.h" #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" #include "scene/property_utils.h" @@ -916,34 +917,35 @@ void EditorProperty::_update_pin_flags() { } Control *EditorProperty::make_custom_tooltip(const String &p_text) const { - EditorHelpBit *tooltip = nullptr; + String custom_warning; + if (object->has_method("_get_property_warning")) { + custom_warning = object->call("_get_property_warning", property); + } - if (has_doc_tooltip) { - String custom_description; + if (has_doc_tooltip || !custom_warning.is_empty()) { + EditorHelpBit *help_bit = memnew(EditorHelpBit); - const EditorInspector *inspector = get_parent_inspector(); - if (inspector) { - custom_description = inspector->get_custom_property_description(p_text); - } - tooltip = memnew(EditorHelpTooltip(p_text, custom_description)); - } + if (has_doc_tooltip) { + help_bit->parse_symbol(p_text); - if (object->has_method("_get_property_warning")) { - String warn = object->call("_get_property_warning", property); - if (!warn.is_empty()) { - String prev_text; - if (tooltip == nullptr) { - tooltip = memnew(EditorHelpBit()); - tooltip->set_text(p_text); - tooltip->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0)); - } else { - prev_text = tooltip->get_rich_text()->get_text() + "\n"; + const EditorInspector *inspector = get_parent_inspector(); + if (inspector) { + const String custom_description = inspector->get_custom_property_description(p_text); + if (!custom_description.is_empty()) { + help_bit->prepend_description(custom_description); + } } - tooltip->set_text(prev_text + "[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + warn + "[/color][/b]"); } + + if (!custom_warning.is_empty()) { + help_bit->prepend_description("[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + custom_warning + "[/color][/b]"); + } + + EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<EditorProperty *>(this)); + return memnew(Control); // Make the standard tooltip invisible. } - return tooltip; + return nullptr; } void EditorProperty::menu_option(int p_option) { @@ -1178,7 +1180,14 @@ void EditorInspectorCategory::_notification(int p_what) { } Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const { - return doc_class_name.is_empty() ? nullptr : memnew(EditorHelpTooltip(p_text)); + // If it's not a doc tooltip, fallback to the default one. + if (doc_class_name.is_empty()) { + return nullptr; + } + + EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text)); + EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<EditorInspectorCategory *>(this)); + return memnew(Control); // Make the standard tooltip invisible. } Size2 EditorInspectorCategory::get_minimum_size() const { @@ -2887,8 +2896,8 @@ void EditorInspector::update_tree() { category->doc_class_name = doc_name; if (use_doc_hints) { - // `|` separator used in `EditorHelpTooltip` for formatting. - category->set_tooltip_text("class|" + doc_name + "||"); + // `|` separators used in `EditorHelpBit`. + category->set_tooltip_text("class|" + doc_name + "|"); } } @@ -3368,15 +3377,15 @@ void EditorInspector::update_tree() { ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED); if (use_doc_hints) { - // `|` separator used in `EditorHelpTooltip` for formatting. + // `|` separators used in `EditorHelpBit`. if (theme_item_name.is_empty()) { if (p.usage & PROPERTY_USAGE_INTERNAL) { - ep->set_tooltip_text("internal_property|" + classname + "|" + property_prefix + p.name + "|"); + ep->set_tooltip_text("internal_property|" + classname + "|" + property_prefix + p.name); } else { - ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name + "|"); + ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name); } } else { - ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name + "|"); + ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name); } ep->has_doc_tooltip = true; } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index eff4f9caa5..cf3bf89e09 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -41,6 +41,7 @@ class ConfirmationDialog; class EditorInspector; class EditorValidationPanel; class LineEdit; +class MarginContainer; class OptionButton; class PanelContainer; class PopupMenu; diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp index 801c81efa8..be511452a6 100644 --- a/editor/editor_paths.cpp +++ b/editor/editor_paths.cpp @@ -70,6 +70,10 @@ String EditorPaths::get_export_templates_dir() const { return get_data_dir().path_join(export_templates_folder); } +String EditorPaths::get_debug_keystore_path() const { + return get_data_dir().path_join("keystores/debug.keystore"); +} + String EditorPaths::get_project_settings_dir() const { return get_project_data_dir().path_join("editor"); } diff --git a/editor/editor_paths.h b/editor/editor_paths.h index 547b93ad7e..a396c43301 100644 --- a/editor/editor_paths.h +++ b/editor/editor_paths.h @@ -63,6 +63,7 @@ public: String get_cache_dir() const; String get_project_data_dir() const; String get_export_templates_dir() const; + String get_debug_keystore_path() const; String get_project_settings_dir() const; String get_text_editor_themes_dir() const; String get_script_templates_dir() const; diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index c59c221e02..da072744b8 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -40,6 +40,7 @@ #include "editor/inspector_dock.h" #include "editor/themes/editor_scale.h" #include "scene/gui/button.h" +#include "scene/gui/margin_container.h" #include "scene/resources/packed_scene.h" bool EditorPropertyArrayObject::_set(const StringName &p_name, const Variant &p_value) { diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h index dae0fc52a6..b00f85c93c 100644 --- a/editor/editor_properties_array_dict.h +++ b/editor/editor_properties_array_dict.h @@ -37,6 +37,7 @@ class Button; class EditorSpinSlider; +class MarginContainer; class EditorPropertyArrayObject : public RefCounted { GDCLASS(EditorPropertyArrayObject, RefCounted); diff --git a/editor/editor_quick_open.cpp b/editor/editor_quick_open.cpp index 6fd1fd687b..f8df0f9fa8 100644 --- a/editor/editor_quick_open.cpp +++ b/editor/editor_quick_open.cpp @@ -91,17 +91,21 @@ void EditorQuickOpen::_build_search_cache(EditorFileSystemDirectory *p_efsd) { } void EditorQuickOpen::_update_search() { - const String search_text = search_box->get_text(); - const bool empty_search = search_text.is_empty(); + const PackedStringArray search_tokens = search_box->get_text().to_lower().replace("/", " ").split(" ", false); + const bool empty_search = search_tokens.is_empty(); // Filter possible candidates. Vector<Entry> entries; for (int i = 0; i < files.size(); i++) { - if (empty_search || search_text.is_subsequence_ofn(files[i])) { - Entry r; - r.path = files[i]; - r.score = empty_search ? 0 : _score_path(search_text, files[i].to_lower()); + Entry r; + r.path = files[i]; + if (empty_search) { entries.push_back(r); + } else { + r.score = _score_search_result(search_tokens, r.path.to_lower()); + if (r.score > 0) { + entries.push_back(r); + } } } @@ -135,23 +139,42 @@ void EditorQuickOpen::_update_search() { } } -float EditorQuickOpen::_score_path(const String &p_search, const String &p_path) { - float score = 0.9f + .1f * (p_search.length() / (float)p_path.length()); +float EditorQuickOpen::_score_search_result(const PackedStringArray &p_search_tokens, const String &p_path) { + float score = 0.0f; + int prev_min_match_idx = -1; - // Exact match. - if (p_search == p_path) { - return 1.2f; - } + for (const String &s : p_search_tokens) { + int min_match_idx = p_path.find(s); + + if (min_match_idx == -1) { + return 0.0f; + } + + float token_score = s.length(); + + int max_match_idx = p_path.rfind(s); + + // Prioritize the actual file name over folder. + if (max_match_idx > p_path.rfind("/")) { + token_score *= 2.0f; + } + + // Prioritize matches at the front of the path token. + if (min_match_idx == 0 || p_path.find("/" + s) != -1) { + token_score += 1.0f; + } + + score += token_score; + + // Prioritize tokens which appear in order. + if (prev_min_match_idx != -1 && max_match_idx > prev_min_match_idx) { + score += 1.0f; + } - // Positive bias for matches close to the beginning of the file name. - String file = p_path.get_file(); - int pos = file.findn(p_search); - if (pos != -1) { - return score * (1.0f - 0.1f * (float(pos) / file.length())); + prev_min_match_idx = min_match_idx; } - // Similarity - return p_path.to_lower().similarity(p_search.to_lower()); + return score; } void EditorQuickOpen::_confirmed() { diff --git a/editor/editor_quick_open.h b/editor/editor_quick_open.h index ec8ce0175e..bbc689040a 100644 --- a/editor/editor_quick_open.h +++ b/editor/editor_quick_open.h @@ -63,7 +63,7 @@ class EditorQuickOpen : public ConfirmationDialog { void _update_search(); void _build_search_cache(EditorFileSystemDirectory *p_efsd); - float _score_path(const String &p_search, const String &p_path); + float _score_search_result(const PackedStringArray &p_search_tokens, const String &p_path); void _confirmed(); virtual void cancel_pressed() override; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 452715a577..752b060513 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -690,6 +690,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/aabb", Color(0.28, 0.8, 0.82), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) // If a line is a multiple of this, it uses the primary grid color. // Use a power of 2 value by default as it's more common to use powers of 2 in level design. @@ -989,6 +990,29 @@ EditorSettings *EditorSettings::get_singleton() { return singleton.ptr(); } +String EditorSettings::get_existing_settings_path() { + const String config_dir = EditorPaths::get_singleton()->get_config_dir(); + int minor = VERSION_MINOR; + String filename; + + do { + if (VERSION_MAJOR == 4 && minor < 3) { + // Minor version is used since 4.3, so special case to load older settings. + filename = vformat("editor_settings-%d.tres", VERSION_MAJOR); + minor = -1; + } else { + filename = vformat("editor_settings-%d.%d.tres", VERSION_MAJOR, minor); + minor--; + } + } while (minor >= 0 && !FileAccess::exists(config_dir.path_join(filename))); + return config_dir.path_join(filename); +} + +String EditorSettings::get_newest_settings_path() { + const String config_file_name = vformat("editor_settings-%d.%d.tres", VERSION_MAJOR, VERSION_MINOR); + return EditorPaths::get_singleton()->get_config_dir().path_join(config_file_name); +} + void EditorSettings::create() { // IMPORTANT: create() *must* create a valid EditorSettings singleton, // as the rest of the engine code will assume it. As such, it should never @@ -1016,16 +1040,16 @@ void EditorSettings::create() { if (EditorPaths::get_singleton()->are_paths_valid()) { // Validate editor config file. - Ref<DirAccess> dir = DirAccess::open(EditorPaths::get_singleton()->get_config_dir()); - ERR_FAIL_COND(dir.is_null()); + ERR_FAIL_COND(!DirAccess::dir_exists_absolute(EditorPaths::get_singleton()->get_config_dir())); - String config_file_name = "editor_settings-" + itos(VERSION_MAJOR) + ".tres"; - config_file_path = EditorPaths::get_singleton()->get_config_dir().path_join(config_file_name); - if (!dir->file_exists(config_file_name)) { + config_file_path = get_existing_settings_path(); + if (!FileAccess::exists(config_file_path)) { + config_file_path = get_newest_settings_path(); goto fail; } singleton = ResourceLoader::load(config_file_path, "EditorSettings"); + singleton->set_path(get_newest_settings_path()); // Settings can be loaded from older version file, so make sure it's newest. if (singleton.is_null()) { ERR_PRINT("Could not load editor settings from path: " + config_file_path); diff --git a/editor/editor_settings.h b/editor/editor_settings.h index a058f91be8..6a329f6979 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -124,6 +124,8 @@ public: }; static EditorSettings *get_singleton(); + static String get_existing_settings_path(); + static String get_newest_settings_path(); static void create(); void setup_language(); diff --git a/editor/engine_update_label.cpp b/editor/engine_update_label.cpp index 0b20738e99..1d7df806de 100644 --- a/editor/engine_update_label.cpp +++ b/editor/engine_update_label.cpp @@ -180,22 +180,15 @@ void EngineUpdateLabel::_set_message(const String &p_message, const Color &p_col void EngineUpdateLabel::_set_status(UpdateStatus p_status) { status = p_status; - if (compact_mode) { - if (status != UpdateStatus::BUSY && status != UpdateStatus::UPDATE_AVAILABLE) { - hide(); - return; - } else { - show(); - } + if (status == UpdateStatus::DEV || status == UpdateStatus::BUSY || status == UpdateStatus::UP_TO_DATE) { + // Hide the label to prevent unnecessary distraction. + hide(); + return; + } else { + show(); } switch (status) { - case UpdateStatus::DEV: { - set_disabled(true); - _set_message(TTR("Running a development build."), theme_cache.disabled_color); - set_tooltip_text(TTR("Exact version can't be determined for update checking.")); - break; - } case UpdateStatus::OFFLINE: { set_disabled(false); if (int(EDITOR_GET("network/connection/network_mode")) == EditorSettings::NETWORK_OFFLINE) { @@ -206,23 +199,12 @@ void EngineUpdateLabel::_set_status(UpdateStatus p_status) { set_tooltip_text(""); break; } - case UpdateStatus::BUSY: { - set_disabled(true); - _set_message(TTR("Checking for updates..."), theme_cache.default_color); - set_tooltip_text(""); - } break; case UpdateStatus::ERROR: { set_disabled(false); set_tooltip_text(TTR("An error has occurred. Click to try again.")); } break; - case UpdateStatus::UP_TO_DATE: { - set_disabled(false); - _set_message(TTR("Current version up to date."), theme_cache.disabled_color); - set_tooltip_text(TTR("Click to check again.")); - } break; - case UpdateStatus::UPDATE_AVAILABLE: { set_disabled(false); set_tooltip_text(TTR("Click to open download page.")); @@ -315,8 +297,7 @@ void EngineUpdateLabel::pressed() { emit_signal("offline_clicked"); } break; - case UpdateStatus::ERROR: - case UpdateStatus::UP_TO_DATE: { + case UpdateStatus::ERROR: { _check_update(); } break; @@ -329,10 +310,6 @@ void EngineUpdateLabel::pressed() { } } -void EngineUpdateLabel::enable_compact_mode() { - compact_mode = true; -} - EngineUpdateLabel::EngineUpdateLabel() { set_underline_mode(UNDERLINE_MODE_ON_HOVER); diff --git a/editor/engine_update_label.h b/editor/engine_update_label.h index d00fe53e1e..3b852b8e53 100644 --- a/editor/engine_update_label.h +++ b/editor/engine_update_label.h @@ -76,7 +76,6 @@ private: } theme_cache; HTTPRequest *http = nullptr; - bool compact_mode = false; UpdateStatus status = UpdateStatus::NONE; bool checked_update = false; @@ -99,8 +98,6 @@ protected: virtual void pressed() override; public: - void enable_compact_mode(); - EngineUpdateLabel(); }; diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index 0f02ab5a7a..9a0f2f18fa 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/io/config_file.h" +#include "editor/editor_settings.h" EditorExport *EditorExport::singleton = nullptr; @@ -191,6 +192,12 @@ void EditorExport::_notification(int p_what) { export_platforms.write[i]->cleanup(); } } break; + + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + for (int i = 0; i < export_platforms.size(); i++) { + export_platforms.write[i]->notification(p_what); + } + } break; } } diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index 26e1f86c8b..3fd75ff67f 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -36,8 +36,8 @@ struct EditorProgress; #include "core/io/dir_access.h" #include "core/io/zip_io.h" +#include "core/os/shared_object.h" #include "editor_export_preset.h" -#include "editor_export_shared_object.h" #include "scene/gui/rich_text_label.h" #include "scene/main/node.h" #include "scene/resources/image_texture.h" diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index 5353eae654..28d0750d5a 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -60,6 +60,10 @@ void EditorExportPlugin::add_shared_object(const String &p_path, const Vector<St shared_objects.push_back(SharedObject(p_path, p_tags, p_target)); } +void EditorExportPlugin::_add_shared_object(const SharedObject &p_shared_object) { + shared_objects.push_back(p_shared_object); +} + void EditorExportPlugin::add_ios_framework(const String &p_path) { ios_frameworks.push_back(p_path); } diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h index a4e9917a81..56eea85010 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -32,9 +32,9 @@ #define EDITOR_EXPORT_PLUGIN_H #include "core/extension/gdextension.h" +#include "core/os/shared_object.h" #include "editor_export_platform.h" #include "editor_export_preset.h" -#include "editor_export_shared_object.h" #include "scene/main/node.h" class EditorExportPlugin : public RefCounted { @@ -94,6 +94,7 @@ protected: void add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap); void add_shared_object(const String &p_path, const Vector<String> &tags, const String &p_target = String()); + void _add_shared_object(const SharedObject &p_shared_object); void add_ios_framework(const String &p_path); void add_ios_embedded_framework(const String &p_path); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 2ed547a970..b7deb6afa6 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -59,8 +59,6 @@ #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/progress_bar.h" -#include "scene/gui/texture_rect.h" -#include "scene/main/window.h" #include "scene/resources/packed_scene.h" #include "servers/display_server.h" @@ -234,15 +232,15 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory Color custom_color = has_custom_color ? folder_colors[assigned_folder_colors[lpath]] : Color(); if (has_custom_color) { - subdirectory_item->set_icon_modulate(0, editor_is_dark_theme ? custom_color : custom_color * 1.75); - subdirectory_item->set_custom_bg_color(0, Color(custom_color, editor_is_dark_theme ? 0.1 : 0.15)); + subdirectory_item->set_icon_modulate(0, editor_is_dark_theme ? custom_color : custom_color * ITEM_COLOR_SCALE); + subdirectory_item->set_custom_bg_color(0, Color(custom_color, editor_is_dark_theme ? ITEM_ALPHA_MIN : ITEM_ALPHA_MAX)); } else { TreeItem *parent = subdirectory_item->get_parent(); if (parent) { Color parent_bg_color = parent->get_custom_bg_color(0); if (parent_bg_color != Color()) { bool parent_has_custom_color = assigned_folder_colors.has(parent->get_metadata(0)); - subdirectory_item->set_custom_bg_color(0, parent_has_custom_color ? parent_bg_color.darkened(0.3) : parent_bg_color); + subdirectory_item->set_custom_bg_color(0, parent_has_custom_color ? parent_bg_color.darkened(ITEM_BG_DARK_SCALE) : parent_bg_color); subdirectory_item->set_icon_modulate(0, parent->get_icon_modulate(0)); } else { subdirectory_item->set_icon_modulate(0, get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog"))); @@ -267,7 +265,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } else { subdirectory_item->set_collapsed(uncollapsed_paths.find(lpath) < 0); } - if (searched_string.length() > 0 && dname.to_lower().find(searched_string) >= 0) { + if (!searched_tokens.is_empty() && _matches_all_search_tokens(dname)) { parent_should_expand = true; } @@ -293,8 +291,8 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } String file_name = p_dir->get_file(i); - if (searched_string.length() > 0) { - if (file_name.to_lower().find(searched_string) < 0) { + if (!searched_tokens.is_empty()) { + if (!_matches_all_search_tokens(file_name)) { // The searched string is not in the file name, we skip it. continue; } else { @@ -328,7 +326,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory file_item->set_icon_max_width(0, icon_size); Color parent_bg_color = subdirectory_item->get_custom_bg_color(0); if (has_custom_color) { - file_item->set_custom_bg_color(0, parent_bg_color.darkened(0.3)); + file_item->set_custom_bg_color(0, parent_bg_color.darkened(ITEM_BG_DARK_SCALE)); } else if (parent_bg_color != Color()) { file_item->set_custom_bg_color(0, parent_bg_color); } @@ -352,7 +350,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } } - if (searched_string.length() > 0) { + if (!searched_tokens.is_empty()) { if (parent_should_expand) { subdirectory_item->set_collapsed(false); } else if (dname != "res://") { @@ -460,7 +458,7 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo color = Color(1, 1, 1); } - if (searched_string.length() == 0 || text.to_lower().find(searched_string) >= 0) { + if (searched_tokens.is_empty() || _matches_all_search_tokens(text)) { TreeItem *ti = tree->create_item(favorites_item); ti->set_text(0, text); ti->set_icon(0, icon); @@ -857,7 +855,7 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> * for (int i = 0; i < p_path->get_file_count(); i++) { String file = p_path->get_file(i); - if (file.to_lower().contains(searched_string)) { + if (_matches_all_search_tokens(file)) { FileInfo fi; fi.name = file; fi.type = p_path->get_file_type(i); @@ -984,14 +982,14 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { if (favorite == "res://") { text = "/"; icon = folder_icon; - if (searched_string.length() == 0 || text.to_lower().find(searched_string) >= 0) { + if (searched_tokens.is_empty() || _matches_all_search_tokens(text)) { files->add_item(text, icon, true); files->set_item_metadata(-1, favorite); } } else if (favorite.ends_with("/")) { text = favorite.substr(0, favorite.length() - 1).get_file(); icon = folder_icon; - if (searched_string.length() == 0 || text.to_lower().find(searched_string) >= 0) { + if (searched_tokens.is_empty() || _matches_all_search_tokens(text)) { files->add_item(text, icon, true); files->set_item_metadata(-1, favorite); } @@ -1013,7 +1011,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { fi.modified_time = 0; } - if (searched_string.length() == 0 || fi.name.to_lower().find(searched_string) >= 0) { + if (searched_tokens.is_empty() || _matches_all_search_tokens(fi.name)) { file_list.push_back(fi); } } @@ -1036,7 +1034,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { return; } - if (searched_string.length() > 0) { + if (!searched_tokens.is_empty()) { // Display the search results. // Limit the number of results displayed to avoid an infinite loop. _search(EditorFileSystem::get_singleton()->get_filesystem(), &file_list, 10000); @@ -1068,7 +1066,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->set_item_metadata(-1, bd); files->set_item_selectable(-1, false); - files->set_item_icon_modulate(-1, editor_is_dark_theme ? inherited_folder_color : inherited_folder_color * 1.75); + files->set_item_icon_modulate(-1, editor_is_dark_theme ? inherited_folder_color : inherited_folder_color * ITEM_COLOR_SCALE); } bool reversed = file_sort == FILE_SORT_NAME_REVERSE; @@ -1082,7 +1080,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->add_item(dname, folder_icon, true); files->set_item_metadata(-1, dpath); Color this_folder_color = has_custom_color ? folder_colors[assigned_folder_colors[dpath]] : inherited_folder_color; - files->set_item_icon_modulate(-1, editor_is_dark_theme ? this_folder_color : this_folder_color * 1.75); + files->set_item_icon_modulate(-1, editor_is_dark_theme ? this_folder_color : this_folder_color * ITEM_COLOR_SCALE); if (previous_selection.has(dname)) { files->select(files->get_item_count() - 1, false); @@ -1272,7 +1270,7 @@ void FileSystemDock::_file_list_activate_file(int p_idx) { } void FileSystemDock::_preview_invalidated(const String &p_path) { - if (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS && p_path.get_base_dir() == current_path && searched_string.length() == 0 && file_list_vb->is_visible_in_tree()) { + if (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS && p_path.get_base_dir() == current_path && searched_tokens.is_empty() && file_list_vb->is_visible_in_tree()) { for (int i = 0; i < files->get_item_count(); i++) { if (files->get_item_metadata(i) == p_path) { // Re-request preview. @@ -1778,8 +1776,19 @@ void FileSystemDock::_folder_removed(const String &p_folder) { current_path = current_path.get_base_dir(); } - if (assigned_folder_colors.has(p_folder)) { - assigned_folder_colors.erase(p_folder); + // Remove assigned folder color for all subfolders. + bool folder_colors_updated = false; + List<Variant> paths; + assigned_folder_colors.get_key_list(&paths); + for (const Variant &E : paths) { + const String &path = E; + // These folder paths are guaranteed to end with a "/". + if (path.begins_with(p_folder)) { + assigned_folder_colors.erase(path); + folder_colors_updated = true; + } + } + if (folder_colors_updated) { _update_folder_colors_setting(); } @@ -2603,12 +2612,13 @@ void FileSystemDock::_resource_created() { } void FileSystemDock::_search_changed(const String &p_text, const Control *p_from) { - if (searched_string.length() == 0) { + if (searched_tokens.is_empty()) { // Register the uncollapsed paths before they change. uncollapsed_paths_before_search = get_uncollapsed_paths(); } - searched_string = p_text.to_lower(); + const String searched_string = p_text.to_lower(); + searched_tokens = searched_string.split(" ", false); if (p_from == tree_search_box) { file_list_search_box->set_text(searched_string); @@ -2619,16 +2629,29 @@ void FileSystemDock::_search_changed(const String &p_text, const Control *p_from bool unfold_path = (p_text.is_empty() && !current_path.is_empty()); switch (display_mode) { case DISPLAY_MODE_TREE_ONLY: { - _update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path); + _update_tree(searched_tokens.is_empty() ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path); } break; case DISPLAY_MODE_HSPLIT: case DISPLAY_MODE_VSPLIT: { _update_file_list(false); - _update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path); + _update_tree(searched_tokens.is_empty() ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path); } break; } } +bool FileSystemDock::_matches_all_search_tokens(const String &p_text) { + if (searched_tokens.is_empty()) { + return false; + } + const String s = p_text.to_lower(); + for (const String &t : searched_tokens) { + if (!s.contains(t)) { + return false; + } + } + return true; +} + void FileSystemDock::_rescan() { _set_scanning_mode(); EditorFileSystem::get_singleton()->scan(); @@ -3086,6 +3109,8 @@ void FileSystemDock::_folder_color_index_pressed(int p_index, PopupMenu *p_menu) _update_tree(get_uncollapsed_paths()); _update_file_list(true); + + emit_signal(SNAME("folder_color_changed")); } void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vector<String> &p_paths, bool p_display_path_dependent_options) { @@ -3354,7 +3379,7 @@ void FileSystemDock::_file_list_item_clicked(int p_item, const Vector2 &p_pos, M // Popup. if (!paths.is_empty()) { file_list_popup->clear(); - _file_and_folders_fill_popup(file_list_popup, paths, searched_string.length() == 0); + _file_and_folders_fill_popup(file_list_popup, paths, searched_tokens.is_empty()); file_list_popup->set_position(files->get_screen_position() + p_pos); file_list_popup->reset_size(); file_list_popup->popup(); @@ -3367,7 +3392,7 @@ void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton } // Right click on empty space for file list. - if (searched_string.length() > 0) { + if (!searched_tokens.is_empty()) { return; } @@ -3796,6 +3821,7 @@ void FileSystemDock::_bind_methods() { ADD_SIGNAL(MethodInfo("folder_removed", PropertyInfo(Variant::STRING, "folder"))); ADD_SIGNAL(MethodInfo("files_moved", PropertyInfo(Variant::STRING, "old_file"), PropertyInfo(Variant::STRING, "new_file"))); ADD_SIGNAL(MethodInfo("folder_moved", PropertyInfo(Variant::STRING, "old_folder"), PropertyInfo(Variant::STRING, "new_folder"))); + ADD_SIGNAL(MethodInfo("folder_color_changed")); ADD_SIGNAL(MethodInfo("display_mode_changed")); } @@ -4113,7 +4139,6 @@ FileSystemDock::FileSystemDock() { new_resource_dialog->set_base_type("Resource"); new_resource_dialog->connect("create", callable_mp(this, &FileSystemDock::_resource_created)); - searched_string = String(); uncollapsed_paths_before_search = Vector<String>(); tree_update_id = 0; diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 8830f31d2d..058886c91a 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -172,7 +172,7 @@ private: LineEdit *file_list_search_box = nullptr; MenuButton *file_list_button_sort = nullptr; - String searched_string; + PackedStringArray searched_tokens; Vector<String> uncollapsed_paths_before_search; TextureRect *search_icon = nullptr; @@ -311,6 +311,7 @@ private: void _split_dragged(int p_offset); void _search_changed(const String &p_text, const Control *p_from); + bool _matches_all_search_tokens(const String &p_text); MenuButton *_create_file_menu_button(); void _file_sort_popup(int p_id); @@ -381,6 +382,11 @@ protected: static void _bind_methods(); public: + static constexpr double ITEM_COLOR_SCALE = 1.75; + static constexpr double ITEM_ALPHA_MIN = 0.1; + static constexpr double ITEM_ALPHA_MAX = 0.15; + static constexpr double ITEM_BG_DARK_SCALE = 0.3; + const HashMap<String, Color> &get_folder_colors() const; Dictionary get_assigned_folder_colors() const; diff --git a/editor/gui/editor_dir_dialog.cpp b/editor/gui/editor_dir_dialog.cpp index 8821a0eeae..9d5464210b 100644 --- a/editor/gui/editor_dir_dialog.cpp +++ b/editor/gui/editor_dir_dialog.cpp @@ -32,27 +32,44 @@ #include "editor/directory_create_dialog.h" #include "editor/editor_file_system.h" +#include "editor/filesystem_dock.h" +#include "editor/themes/editor_theme_manager.h" #include "scene/gui/box_container.h" #include "scene/gui/tree.h" #include "servers/display_server.h" -void EditorDirDialog::_update_dir(TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path) { +void EditorDirDialog::_update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap<String, Color> &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path) { updating = true; const String path = p_dir->get_path(); p_item->set_metadata(0, path); p_item->set_icon(0, tree->get_editor_theme_icon(SNAME("Folder"))); - p_item->set_icon_modulate(0, tree->get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog"))); if (!p_item->get_parent()) { p_item->set_text(0, "res://"); + p_item->set_icon_modulate(0, p_default_folder_color); } else { if (!opened_paths.has(path) && (p_select_path.is_empty() || !p_select_path.begins_with(path))) { p_item->set_collapsed(true); } p_item->set_text(0, p_dir->get_name()); + + if (p_assigned_folder_colors.has(path)) { + const Color &folder_color = p_folder_colors[p_assigned_folder_colors[path]]; + p_item->set_icon_modulate(0, p_is_dark_theme ? folder_color : folder_color * FileSystemDock::ITEM_COLOR_SCALE); + p_item->set_custom_bg_color(0, Color(folder_color, p_is_dark_theme ? FileSystemDock::ITEM_ALPHA_MIN : FileSystemDock::ITEM_ALPHA_MAX)); + } else { + TreeItem *parent_item = p_item->get_parent(); + Color parent_bg_color = parent_item->get_custom_bg_color(0); + if (parent_bg_color != Color()) { + p_item->set_custom_bg_color(0, p_assigned_folder_colors.has(parent_item->get_metadata(0)) ? parent_bg_color.darkened(FileSystemDock::ITEM_BG_DARK_SCALE) : parent_bg_color); + p_item->set_icon_modulate(0, parent_item->get_icon_modulate(0)); + } else { + p_item->set_icon_modulate(0, p_default_folder_color); + } + } } if (path == new_dir_path || !p_item->get_parent()) { @@ -62,7 +79,7 @@ void EditorDirDialog::_update_dir(TreeItem *p_item, EditorFileSystemDirectory *p updating = false; for (int i = 0; i < p_dir->get_subdir_count(); i++) { TreeItem *ti = tree->create_item(p_item); - _update_dir(ti, p_dir->get_subdir(i)); + _update_dir(p_default_folder_color, p_assigned_folder_colors, p_folder_colors, p_is_dark_theme, ti, p_dir->get_subdir(i)); } } @@ -90,7 +107,7 @@ void EditorDirDialog::reload(const String &p_path) { tree->clear(); TreeItem *root = tree->create_item(); - _update_dir(root, EditorFileSystem::get_singleton()->get_filesystem(), p_path); + _update_dir(tree->get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")), FileSystemDock::get_singleton()->get_assigned_folder_colors(), FileSystemDock::get_singleton()->get_folder_colors(), EditorThemeManager::is_dark_theme(), root, EditorFileSystem::get_singleton()->get_filesystem(), p_path); _item_collapsed(root); new_dir_path.clear(); must_reload = false; @@ -99,6 +116,8 @@ void EditorDirDialog::reload(const String &p_path) { void EditorDirDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + FileSystemDock::get_singleton()->connect("folder_color_changed", callable_mp(this, &EditorDirDialog::reload).bind("")); + EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorDirDialog::reload).bind("")); reload(); diff --git a/editor/gui/editor_dir_dialog.h b/editor/gui/editor_dir_dialog.h index 40badec212..b10cc8dd9f 100644 --- a/editor/gui/editor_dir_dialog.h +++ b/editor/gui/editor_dir_dialog.h @@ -53,7 +53,7 @@ class EditorDirDialog : public ConfirmationDialog { void _item_collapsed(Object *p_item); void _item_activated(); - void _update_dir(TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String()); + void _update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap<String, Color> &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String()); void _make_dir(); void _make_dir_confirm(const String &p_path); diff --git a/editor/icons/PreviewRotate.svg b/editor/icons/PreviewRotate.svg new file mode 100644 index 0000000000..9e0da46169 --- /dev/null +++ b/editor/icons/PreviewRotate.svg @@ -0,0 +1 @@ +<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#000" stroke-width="2" opacity=".8" stroke-linejoin="round"><circle cx="8" cy="8" r="2"/><path d="M8 1a7 7 0 00-4.982 11.998H1.911v2h4a1 1 0 00.97-1.242l-1-4-1.94.486.28 1.121a5 5 0 117.223.168l1.416 1.416A7 7 0 008 1z"/></g><g fill="#f9f9f9"><circle cx="8" cy="8" r="2"/><path d="M8 1a7 7 0 00-4.982 11.998H1.911v2h4a1 1 0 00.97-1.242l-1-4-1.94.486.28 1.121a5 5 0 117.223.168l1.416 1.416A7 7 0 008 1z"/></g></svg> diff --git a/editor/icons/XRFaceModifier3D.svg b/editor/icons/XRFaceModifier3D.svg new file mode 100644 index 0000000000..6ab48ca29c --- /dev/null +++ b/editor/icons/XRFaceModifier3D.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f"><path d="m4 12v1c0 .552.448 1 1 1-.552 0-1 .448-1 1v1h1v-1h1v1h1v-1c0-.552-.448-1-1-1 .552 0 1-.448 1-1v-1h-1v1h-1v-1zm5 0v4h1v-1h1v1h1v-1c-.001-.176-.048-.348-.137-.5.089-.152.136-.324.137-.5v-1c0-.552-.448-1-1-1zm1 1h1v1h-1z"/><path d="m11.384 9.462v-2.155c1.613-.944 2.156-3.016 1.213-4.631-.603-1.033-1.709-1.67-2.905-1.676h-3.385c-1.869.008-3.377 1.532-3.368 3.401.005 1.197.643 2.301 1.676 2.906v2.155c0 .934.758 1.692 1.692 1.692h3.385c.935 0 1.692-.758 1.692-1.692zm-4.23-4.231h1.692v.846h-1.692zm-2.539-.846c0-.468.378-.846.847-.846.468 0 .846.378.846.846 0 .467-.378.846-.846.846-.469 0-.847-.379-.847-.846zm5.923 5.077h-.846v-.847h-.846v.846h-1.692v-.846h-.847v.846h-.846v-2.538h.846v.846h.847v-.846h1.692v.846h.846v-.846h.846zm-.846-5.077c0-.468.378-.846.846-.846s.846.378.846.846c0 .467-.378.846-.846.846s-.846-.379-.846-.846z"/></g></svg>
\ No newline at end of file diff --git a/editor/icons/XRNode3D.svg b/editor/icons/XRNode3D.svg new file mode 100644 index 0000000000..50dd3ad951 --- /dev/null +++ b/editor/icons/XRNode3D.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><circle cx="8" cy="6" fill="none" r="4" stroke="#fc7f7f" stroke-width="2"/><path d="m4 12v1c0 .552.448 1 1 1-.552 0-1 .448-1 1v1h1v-1h1v1h1v-1c0-.552-.448-1-1-1 .552 0 1-.448 1-1v-1h-1v1h-1v-1zm5 0v4h1v-1h1v1h1v-1c-.001-.176-.048-.348-.137-.5.089-.152.136-.324.137-.5v-1c0-.552-.448-1-1-1zm1 1h1v1h-1z" fill="#fc7f7f"/></svg>
\ No newline at end of file diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp index 251834f42f..44016292b1 100644 --- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp +++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp @@ -109,7 +109,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory // Apply node transforms. if (bool(p_options["retarget/rest_fixer/apply_node_transforms"])) { - Vector3 scl = global_transform.basis.get_scale_local(); + Vector3 scl = global_transform.basis.get_scale_global(); Vector<int> bones_to_process = src_skeleton->get_parentless_bones(); for (int i = 0; i < bones_to_process.size(); i++) { @@ -674,7 +674,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory int bone_idx = src_skeleton->find_bone(bn); if (bone_idx >= 0) { Transform3D adjust_transform = src_skeleton->get_bone_global_rest(bone_idx).affine_inverse() * silhouette_diff[bone_idx].affine_inverse() * pre_silhouette_skeleton_global_rest[bone_idx]; - adjust_transform.scale(global_transform.basis.get_scale_local()); + adjust_transform.scale(global_transform.basis.get_scale_global()); skin->set_bind_pose(i, adjust_transform * skin->get_bind_pose(i)); } } @@ -691,7 +691,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory } ERR_CONTINUE(bone_idx < 0 || bone_idx >= src_skeleton->get_bone_count()); Transform3D adjust_transform = src_skeleton->get_bone_global_rest(bone_idx).affine_inverse() * silhouette_diff[bone_idx].affine_inverse() * pre_silhouette_skeleton_global_rest[bone_idx]; - adjust_transform.scale(global_transform.basis.get_scale_local()); + adjust_transform.scale(global_transform.basis.get_scale_global()); TypedArray<Node> child_nodes = attachment->get_children(); while (child_nodes.size()) { diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp index 242b483b51..f5bf60175a 100644 --- a/editor/import/3d/resource_importer_obj.cpp +++ b/editor/import/3d/resource_importer_obj.cpp @@ -497,7 +497,7 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, } } - if (p_single_mesh) { + if (p_single_mesh && mesh->get_surface_count() > 0) { r_meshes.push_back(mesh); } diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index 620ebce44b..325525be1b 100644 --- a/editor/import/3d/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp @@ -1117,6 +1117,20 @@ void SceneImportSettingsDialog::_cleanup() { set_process(false); } +void SceneImportSettingsDialog::_on_light_1_switch_pressed() { + light1->set_visible(light_1_switch->is_pressed()); +} + +void SceneImportSettingsDialog::_on_light_2_switch_pressed() { + light2->set_visible(light_2_switch->is_pressed()); +} + +void SceneImportSettingsDialog::_on_light_rotate_switch_pressed() { + bool light_top_level = !light_rotate_switch->is_pressed(); + light1->set_as_top_level_keep_local(light_top_level); + light2->set_as_top_level_keep_local(light_top_level); +} + void SceneImportSettingsDialog::_viewport_input(const Ref<InputEvent> &p_input) { float *rot_x = &cam_rot_x; float *rot_y = &cam_rot_y; @@ -1232,6 +1246,13 @@ void SceneImportSettingsDialog::_re_import() { EditorFileSystem::get_singleton()->reimport_file_with_custom_parameters(base_path, editing_animation ? "animation_library" : "scene", main_settings); } +void SceneImportSettingsDialog::_update_theme_item_cache() { + ConfirmationDialog::_update_theme_item_cache(); + theme_cache.light_1_icon = get_editor_theme_icon(SNAME("MaterialPreviewLight1")); + theme_cache.light_2_icon = get_editor_theme_icon(SNAME("MaterialPreviewLight2")); + theme_cache.rotate_icon = get_editor_theme_icon(SNAME("PreviewRotate")); +} + void SceneImportSettingsDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { @@ -1251,6 +1272,10 @@ void SceneImportSettingsDialog::_notification(int p_what) { animation_play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay"))); } animation_stop_button->set_icon(get_editor_theme_icon(SNAME("Stop"))); + + light_1_switch->set_icon(theme_cache.light_1_icon); + light_2_switch->set_icon(theme_cache.light_2_icon); + light_rotate_switch->set_icon(theme_cache.rotate_icon); } break; case NOTIFICATION_PROCESS: { @@ -1644,6 +1669,40 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { base_viewport->set_use_own_world_3d(true); + HBoxContainer *viewport_hbox = memnew(HBoxContainer); + vp_container->add_child(viewport_hbox); + viewport_hbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 2); + + viewport_hbox->add_spacer(); + + VBoxContainer *vb_light = memnew(VBoxContainer); + vb_light->set_v_size_flags(Control::SIZE_EXPAND_FILL); + viewport_hbox->add_child(vb_light); + + light_rotate_switch = memnew(Button); + light_rotate_switch->set_theme_type_variation("PreviewLightButton"); + light_rotate_switch->set_toggle_mode(true); + light_rotate_switch->set_pressed(true); + light_rotate_switch->set_tooltip_text(TTR("Rotate Lights With Model")); + light_rotate_switch->connect("pressed", callable_mp(this, &SceneImportSettingsDialog::_on_light_rotate_switch_pressed)); + vb_light->add_child(light_rotate_switch); + + light_1_switch = memnew(Button); + light_1_switch->set_theme_type_variation("PreviewLightButton"); + light_1_switch->set_toggle_mode(true); + light_1_switch->set_pressed(true); + light_1_switch->set_tooltip_text(TTR("Primary Light")); + light_1_switch->connect("pressed", callable_mp(this, &SceneImportSettingsDialog::_on_light_1_switch_pressed)); + vb_light->add_child(light_1_switch); + + light_2_switch = memnew(Button); + light_2_switch->set_theme_type_variation("PreviewLightButton"); + light_2_switch->set_toggle_mode(true); + light_2_switch->set_pressed(true); + light_2_switch->set_tooltip_text(TTR("Secondary Light")); + light_2_switch->connect("pressed", callable_mp(this, &SceneImportSettingsDialog::_on_light_2_switch_pressed)); + vb_light->add_child(light_2_switch); + camera = memnew(Camera3D); base_viewport->add_child(camera); camera->make_current(); @@ -1675,10 +1734,15 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { environment->set_sky_custom_fov(50.0); camera->set_environment(environment); - light = memnew(DirectionalLight3D); - light->set_transform(Transform3D().looking_at(Vector3(-1, -2, -0.6), Vector3(0, 1, 0))); - base_viewport->add_child(light); - light->set_shadow(true); + light1 = memnew(DirectionalLight3D); + light1->set_transform(Transform3D(Basis::looking_at(Vector3(-1, -1, -1)))); + light1->set_shadow(true); + camera->add_child(light1); + + light2 = memnew(DirectionalLight3D); + light2->set_transform(Transform3D(Basis::looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)))); + light2->set_color(Color(0.5f, 0.5f, 0.5f)); + camera->add_child(light2); { Ref<StandardMaterial3D> selection_mat; diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h index 17d6616fc0..c2a5151432 100644 --- a/editor/import/3d/scene_import_settings.h +++ b/editor/import/3d/scene_import_settings.h @@ -85,7 +85,18 @@ class SceneImportSettingsDialog : public ConfirmationDialog { bool first_aabb = false; AABB contents_aabb; - DirectionalLight3D *light = nullptr; + Button *light_1_switch = nullptr; + Button *light_2_switch = nullptr; + Button *light_rotate_switch = nullptr; + + struct ThemeCache { + Ref<Texture2D> light_1_icon; + Ref<Texture2D> light_2_icon; + Ref<Texture2D> rotate_icon; + } theme_cache; + + DirectionalLight3D *light1 = nullptr; + DirectionalLight3D *light2 = nullptr; Ref<ArrayMesh> selection_mesh; MeshInstance3D *node_selected = nullptr; @@ -180,6 +191,9 @@ class SceneImportSettingsDialog : public ConfirmationDialog { void _mesh_tree_selected(); void _scene_tree_selected(); void _cleanup(); + void _on_light_1_switch_pressed(); + void _on_light_2_switch_pressed(); + void _on_light_rotate_switch_pressed(); void _viewport_input(const Ref<InputEvent> &p_input); @@ -222,6 +236,7 @@ class SceneImportSettingsDialog : public ConfirmationDialog { Timer *update_view_timer = nullptr; protected: + virtual void _update_theme_item_cache() override; void _notification(int p_what); public: diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index e2aac6c75d..a667f94a62 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -587,7 +587,7 @@ void InputEventConfigurationDialog::_notification(int p_what) { void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p_event, const String &p_current_action_name) { if (p_event.is_valid()) { - _set_event(p_event->duplicate(), p_event); + _set_event(p_event->duplicate(), p_event->duplicate()); } else { // Clear Event _set_event(Ref<InputEvent>(), Ref<InputEvent>()); diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index 28080ed559..da136b70ae 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -129,34 +129,9 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p ERR_FAIL_MSG(vformat("No suitable library found for GDExtension: %s. Possible feature flags for your platform: %s", p_path, String(", ").join(features_vector))); } - List<String> dependencies; - if (config->has_section("dependencies")) { - config->get_section_keys("dependencies", &dependencies); - } - - for (const String &E : dependencies) { - Vector<String> dependency_tags = E.split("."); - bool all_tags_met = true; - for (int i = 0; i < dependency_tags.size(); i++) { - String tag = dependency_tags[i].strip_edges(); - if (!p_features.has(tag)) { - all_tags_met = false; - break; - } - } - - if (all_tags_met) { - Dictionary dependency = config->get_value("dependencies", E); - for (const Variant *key = dependency.next(nullptr); key; key = dependency.next(key)) { - String dependency_path = *key; - String target_path = dependency[*key]; - if (dependency_path.is_relative_path()) { - dependency_path = p_path.get_base_dir().path_join(dependency_path); - } - add_shared_object(dependency_path, dependency_tags, target_path); - } - break; - } + Vector<SharedObject> dependencies_shared_objects = GDExtension::find_extension_dependencies(p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); }); + for (const SharedObject &shared_object : dependencies_shared_objects) { + _add_shared_object(shared_object); } } } diff --git a/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.cpp new file mode 100644 index 0000000000..16be707d08 --- /dev/null +++ b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.cpp @@ -0,0 +1,79 @@ +/**************************************************************************/ +/* geometry_instance_3d_gizmo_plugin.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "geometry_instance_3d_gizmo_plugin.h" + +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/plugins/node_3d_editor_plugin.h" +#include "scene/3d/visual_instance_3d.h" + +GeometryInstance3DGizmoPlugin::GeometryInstance3DGizmoPlugin() { +} + +bool GeometryInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<GeometryInstance3D>(p_spatial) != nullptr; +} + +String GeometryInstance3DGizmoPlugin::get_gizmo_name() const { + return "MeshInstance3DCustomAABB"; +} + +int GeometryInstance3DGizmoPlugin::get_priority() const { + return -1; +} + +void GeometryInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + GeometryInstance3D *geometry = Object::cast_to<GeometryInstance3D>(p_gizmo->get_node_3d()); + + p_gizmo->clear(); + + if (p_gizmo->is_selected()) { + AABB aabb = geometry->get_custom_aabb(); + + Vector<Vector3> lines; + for (int i = 0; i < 12; i++) { + Vector3 a; + Vector3 b; + aabb.get_edge(i, a, b); + + lines.push_back(a); + lines.push_back(b); + } + + Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D); + mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + const Color selection_box_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/aabb"); + mat->set_albedo(selection_box_color); + mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + p_gizmo->add_lines(lines, mat); + } +} diff --git a/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h new file mode 100644 index 0000000000..2482e277ea --- /dev/null +++ b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* geometry_instance_3d_gizmo_plugin.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 GEOMETRY_INSTANCE_3D_GIZMO_PLUGIN_H +#define GEOMETRY_INSTANCE_3D_GIZMO_PLUGIN_H + +#include "editor/plugins/node_3d_editor_gizmos.h" + +class GeometryInstance3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(GeometryInstance3DGizmoPlugin, EditorNode3DGizmoPlugin); + +public: + virtual bool has_gizmo(Node3D *p_spatial) override; + virtual String get_gizmo_name() const override; + virtual int get_priority() const override; + + virtual void redraw(EditorNode3DGizmo *p_gizmo) override; + + GeometryInstance3DGizmoPlugin(); +}; + +#endif // GEOMETRY_INSTANCE_3D_GIZMO_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index aea3ea9700..be14132185 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -53,6 +53,7 @@ #include "editor/plugins/gizmos/cpu_particles_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/decal_gizmo_plugin.h" #include "editor/plugins/gizmos/fog_volume_gizmo_plugin.h" +#include "editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/gpu_particles_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/gpu_particles_collision_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/joint_3d_gizmo_plugin.h" @@ -8096,6 +8097,7 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<SoftBody3DGizmoPlugin>(memnew(SoftBody3DGizmoPlugin))); add_gizmo_plugin(Ref<SpriteBase3DGizmoPlugin>(memnew(SpriteBase3DGizmoPlugin))); add_gizmo_plugin(Ref<Label3DGizmoPlugin>(memnew(Label3DGizmoPlugin))); + add_gizmo_plugin(Ref<GeometryInstance3DGizmoPlugin>(memnew(GeometryInstance3DGizmoPlugin))); add_gizmo_plugin(Ref<Marker3DGizmoPlugin>(memnew(Marker3DGizmoPlugin))); add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin))); add_gizmo_plugin(Ref<ShapeCast3DGizmoPlugin>(memnew(ShapeCast3DGizmoPlugin))); diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index 07d6ed4185..34a0df5906 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -46,7 +46,9 @@ #include "scene/gui/menu_button.h" #include "scene/gui/panel.h" #include "scene/gui/view_panner.h" -#include "thirdparty/misc/clipper.hpp" +#include "thirdparty/clipper2/include/clipper2/clipper.h" + +#define PRECISION 1 void Sprite2DEditor::_node_removed(Node *p_node) { if (p_node == node) { @@ -59,58 +61,39 @@ void Sprite2DEditor::edit(Sprite2D *p_sprite) { node = p_sprite; } -#define PRECISION 10.0 - Vector<Vector2> expand(const Vector<Vector2> &points, const Rect2i &rect, float epsilon = 2.0) { int size = points.size(); ERR_FAIL_COND_V(size < 2, Vector<Vector2>()); - ClipperLib::Path subj; - ClipperLib::PolyTree solution; - ClipperLib::PolyTree out; - + Clipper2Lib::PathD subj(points.size()); for (int i = 0; i < points.size(); i++) { - subj << ClipperLib::IntPoint(points[i].x * PRECISION, points[i].y * PRECISION); + subj[i] = Clipper2Lib::PointD(points[i].x, points[i].y); } - ClipperLib::ClipperOffset co; - co.AddPath(subj, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); - co.Execute(solution, epsilon * PRECISION); - ClipperLib::PolyNode *p = solution.GetFirst(); + Clipper2Lib::PathsD solution = Clipper2Lib::InflatePaths({ subj }, epsilon, Clipper2Lib::JoinType::Miter, Clipper2Lib::EndType::Polygon, 2.0, PRECISION, 0.0); + // Here the miter_limit = 2.0 and arc_tolerance = 0.0 are Clipper2 defaults, + // and PRECISION is used to scale points up internally, to attain the desired precision. - ERR_FAIL_NULL_V(p, points); + ERR_FAIL_COND_V(solution.size() == 0, points); - while (p->IsHole()) { - p = p->GetNext(); - } + // Clamp into the specified rect. + Clipper2Lib::RectD clamp(rect.position.x, + rect.position.y, + rect.position.x + rect.size.width, + rect.position.y + rect.size.height); + Clipper2Lib::PathsD out = Clipper2Lib::RectClip(clamp, solution[0], PRECISION); + // Here PRECISION is used to scale points up internally, to attain the desired precision. - //turn the result into simply polygon (AKA, fix overlap) - - //clamp into the specified rect - ClipperLib::Clipper cl; - cl.StrictlySimple(true); - cl.AddPath(p->Contour, ClipperLib::ptSubject, true); - //create the clipping rect - ClipperLib::Path clamp; - clamp.push_back(ClipperLib::IntPoint(0, 0)); - clamp.push_back(ClipperLib::IntPoint(rect.size.width * PRECISION, 0)); - clamp.push_back(ClipperLib::IntPoint(rect.size.width * PRECISION, rect.size.height * PRECISION)); - clamp.push_back(ClipperLib::IntPoint(0, rect.size.height * PRECISION)); - cl.AddPath(clamp, ClipperLib::ptClip, true); - cl.Execute(ClipperLib::ctIntersection, out); + ERR_FAIL_COND_V(out.size() == 0, points); - Vector<Vector2> outPoints; - ClipperLib::PolyNode *p2 = out.GetFirst(); - ERR_FAIL_NULL_V(p2, points); + const Clipper2Lib::PathD &p2 = out[0]; - while (p2->IsHole()) { - p2 = p2->GetNext(); - } + Vector<Vector2> outPoints; - int lasti = p2->Contour.size() - 1; - Vector2 prev = Vector2(p2->Contour[lasti].X / PRECISION, p2->Contour[lasti].Y / PRECISION); - for (uint64_t i = 0; i < p2->Contour.size(); i++) { - Vector2 cur = Vector2(p2->Contour[i].X / PRECISION, p2->Contour[i].Y / PRECISION); + int lasti = p2.size() - 1; + Vector2 prev = Vector2(p2[lasti].x, p2[lasti].y); + for (uint64_t i = 0; i < p2.size(); i++) { + Vector2 cur = Vector2(p2[i].x, p2[i].y); if (cur.distance_to(prev) > 0.5) { outPoints.push_back(cur); prev = cur; diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index e4e66b38e9..5e67cbc6ce 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -1476,6 +1476,10 @@ void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) { _fetch_sprite_node(); // Fetch node after set frames. } +bool SpriteFramesEditor::is_editing() const { + return frames.is_valid(); +} + Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { if (read_only) { return false; @@ -2325,7 +2329,7 @@ bool SpriteFramesEditorPlugin::handles(Object *p_object) const { if (animated_sprite_3d && *animated_sprite_3d->get_sprite_frames()) { return true; } - return p_object->is_class("SpriteFrames"); + return !frames_editor->is_editing() && Object::cast_to<SpriteFrames>(p_object); } void SpriteFramesEditorPlugin::make_visible(bool p_visible) { diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index 2d0e43be1e..e9fbaf7dde 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -253,7 +253,6 @@ class SpriteFramesEditor : public HSplitContainer { void _update_show_settings(); void _edit(); - void _regist_scene_undo(EditorUndoRedoManager *undo_redo); void _fetch_sprite_node(); void _remove_sprite_node(); @@ -270,6 +269,8 @@ protected: public: void edit(Ref<SpriteFrames> p_frames); + bool is_editing() const; + SpriteFramesEditor(); }; diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 0db752e771..166ed05748 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -2267,7 +2267,9 @@ ThemeTypeDialog::ThemeTypeDialog() { /////////////////////// Control *ThemeItemLabel::make_custom_tooltip(const String &p_text) const { - return memnew(EditorHelpTooltip(p_text)); + EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text)); + EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<ThemeItemLabel *>(this)); + return memnew(Control); // Make the standard tooltip invisible. } VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) { @@ -2436,8 +2438,8 @@ HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_ item_name->set_h_size_flags(SIZE_EXPAND_FILL); item_name->set_clip_text(true); item_name->set_text(p_item_name); - // `|` separators used in `EditorHelpTooltip` for formatting. - item_name->set_tooltip_text("theme_item|" + edited_type + "|" + p_item_name + "|"); + // `|` separators used in `EditorHelpBit`. + item_name->set_tooltip_text("theme_item|" + edited_type + "|" + p_item_name); item_name->set_mouse_filter(Control::MOUSE_FILTER_STOP); item_name_container->add_child(item_name); diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index ba3446807e..0bc02789aa 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -321,7 +321,7 @@ public: ThemeTypeDialog(); }; -// Custom `Label` needed to use `EditorHelpTooltip` to display theme item documentation. +// Custom `Label` needed to use `EditorHelpBit` to display theme item documentation. class ThemeItemLabel : public Label { virtual Control *make_custom_tooltip(const String &p_text) const; }; diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index a16767d916..70cef0e345 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1571,6 +1571,8 @@ ProjectManager::ProjectManager() { // Initialize project list. { + project_list->load_project_list(); + Ref<DirAccess> dir_access = DirAccess::create(DirAccess::AccessType::ACCESS_FILESYSTEM); String default_project_path = EDITOR_GET("filesystem/directories/default_project_path"); @@ -1581,13 +1583,10 @@ ProjectManager::ProjectManager() { } } - bool scanned_for_projects = false; // Scanning will update the list automatically. - String autoscan_path = EDITOR_GET("filesystem/directories/autoscan_project_path"); if (!autoscan_path.is_empty()) { if (dir_access->dir_exists(autoscan_path)) { project_list->find_projects(autoscan_path); - scanned_for_projects = true; } else { Error error = dir_access->make_dir_recursive(autoscan_path); if (error != OK) { @@ -1595,10 +1594,8 @@ ProjectManager::ProjectManager() { } } } - - if (!scanned_for_projects) { - project_list->update_project_list(); - } + project_list->update_project_list(); + initialized = true; } // Extend menu bar to window title. diff --git a/editor/project_manager.h b/editor/project_manager.h index d472666d1a..669b5d8b6c 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -141,6 +141,7 @@ class ProjectManager : public Control { void _update_list_placeholder(); ProjectList *project_list = nullptr; + bool initialized = false; LineEdit *search_box = nullptr; Label *loading_label = nullptr; @@ -239,6 +240,7 @@ public: // Project list. + bool is_initialized() const { return initialized; } LineEdit *get_search_box(); // Project tag management. diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp index aa93d9414b..d125754dd7 100644 --- a/editor/project_manager/project_list.cpp +++ b/editor/project_manager/project_list.cpp @@ -469,23 +469,19 @@ void ProjectList::update_project_list() { // If you have 150 projects, it may read through 150 files on your disk at once + load 150 icons. // FIXME: Does it really have to be a full, hard reload? Runtime updates should be made much cheaper. - // Clear whole list - for (int i = 0; i < _projects.size(); ++i) { - Item &project = _projects.write[i]; - CRASH_COND(project.control == nullptr); - memdelete(project.control); // Why not queue_free()? - } - _projects.clear(); - _last_clicked = ""; - _selected_project_paths.clear(); + if (ProjectManager::get_singleton()->is_initialized()) { + // Clear whole list + for (int i = 0; i < _projects.size(); ++i) { + Item &project = _projects.write[i]; + CRASH_COND(project.control == nullptr); + memdelete(project.control); // Why not queue_free()? + } - List<String> sections; - _config.load(_config_path); - _config.get_sections(§ions); + _projects.clear(); + _last_clicked = ""; + _selected_project_paths.clear(); - for (const String &path : sections) { - bool favorite = _config.get_value(path, "favorite", false); - _projects.push_back(load_project_data(path, favorite)); + load_project_list(); } // Create controls @@ -590,7 +586,21 @@ void ProjectList::find_projects_multiple(const PackedStringArray &p_paths) { } save_config(); - update_project_list(); + + if (ProjectManager::get_singleton()->is_initialized()) { + update_project_list(); + } +} + +void ProjectList::load_project_list() { + List<String> sections; + _config.load(_config_path); + _config.get_sections(§ions); + + for (const String &path : sections) { + bool favorite = _config.get_value(path, "favorite", false); + _projects.push_back(load_project_data(path, favorite)); + } } void ProjectList::_scan_folder_recursive(const String &p_path, List<String> *r_projects) { diff --git a/editor/project_manager/project_list.h b/editor/project_manager/project_list.h index 86f1f13bd8..981df0f3a0 100644 --- a/editor/project_manager/project_list.h +++ b/editor/project_manager/project_list.h @@ -220,6 +220,7 @@ public: // Project list updates. + void load_project_list(); void update_project_list(); void sort_projects(); int get_project_count() const; diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index ac175d01a6..d7af751f95 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -87,7 +87,7 @@ void PropertySelector::_update_search() { } search_options->clear(); - help_bit->set_text(""); + help_bit->set_custom_text(String(), String(), String()); TreeItem *root = search_options->create_item(); @@ -353,7 +353,7 @@ void PropertySelector::_confirmed() { } void PropertySelector::_item_selected() { - help_bit->set_text(""); + help_bit->set_custom_text(String(), String(), String()); TreeItem *item = search_options->get_selected(); if (!item) { @@ -372,25 +372,21 @@ void PropertySelector::_item_selected() { String text; while (!class_type.is_empty()) { - text = properties ? help_bit->get_property_description(class_type, name) : help_bit->get_method_description(class_type, name); - if (!text.is_empty()) { - break; + if (properties) { + if (ClassDB::has_property(class_type, name, true)) { + help_bit->parse_symbol("property|" + class_type + "|" + name); + break; + } + } else { + if (ClassDB::has_method(class_type, name, true)) { + help_bit->parse_symbol("method|" + class_type + "|" + name); + break; + } } // It may be from a parent class, keep looking. class_type = ClassDB::get_parent_class(class_type); } - - if (!text.is_empty()) { - // Display both property name and description, since the help bit may be displayed - // far away from the location (especially if the dialog was resized to be taller). - help_bit->set_text(vformat("[b]%s[/b]: %s", name, text)); - help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); - } else { - // Use nested `vformat()` as translators shouldn't interfere with BBCode tags. - help_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", name))); - help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5)); - } } void PropertySelector::_hide_requested() { @@ -569,8 +565,7 @@ PropertySelector::PropertySelector() { search_options->set_hide_folding(true); help_bit = memnew(EditorHelpBit); - vbc->add_margin_child(TTR("Description:"), help_bit); - help_bit->get_rich_text()->set_fit_content(false); - help_bit->get_rich_text()->set_custom_minimum_size(Size2(help_bit->get_rich_text()->get_minimum_size().x, 135 * EDSCALE)); + help_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE); help_bit->connect("request_hide", callable_mp(this, &PropertySelector::_hide_requested)); + vbc->add_margin_child(TTR("Description:"), help_bit); } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index e1084d4873..67c269aa80 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -57,11 +57,9 @@ #include "editor/themes/editor_scale.h" #include "scene/animation/animation_tree.h" #include "scene/gui/check_box.h" -#include "scene/main/window.h" #include "scene/property_utils.h" #include "scene/resources/packed_scene.h" #include "servers/display_server.h" -#include "servers/rendering_server.h" #include "modules/modules_enabled.gen.h" // For regex. #ifdef MODULE_REGEX_ENABLED @@ -3636,10 +3634,7 @@ void SceneTreeDock::_filter_option_selected(int p_option) { void SceneTreeDock::_append_filter_options_to(PopupMenu *p_menu, bool p_include_separator) { if (p_include_separator) { - p_menu->add_separator(); - - p_menu->set_item_text(-1, TTR("Filters")); - p_menu->set_item_indent(-1, -2); + p_menu->add_separator(TTR("Filters")); } p_menu->add_item(TTR("Filter by Type"), FILTER_BY_TYPE); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 734fa415a3..6151cb4b74 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -356,20 +356,25 @@ EditorThemeManager::ThemeConfiguration EditorThemeManager::_create_theme_config( if (config.spacing_preset != "Custom") { int preset_base_spacing = 0; int preset_extra_spacing = 0; + Size2 preset_dialogs_buttons_min_size; if (config.spacing_preset == "Compact") { preset_base_spacing = 0; preset_extra_spacing = 4; + preset_dialogs_buttons_min_size = Size2(90, 26); } else if (config.spacing_preset == "Spacious") { preset_base_spacing = 6; preset_extra_spacing = 2; + preset_dialogs_buttons_min_size = Size2(112, 36); } else { // Default preset_base_spacing = 4; preset_extra_spacing = 0; + preset_dialogs_buttons_min_size = Size2(105, 34); } config.base_spacing = preset_base_spacing; config.extra_spacing = preset_extra_spacing; + config.dialogs_buttons_min_size = preset_dialogs_buttons_min_size; EditorSettings::get_singleton()->set_initial_value("interface/theme/base_spacing", config.base_spacing); EditorSettings::get_singleton()->set_initial_value("interface/theme/additional_spacing", config.extra_spacing); @@ -1271,6 +1276,9 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the // AcceptDialog. p_theme->set_stylebox("panel", "AcceptDialog", p_config.dialog_style); p_theme->set_constant("buttons_separation", "AcceptDialog", 8 * EDSCALE); + // Make buttons with short texts such as "OK" easier to click/tap. + p_theme->set_constant("buttons_min_width", "AcceptDialog", p_config.dialogs_buttons_min_size.x * EDSCALE); + p_theme->set_constant("buttons_min_height", "AcceptDialog", p_config.dialogs_buttons_min_size.y * EDSCALE); // FileDialog. p_theme->set_icon("folder", "FileDialog", p_theme->get_icon(SNAME("Folder"), EditorStringName(EditorIcons))); @@ -2158,6 +2166,28 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme p_theme->set_constant("text_highlight_v_padding", "EditorHelp", 2 * EDSCALE); } + // EditorHelpBitTitle. + { + Ref<StyleBoxFlat> style = p_config.tree_panel_style->duplicate(); + style->set_bg_color(p_config.dark_theme ? style->get_bg_color().lightened(0.04) : style->get_bg_color().darkened(0.04)); + style->set_border_color(p_config.dark_theme ? style->get_border_color().lightened(0.04) : style->get_border_color().darkened(0.04)); + style->set_corner_radius(CORNER_BOTTOM_LEFT, 0); + style->set_corner_radius(CORNER_BOTTOM_RIGHT, 0); + + p_theme->set_type_variation("EditorHelpBitTitle", "RichTextLabel"); + p_theme->set_stylebox("normal", "EditorHelpBitTitle", style); + } + + // EditorHelpBitContent. + { + Ref<StyleBoxFlat> style = p_config.tree_panel_style->duplicate(); + style->set_corner_radius(CORNER_TOP_LEFT, 0); + style->set_corner_radius(CORNER_TOP_RIGHT, 0); + + p_theme->set_type_variation("EditorHelpBitContent", "RichTextLabel"); + p_theme->set_stylebox("normal", "EditorHelpBitContent", style); + } + // Asset Library. p_theme->set_stylebox("bg", "AssetLib", p_config.base_empty_style); p_theme->set_stylebox("panel", "AssetLib", p_config.content_panel_style); diff --git a/editor/themes/editor_theme_manager.h b/editor/themes/editor_theme_manager.h index 3eb1dd5ffd..5e7bd00083 100644 --- a/editor/themes/editor_theme_manager.h +++ b/editor/themes/editor_theme_manager.h @@ -62,6 +62,7 @@ class EditorThemeManager { int base_spacing = 4; int extra_spacing = 0; + Size2 dialogs_buttons_min_size = Size2(105, 34); int border_width = 0; int corner_radius = 3; diff --git a/main/main.cpp b/main/main.cpp index ed74094c9e..78a539fff7 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -842,21 +842,26 @@ void Main::test_cleanup() { #endif int Main::test_entrypoint(int argc, char *argv[], bool &tests_need_run) { -#ifdef TESTS_ENABLED for (int x = 0; x < argc; x++) { if ((strncmp(argv[x], "--test", 6) == 0) && (strlen(argv[x]) == 6)) { tests_need_run = true; +#ifdef TESTS_ENABLED // TODO: need to come up with different test contexts. // Not every test requires high-level functionality like `ClassDB`. test_setup(); int status = test_main(argc, argv); test_cleanup(); return status; +#else + ERR_PRINT( + "`--test` was specified on the command line, but this Godot binary was compiled without support for unit tests. Aborting.\n" + "To be able to run unit tests, use the `tests=yes` SCons option when compiling Godot.\n"); + return EXIT_FAILURE; +#endif } } -#endif tests_need_run = false; - return 0; + return EXIT_SUCCESS; } /* Engine initialization @@ -2513,12 +2518,10 @@ Error Main::setup2() { // Editor setting class is not available, load config directly. if (!init_use_custom_screen && (editor || project_manager) && EditorPaths::get_singleton()->are_paths_valid()) { - Ref<DirAccess> dir = DirAccess::open(EditorPaths::get_singleton()->get_config_dir()); - ERR_FAIL_COND_V(dir.is_null(), FAILED); + ERR_FAIL_COND_V(!DirAccess::dir_exists_absolute(EditorPaths::get_singleton()->get_config_dir()), FAILED); - String config_file_name = "editor_settings-" + itos(VERSION_MAJOR) + ".tres"; - String config_file_path = EditorPaths::get_singleton()->get_config_dir().path_join(config_file_name); - if (dir->file_exists(config_file_name)) { + String config_file_path = EditorSettings::get_existing_settings_path(); + if (FileAccess::exists(config_file_path)) { Error err; Ref<FileAccess> f = FileAccess::open(config_file_path, FileAccess::READ, &err); if (f.is_valid()) { diff --git a/methods.py b/methods.py index 4d1f4c1cda..01b127ea30 100644 --- a/methods.py +++ b/methods.py @@ -763,19 +763,15 @@ def detect_visual_c_compiler_version(tools_env): def find_visual_c_batch_file(env): from SCons.Tool.MSCommon.vc import get_default_version, get_host_target, find_batch_file, find_vc_pdir - # Syntax changed in SCons 4.4.0. - from SCons import __version__ as scons_raw_version - - scons_ver = env._get_major_minor_revision(scons_raw_version) - msvc_version = get_default_version(env) - if scons_ver >= (4, 4, 0): + # Syntax changed in SCons 4.4.0. + if env.scons_version >= (4, 4, 0): (host_platform, target_platform, _) = get_host_target(env, msvc_version) else: (host_platform, target_platform, _) = get_host_target(env) - if scons_ver < (4, 6, 0): + if env.scons_version < (4, 6, 0): return find_batch_file(env, msvc_version, host_platform, target_platform)[0] # Scons 4.6.0+ removed passing env, so we need to get the product_dir ourselves first, @@ -926,7 +922,11 @@ def get_compiler_version(env): # Not using -dumpversion as some GCC distros only return major, and # Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803 try: - version = subprocess.check_output([env.subst(env["CXX"]), "--version"]).strip().decode("utf-8") + version = ( + subprocess.check_output([env.subst(env["CXX"]), "--version"], shell=(os.name == "nt")) + .strip() + .decode("utf-8") + ) except (subprocess.CalledProcessError, OSError): print("Couldn't parse CXX environment variable to infer compiler version.") return ret @@ -990,6 +990,10 @@ def using_emcc(env): def show_progress(env): + if env["ninja"]: + # Has its own progress/tracking tool that clashes with ours + return + import sys from SCons.Script import Progress, Command, AlwaysBuild diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index 6471c5a142..4706ed37f0 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -256,17 +256,14 @@ Compatibility methods registered. GH-88014 -------- -Validate extension JSON: API was removed: classes/VisualShaderNodeComment - -Removed VisualShaderNodeComment, which is replaced by VisualShaderNodeFrame. +Validate extension JSON: API was removed: classes/VisualShaderNodeComment/methods/get_title +Validate extension JSON: API was removed: classes/VisualShaderNodeComment/methods/set_title +Validate extension JSON: API was removed: classes/VisualShaderNodeComment/properties/title GH-87888 -------- Validate extension JSON: API was removed: classes/Skeleton3D/properties/animate_physical_bones -Validate extension JSON: API was removed: classes/SkeletonIK3D/methods/get_interpolation -Validate extension JSON: API was removed: classes/SkeletonIK3D/methods/set_interpolation -Validate extension JSON: API was removed: classes/SkeletonIK3D/properties/interpolation These base class is changed to SkeletonModifier3D which is processed by Skeleton3D with the assumption that it is Skeleton3D's child. @@ -278,6 +275,7 @@ Validate extension JSON: API was removed: classes/Skeleton3D/signals/bone_pose_c They have been replaced by a safer API due to performance concerns. Compatibility method registered. + GH-90747 -------- Validate extension JSON: API was removed: classes/NavigationRegion2D/methods/get_avoidance_layers @@ -290,3 +288,30 @@ Validate extension JSON: API was removed: classes/NavigationRegion2D/methods/get Validate extension JSON: API was removed: classes/NavigationRegion2D/properties/constrain_avoidance Experimental NavigationRegion2D feature "constrain_avoidance" was discontinued with no replacement. + + +GH-90645 +-------- +Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/get_tracker_desc +Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/get_tracker_name +Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/get_tracker_type +Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/set_tracker_desc +Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/set_tracker_name +Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/set_tracker_type +Validate extension JSON: API was removed: classes/XRPositionalTracker/properties/description +Validate extension JSON: API was removed: classes/XRPositionalTracker/properties/name +Validate extension JSON: API was removed: classes/XRPositionalTracker/properties/type +Validate extension JSON: Error: Field 'classes/WebXRInterface/methods/get_input_source_tracker/return_value': type changed value in new API, from "XRPositionalTracker" to "XRControllerTracker". +Validate extension JSON: Error: Field 'classes/XRServer/methods/add_tracker/arguments/0': type changed value in new API, from "XRPositionalTracker" to "XRTracker". +Validate extension JSON: Error: Field 'classes/XRServer/methods/get_tracker/return_value': type changed value in new API, from "XRPositionalTracker" to "XRTracker". +Validate extension JSON: Error: Field 'classes/XRServer/methods/remove_tracker/arguments/0': type changed value in new API, from "XRPositionalTracker" to "XRTracker". + +All trackers now have an XRTracker base, and the XRServer uses the XRTracker type. + + +GH-90732 +-------- +Validate extension JSON: Error: Field 'classes/TextServer/methods/shaped_text_get_word_breaks/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/TextServerExtension/methods/_shaped_text_get_word_breaks/arguments': size changed value in new API, from 2 to 3. + +Added optional argument. Compatibility method registered. diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp index ad70772270..e9a7009d7c 100644 --- a/modules/cvtt/image_compress_cvtt.cpp +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -302,8 +302,6 @@ void image_decompress_cvtt(Image *p_image) { int y_end = y_start + 4; for (int x_start = 0; x_start < w; x_start += 4 * cvtt::NumParallelBlocks) { - int x_end = x_start + 4 * cvtt::NumParallelBlocks; - uint8_t input_blocks[16 * cvtt::NumParallelBlocks]; memset(input_blocks, 0, sizeof(input_blocks)); @@ -315,6 +313,8 @@ void image_decompress_cvtt(Image *p_image) { memcpy(input_blocks, in_bytes, 16 * num_real_blocks); in_bytes += 16 * num_real_blocks; + int x_end = x_start + 4 * num_real_blocks; + if (is_hdr) { if (is_signed) { cvtt::Kernels::DecodeBC6HS(output_blocks_hdr, input_blocks); diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index bde23289f4..95b4a91809 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -320,7 +320,7 @@ Error FBXDocument::_parse_nodes(Ref<FBXState> p_state) { node->set_name(_as_string(fbx_node->name)); node->set_original_name(node->get_name()); } else if (fbx_node->is_root) { - node->set_name("Root"); + node->set_name("RootNode"); } if (fbx_node->camera) { node->camera = fbx_node->camera->typed_id; @@ -1629,6 +1629,9 @@ void FBXDocument::_generate_skeleton_bone_node(Ref<FBXState> p_state, const GLTF active_skeleton = skeleton; current_node = active_skeleton; + if (active_skeleton) { + p_scene_parent = active_skeleton; + } if (requires_extra_node) { current_node = nullptr; @@ -2019,8 +2022,8 @@ Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool GLTFNodeIndex fbx_root = state->root_nodes.write[0]; Node *fbx_root_node = state->get_scene_node(fbx_root); Node *root = fbx_root_node; - if (fbx_root_node && fbx_root_node->get_parent()) { - root = fbx_root_node->get_parent(); + if (root && root->get_owner() && root->get_owner() != root) { + root = root->get_owner(); } ERR_FAIL_NULL_V(root, nullptr); _process_mesh_instances(state, root); diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 601db5414b..35b69fab8c 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -229,6 +229,36 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re } } +String GDScriptDocGen::_docvalue_from_expression(const GDP::ExpressionNode *p_expression) { + ERR_FAIL_NULL_V(p_expression, String()); + + if (p_expression->is_constant) { + return _docvalue_from_variant(p_expression->reduced_value); + } + + switch (p_expression->type) { + case GDP::Node::ARRAY: { + const GDP::ArrayNode *array = static_cast<const GDP::ArrayNode *>(p_expression); + return array->elements.is_empty() ? "[]" : "[...]"; + } break; + case GDP::Node::CALL: { + const GDP::CallNode *call = static_cast<const GDP::CallNode *>(p_expression); + return call->function_name.operator String() + (call->arguments.is_empty() ? "()" : "(...)"); + } break; + case GDP::Node::DICTIONARY: { + const GDP::DictionaryNode *dict = static_cast<const GDP::DictionaryNode *>(p_expression); + return dict->elements.is_empty() ? "{}" : "{...}"; + } break; + case GDP::Node::IDENTIFIER: { + const GDP::IdentifierNode *id = static_cast<const GDP::IdentifierNode *>(p_expression); + return id->name; + } break; + default: { + return "<unknown>"; + } break; + } +} + void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) { p_script->_clear_doc(); @@ -328,16 +358,12 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_ method_doc.return_type = "Variant"; } - for (const GDScriptParser::ParameterNode *p : m_func->parameters) { + for (const GDP::ParameterNode *p : m_func->parameters) { DocData::ArgumentDoc arg_doc; arg_doc.name = p->identifier->name; _doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration); if (p->initializer != nullptr) { - if (p->initializer->is_constant) { - arg_doc.default_value = _docvalue_from_variant(p->initializer->reduced_value); - } else { - arg_doc.default_value = "<unknown>"; - } + arg_doc.default_value = _docvalue_from_expression(p->initializer); } method_doc.arguments.push_back(arg_doc); } @@ -359,7 +385,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_ signal_doc.is_experimental = m_signal->doc_data.is_experimental; signal_doc.experimental_message = m_signal->doc_data.experimental_message; - for (const GDScriptParser::ParameterNode *p : m_signal->parameters) { + for (const GDP::ParameterNode *p : m_signal->parameters) { DocData::ArgumentDoc arg_doc; arg_doc.name = p->identifier->name; _doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration); @@ -405,12 +431,8 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_ break; } - if (m_var->initializer) { - if (m_var->initializer->is_constant) { - prop_doc.default_value = _docvalue_from_variant(m_var->initializer->reduced_value); - } else { - prop_doc.default_value = "<unknown>"; - } + if (m_var->initializer != nullptr) { + prop_doc.default_value = _docvalue_from_expression(m_var->initializer); } prop_doc.overridden = false; diff --git a/modules/gdscript/editor/gdscript_docgen.h b/modules/gdscript/editor/gdscript_docgen.h index 651a4fb198..0ae37c4133 100644 --- a/modules/gdscript/editor/gdscript_docgen.h +++ b/modules/gdscript/editor/gdscript_docgen.h @@ -45,6 +45,7 @@ class GDScriptDocGen { static String _get_class_name(const GDP::ClassNode &p_class); static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false); static String _docvalue_from_variant(const Variant &p_variant, int p_recursion_level = 1); + static String _docvalue_from_expression(const GDP::ExpressionNode *p_expression); static void _generate_docs(GDScript *p_script, const GDP::ClassNode *p_class); public: diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 8e74de4242..921ed0b416 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -739,10 +739,18 @@ Error GDScript::reload(bool p_keep_state) { if (source_path.is_empty()) { source_path = get_path(); } - Ref<GDScript> cached_script = GDScriptCache::get_cached_script(source_path); - if (!source_path.is_empty() && cached_script.is_null()) { - MutexLock lock(GDScriptCache::singleton->mutex); - GDScriptCache::singleton->shallow_gdscript_cache[source_path] = Ref<GDScript>(this); + if (!source_path.is_empty()) { + if (GDScriptCache::get_cached_script(source_path).is_null()) { + MutexLock lock(GDScriptCache::singleton->mutex); + GDScriptCache::singleton->shallow_gdscript_cache[source_path] = Ref<GDScript>(this); + } + if (GDScriptCache::has_parser(source_path)) { + Error err = OK; + Ref<GDScriptParserRef> parser_ref = GDScriptCache::get_parser(source_path, GDScriptParserRef::EMPTY, err); + if (parser_ref.is_valid() && parser_ref->get_source_hash() != source.hash()) { + GDScriptCache::remove_parser(source_path); + } + } } } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index cd19887d82..0636ac5083 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -325,7 +325,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c if (!parser->has_class(p_class)) { String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); return ERR_PARSE_ERROR; @@ -400,7 +400,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c if (p_class->extends_path.is_relative_path()) { p_class->extends_path = class_type.script_path.get_base_dir().path_join(p_class->extends_path).simplify_path(); } - Ref<GDScriptParserRef> ext_parser = get_parser_for(p_class->extends_path); + Ref<GDScriptParserRef> ext_parser = parser->get_depended_parser_for(p_class->extends_path); if (ext_parser.is_null()) { push_error(vformat(R"(Could not resolve super class path "%s".)", p_class->extends_path), p_class); return ERR_PARSE_ERROR; @@ -428,7 +428,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c if (GDScript::is_canonically_equal_paths(base_path, parser->script_path)) { base = parser->head->get_datatype(); } else { - Ref<GDScriptParserRef> base_parser = get_parser_for(base_path); + Ref<GDScriptParserRef> base_parser = parser->get_depended_parser_for(base_path); if (base_parser.is_null()) { push_error(vformat(R"(Could not resolve super class "%s".)", name), id); return ERR_PARSE_ERROR; @@ -448,7 +448,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c return ERR_PARSE_ERROR; } - Ref<GDScriptParserRef> info_parser = get_parser_for(info.path); + Ref<GDScriptParserRef> info_parser = parser->get_depended_parser_for(info.path); if (info_parser.is_null()) { push_error(vformat(R"(Could not parse singleton from "%s".)", info.path), id); return ERR_PARSE_ERROR; @@ -644,7 +644,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } else if (Ref<Script>(local.constant->initializer->reduced_value).is_valid()) { Ref<GDScript> gdscript = local.constant->initializer->reduced_value; if (gdscript.is_valid()) { - Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path()); + Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(gdscript->get_script_path()); if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), first_id); return bad_type; @@ -710,7 +710,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type String path = ScriptServer::get_global_class_path(first); String ext = path.get_extension(); if (ext == GDScriptLanguage::get_singleton()->get_extension()) { - Ref<GDScriptParserRef> ref = get_parser_for(path); + Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(path); if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type); return bad_type; @@ -722,7 +722,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) { const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first); - Ref<GDScriptParserRef> ref = get_parser_for(autoload.path); + Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(autoload.path); if (ref.is_null()) { push_error(vformat(R"(The referenced autoload "%s" (from "%s") could not be loaded.)", first, autoload.path), p_type); return bad_type; @@ -776,7 +776,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } 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_script_path()); + Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(gdscript->get_script_path()); if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type); return bad_type; @@ -876,7 +876,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, if (!parser->has_class(p_class)) { String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { push_error(vformat(R"(Could not find script "%s" (While resolving "%s").)", script_path, member.get_name()), p_source); return; @@ -1159,7 +1159,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas if (!parser->has_class(p_class)) { String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); return; @@ -1249,7 +1249,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co if (!parser->has_class(p_class)) { String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); return; @@ -1997,7 +1997,7 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant #ifdef DEBUG_ENABLED if (p_is_local) { - if (p_constant->usages == 0) { + if (p_constant->usages == 0 && !String(p_constant->identifier->name).begins_with("_")) { parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); } } @@ -3556,7 +3556,7 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str String path = ScriptServer::get_global_class_path(p_class_name); String ext = path.get_extension(); if (ext == GDScriptLanguage::get_singleton()->get_extension()) { - Ref<GDScriptParserRef> ref = get_parser_for(path); + Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(path); if (ref.is_null()) { push_error(vformat(R"(Could not find script for class "%s".)", p_class_name), p_source); type.type_source = GDScriptParser::DataType::UNDETECTED; @@ -4078,7 +4078,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident result.builtin_type = Variant::OBJECT; result.native_type = SNAME("Node"); if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") { - Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path); + Ref<GDScriptParserRef> singl_parser = parser->get_depended_parser_for(autoload.path); if (singl_parser.is_valid()) { Error err = singl_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err == OK) { @@ -4092,7 +4092,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (node != nullptr) { Ref<GDScript> scr = node->get_script(); if (scr.is_valid()) { - Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_script_path()); + Ref<GDScriptParserRef> singl_parser = parser->get_depended_parser_for(scr->get_script_path()); if (singl_parser.is_valid()) { Error err = singl_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err == OK) { @@ -4822,10 +4822,6 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo return result; } -const HashMap<String, Ref<GDScriptParserRef>> &GDScriptAnalyzer::get_depended_parsers() { - return depended_parsers; -} - GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) { GDScriptParser::DataType result; result.is_constant = true; @@ -4865,7 +4861,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va // This might be an inner class, so we want to get the parser for the root. // But still get the inner class from that tree. String script_path = gds->get_script_path(); - Ref<GDScriptParserRef> ref = get_parser_for(script_path); + Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(script_path); if (ref.is_null()) { push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); GDScriptParser::DataType error_type; @@ -5619,21 +5615,6 @@ bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { return ClassDB::class_exists(p_class) && ClassDB::is_class_exposed(p_class); } -Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { - Ref<GDScriptParserRef> ref; - if (depended_parsers.has(p_path)) { - ref = depended_parsers[p_path]; - } else { - Error err = OK; - ref = GDScriptCache::get_parser(p_path, GDScriptParserRef::EMPTY, err, parser->script_path); - if (ref.is_valid()) { - depended_parsers[p_path] = ref; - } - } - - return ref; -} - Error GDScriptAnalyzer::resolve_inheritance() { return resolve_class_inheritance(parser->head, true); } @@ -5645,11 +5626,17 @@ Error GDScriptAnalyzer::resolve_interface() { Error GDScriptAnalyzer::resolve_body() { resolve_class_body(parser->head, true); + +#ifdef DEBUG_ENABLED + // Apply here, after all `@warning_ignore`s have been resolved and applied. + parser->apply_pending_warnings(); +#endif + return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::resolve_dependencies() { - for (KeyValue<String, Ref<GDScriptParserRef>> &K : depended_parsers) { + for (KeyValue<String, Ref<GDScriptParserRef>> &K : parser->depended_parsers) { if (K.value.is_null()) { return ERR_PARSE_ERROR; } @@ -5668,15 +5655,9 @@ Error GDScriptAnalyzer::analyze() { } resolve_interface(); - resolve_body(); - -#ifdef DEBUG_ENABLED - // Apply here, after all `@warning_ignore`s have been resolved and applied. - parser->apply_pending_warnings(); -#endif - - if (!parser->errors.is_empty()) { - return ERR_PARSE_ERROR; + err = resolve_body(); + if (err) { + return err; } return resolve_dependencies(); diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index e398ccfdbb..922000df52 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -40,7 +40,6 @@ class GDScriptAnalyzer { GDScriptParser *parser = nullptr; - HashMap<String, Ref<GDScriptParserRef>> depended_parsers; const GDScriptParser::EnumNode *current_enum = nullptr; GDScriptParser::LambdaNode *current_lambda = nullptr; @@ -132,7 +131,6 @@ class GDScriptAnalyzer { void mark_lambda_use_self(); void resolve_pending_lambda_bodies(); bool class_exists(const StringName &p_class) const; - Ref<GDScriptParserRef> get_parser_for(const String &p_path); void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype); #ifdef DEBUG_ENABLED void is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope); @@ -146,7 +144,6 @@ public: Error analyze(); Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable); - const HashMap<String, Ref<GDScriptParserRef>> &get_depended_parsers(); static bool check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); GDScriptAnalyzer(GDScriptParser *p_parser); diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 7c27127dce..ac6f5f05c6 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -38,88 +38,98 @@ #include "core/io/file_access.h" #include "core/templates/vector.h" -bool GDScriptParserRef::is_valid() const { - return parser != nullptr; -} - GDScriptParserRef::Status GDScriptParserRef::get_status() const { return status; } -GDScriptParser *GDScriptParserRef::get_parser() const { +uint32_t GDScriptParserRef::get_source_hash() const { + return source_hash; +} + +GDScriptParser *GDScriptParserRef::get_parser() { + if (parser == nullptr) { + parser = memnew(GDScriptParser); + } return parser; } GDScriptAnalyzer *GDScriptParserRef::get_analyzer() { if (analyzer == nullptr) { - analyzer = memnew(GDScriptAnalyzer(parser)); + analyzer = memnew(GDScriptAnalyzer(get_parser())); } return analyzer; } Error GDScriptParserRef::raise_status(Status p_new_status) { - ERR_FAIL_NULL_V(parser, ERR_INVALID_DATA); - - if (result != OK) { - return result; - } + ERR_FAIL_COND_V(clearing, ERR_BUG); + ERR_FAIL_COND_V(parser == nullptr && status != EMPTY, ERR_BUG); - while (p_new_status > status) { + while (result == OK && p_new_status > status) { switch (status) { case EMPTY: { + // Calling parse will clear the parser, which can destruct another GDScriptParserRef which can clear the last reference to the script with this path, calling remove_script, which clears this GDScriptParserRef. + // It's ok if its the first thing done here. + get_parser()->clear(); status = PARSED; String remapped_path = ResourceLoader::path_remap(path); if (remapped_path.get_extension().to_lower() == "gdc") { - result = parser->parse_binary(GDScriptCache::get_binary_tokens(remapped_path), path); + Vector<uint8_t> tokens = GDScriptCache::get_binary_tokens(remapped_path); + source_hash = hash_djb2_buffer(tokens.ptr(), tokens.size()); + result = get_parser()->parse_binary(tokens, path); } else { - result = parser->parse(GDScriptCache::get_source_code(remapped_path), path, false); + String source = GDScriptCache::get_source_code(remapped_path); + source_hash = source.hash(); + result = get_parser()->parse(source, path, false); } } break; case PARSED: { status = INHERITANCE_SOLVED; - Error inheritance_result = get_analyzer()->resolve_inheritance(); - if (result == OK) { - result = inheritance_result; - } + result = get_analyzer()->resolve_inheritance(); } break; case INHERITANCE_SOLVED: { status = INTERFACE_SOLVED; - Error interface_result = get_analyzer()->resolve_interface(); - if (result == OK) { - result = interface_result; - } + result = get_analyzer()->resolve_interface(); } break; case INTERFACE_SOLVED: { + status = BODY_SOLVED; + result = get_analyzer()->resolve_body(); + } break; + case BODY_SOLVED: { status = FULLY_SOLVED; - Error body_result = get_analyzer()->resolve_body(); - if (result == OK) { - result = body_result; - } + result = get_analyzer()->resolve_dependencies(); } break; case FULLY_SOLVED: { return result; } } - if (result != OK) { - return result; - } } return result; } void GDScriptParserRef::clear() { - if (cleared) { + if (clearing) { return; } - cleared = true; + clearing = true; + + GDScriptParser *lparser = parser; + GDScriptAnalyzer *lanalyzer = analyzer; - if (parser != nullptr) { - memdelete(parser); + parser = nullptr; + analyzer = nullptr; + status = EMPTY; + result = OK; + source_hash = 0; + + clearing = false; + + if (lanalyzer != nullptr) { + memdelete(lanalyzer); } - if (analyzer != nullptr) { - memdelete(analyzer); + if (lparser != nullptr) { + memdelete(lparser); } } @@ -171,8 +181,11 @@ void GDScriptCache::remove_script(const String &p_path) { } if (singleton->parser_map.has(p_path)) { - singleton->parser_map[p_path]->clear(); + // Keep a local reference until it goes out of scope. + // Clearing it can trigger a reference to itself to go out of scope, destructing it before clear finishes. + Ref<GDScriptParserRef> parser_ref = singleton->parser_map[p_path]; singleton->parser_map.erase(p_path); + parser_ref->clear(); } singleton->dependencies.erase(p_path); @@ -198,9 +211,7 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP r_error = ERR_FILE_NOT_FOUND; return ref; } - GDScriptParser *parser = memnew(GDScriptParser); ref.instantiate(); - ref->parser = parser; ref->path = p_path; singleton->parser_map[p_path] = ref.ptr(); } @@ -209,6 +220,17 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP return ref; } +bool GDScriptCache::has_parser(const String &p_path) { + MutexLock lock(singleton->mutex); + return singleton->parser_map.has(p_path); +} + +void GDScriptCache::remove_parser(const String &p_path) { + MutexLock lock(singleton->mutex); + // Can't clear the parser because some other parser might be currently using it in the chain of calls. + singleton->parser_map.erase(p_path); +} + String GDScriptCache::get_source_code(const String &p_path) { Vector<uint8_t> source_file; Error err; @@ -400,13 +422,15 @@ void GDScriptCache::clear() { parser_map_refs.insert(E.value); } + singleton->parser_map.clear(); + for (Ref<GDScriptParserRef> &E : parser_map_refs) { - if (E.is_valid()) + if (E.is_valid()) { E->clear(); + } } parser_map_refs.clear(); - singleton->parser_map.clear(); singleton->shallow_gdscript_cache.clear(); singleton->full_gdscript_cache.clear(); } diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index fc7abbd46e..c738233beb 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -48,6 +48,7 @@ public: PARSED, INHERITANCE_SOLVED, INTERFACE_SOLVED, + BODY_SOLVED, FULLY_SOLVED, }; @@ -57,14 +58,16 @@ private: Status status = EMPTY; Error result = OK; String path; - bool cleared = false; + uint32_t source_hash = 0; + bool clearing = false; friend class GDScriptCache; + friend class GDScript; public: - bool is_valid() const; Status get_status() const; - GDScriptParser *get_parser() const; + uint32_t get_source_hash() const; + GDScriptParser *get_parser(); GDScriptAnalyzer *get_analyzer(); Error raise_status(Status p_new_status); void clear(); @@ -95,6 +98,8 @@ public: static void move_script(const String &p_from, const String &p_to); static void remove_script(const String &p_path); static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String()); + static bool has_parser(const String &p_path); + static void remove_parser(const String &p_path); static String get_source_code(const String &p_path); static Vector<uint8_t> get_binary_tokens(const String &p_path); static Ref<GDScript> get_shallow_script(const String &p_path, Error &r_error, const String &p_owner = String()); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index d706c1b101..c526d9c0a4 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -250,7 +250,7 @@ static bool _can_use_validate_call(const MethodBind *p_method, const Vector<GDSc return true; } -GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) { +GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer) { if (p_expression->is_constant && !(p_expression->get_datatype().is_meta_type && p_expression->get_datatype().kind == GDScriptParser::DataType::CLASS)) { return codegen.add_constant(p_expression->reduced_value); } @@ -781,9 +781,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool named = subscript->is_attribute; StringName name; GDScriptCodeGenerator::Address index; - if (p_index_addr.mode != GDScriptCodeGenerator::Address::NIL) { - index = p_index_addr; - } else if (subscript->is_attribute) { + if (subscript->is_attribute) { if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { GDScriptParser::IdentifierNode *identifier = subscript->attribute; HashMap<StringName, GDScript::MemberInfo>::Iterator MI = codegen.script->member_indices.find(identifier->name); diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 0adbe1ed8e..637d61ca3b 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -149,13 +149,9 @@ class GDScriptCompiler { void _set_error(const String &p_error, const GDScriptParser::Node *p_node); - Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype = true); - GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); + GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false); GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested); List<GDScriptCodeGenerator::Address> _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block); void _clear_addresses(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_addresses); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index eaef8a961c..74d383c57f 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -163,7 +163,7 @@ bool GDScriptLanguage::validate(const String &p_script, const String &p_path, Li r_errors->push_back(e); } - for (KeyValue<String, Ref<GDScriptParserRef>> E : analyzer.get_depended_parsers()) { + for (KeyValue<String, Ref<GDScriptParserRef>> E : parser.get_depended_parsers()) { GDScriptParser *depended_parser = E.value->get_parser(); for (const GDScriptParser::ParserError &pe : depended_parser->get_errors()) { ScriptLanguage::ScriptError e; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index bb5d6770c6..9799c6e610 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -147,23 +147,17 @@ GDScriptParser::GDScriptParser() { } GDScriptParser::~GDScriptParser() { - clear(); -} - -void GDScriptParser::clear() { while (list != nullptr) { Node *element = list; list = list->next; memdelete(element); } +} - head = nullptr; - list = nullptr; - _is_tool = false; - for_completion = false; - errors.clear(); - multiline_stack.clear(); - nodes_in_progress.clear(); +void GDScriptParser::clear() { + GDScriptParser tmp; + tmp = *this; + *this = GDScriptParser(); } void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { @@ -709,6 +703,25 @@ void GDScriptParser::parse_program() { clear_unused_annotations(); } +Ref<GDScriptParserRef> GDScriptParser::get_depended_parser_for(const String &p_path) { + Ref<GDScriptParserRef> ref; + if (depended_parsers.has(p_path)) { + ref = depended_parsers[p_path]; + } else { + Error err = OK; + ref = GDScriptCache::get_parser(p_path, GDScriptParserRef::EMPTY, err, script_path); + if (ref.is_valid()) { + depended_parsers[p_path] = ref; + } + } + + return ref; +} + +const HashMap<String, Ref<GDScriptParserRef>> &GDScriptParser::get_depended_parsers() { + return depended_parsers; +} + GDScriptParser::ClassNode *GDScriptParser::find_class(const String &p_qualified_name) const { String first = p_qualified_name.get_slice("::", 0); @@ -4071,6 +4084,7 @@ bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) { push_error(R"("@onready" can only be used in classes that inherit "Node".)", p_annotation); + return false; } VariableNode *variable = static_cast<VariableNode *>(p_target); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index d047fa8e46..77cb8136f8 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1321,6 +1321,7 @@ public: private: friend class GDScriptAnalyzer; + friend class GDScriptParserRef; bool _is_tool = false; String script_path; @@ -1329,6 +1330,7 @@ private: bool can_break = false; bool can_continue = false; List<bool> multiline_stack; + HashMap<String, Ref<GDScriptParserRef>> depended_parsers; ClassNode *head = nullptr; Node *list = nullptr; @@ -1558,6 +1560,8 @@ public: Error parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path); ClassNode *get_tree() const { return head; } bool is_tool() const { return _is_tool; } + Ref<GDScriptParserRef> get_depended_parser_for(const String &p_path); + const HashMap<String, Ref<GDScriptParserRef>> &get_depended_parsers(); ClassNode *find_class(const String &p_qualified_name) const; bool has_class(const GDScriptParser::ClassNode *p_class) const; static Variant::Type get_builtin_type(const StringName &p_type); // Excluding `Variant::NIL` and `Variant::OBJECT`. diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md index 72b5316532..714e38397f 100644 --- a/modules/gdscript/tests/README.md +++ b/modules/gdscript/tests/README.md @@ -13,6 +13,12 @@ The `script/completion` folder contains test for the GDScript autocompletion. Each test case consists of at least one `.gd` file, which contains the code, and one `.cfg` file, which contains expected results and configuration. Inside of the GDScript file the character `➡` represents the cursor position, at which autocompletion is invoked. +The script files won't be parsable GDScript since it contains an invalid char and and often the code is not complete during autocompletion. To allow for a valid base when used with a scene, the +runner will remove the line which contains `➡`. Therefore the scripts need to be valid if this line is removed, otherwise the test might behave in unexpected ways. This may for example require +adding an additional `pass` statement. + +This also means, that the runner will add the script to its owner node, so the script should not be loaded through the scene file. + The config file contains two section: `[input]` contains keys that configure the test environment. The following keys are possible: @@ -20,6 +26,7 @@ The config file contains two section: - `cs: boolean = false`: If `true`, the test will be skipped when running a non C# build. - `use_single_quotes: boolean = false`: Configures the corresponding editor setting for the test. - `scene: String`: Allows to specify a scene which is opened while autocompletion is performed. If this is not set the test runner will search for a `.tscn` file with the same basename as the GDScript file. If that isn't found either, autocompletion will behave as if no scene was opened. +- `node_path: String`: The node path of the node which holds the current script inside of the scene. Defaults to the scene root node. `[output]` specifies the expected results for the test. The following key are supported: diff --git a/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn b/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn new file mode 100644 index 0000000000..d3dea6b12b --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn @@ -0,0 +1,3 @@ +[gd_scene load_steps=1 format=3 uid="uid://dl28pdkxcjvym"] + +[node name="GetNode" type="Node"] diff --git a/modules/gdscript/tests/scripts/completion/argument_options/connect.cfg b/modules/gdscript/tests/scripts/completion/argument_options/connect.cfg new file mode 100644 index 0000000000..8c3fbf90da --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/connect.cfg @@ -0,0 +1,8 @@ +[input] +scene="res://completion/argument_options/argument_options.tscn" +[output] +include=[ + ; Node + {"display": "\"signal_a\""}, + {"display": "\"child_entered_tree\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/argument_options/connect.gd b/modules/gdscript/tests/scripts/completion/argument_options/connect.gd new file mode 100644 index 0000000000..1137070c4e --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/connect.gd @@ -0,0 +1,7 @@ +extends Node + +signal signal_a() + +func _ready(): + connect(➡) + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal/dollar.gd b/modules/gdscript/tests/scripts/completion/get_node/literal/dollar.gd index df458a9435..dc7cc99554 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/literal/dollar.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/literal/dollar.gd @@ -2,3 +2,4 @@ extends Node func a(): %AnimationPlayer.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal/percent.gd b/modules/gdscript/tests/scripts/completion/get_node/literal/percent.gd index 7050761b86..5586317938 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/literal/percent.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/literal/percent.gd @@ -2,3 +2,4 @@ extends Node func a(): $UniqueAnimationPlayer.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_class_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_class_scene.gd index a84283a1de..69fcac4471 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_class_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_class_scene.gd @@ -2,3 +2,4 @@ extends Node func a(): $A.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_native_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_native_scene.gd index 6e3fee1696..0b13a207ff 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_native_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_native_scene.gd @@ -2,3 +2,4 @@ extends Node func a(): $AnimationPlayer.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/percent_class_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/percent_class_scene.gd index 27f059c944..48945db38e 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/percent_class_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/percent_class_scene.gd @@ -2,3 +2,4 @@ extends Node func a(): %UniqueA.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/percent_native_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/percent_native_scene.gd index 07068fc5a4..3684edc73b 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/percent_native_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/percent_native_scene.gd @@ -2,3 +2,4 @@ extends Node func a(): %UniqueAnimationPlayer.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local/local.gd b/modules/gdscript/tests/scripts/completion/get_node/local/local.gd index 596ad80ef2..dcd232d82d 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local/local.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local/local.gd @@ -3,3 +3,4 @@ extends Node func a(): var test = $AnimationPlayer test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd b/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd index 6f87af3c85..7710c2d13b 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd @@ -3,3 +3,4 @@ extends Node func a(): var test := $AnimationPlayer test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd index a710c8bbd7..6b29bf5526 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd @@ -3,3 +3,4 @@ extends Node func a(): var test := $A test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd index 6f87af3c85..7710c2d13b 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd @@ -3,3 +3,4 @@ extends Node func a(): var test := $AnimationPlayer test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_scene/class_local_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_scene/class_local_scene.gd index 2fc88f93dd..02c0d2dceb 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_scene/class_local_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_scene/class_local_scene.gd @@ -3,3 +3,4 @@ extends Node func a(): var test = $A test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_scene/native_local_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_scene/native_local_scene.gd index 596ad80ef2..dcd232d82d 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_scene/native_local_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_scene/native_local_scene.gd @@ -3,3 +3,4 @@ extends Node func a(): var test = $AnimationPlayer test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_typehint/class_local_typehint.gd b/modules/gdscript/tests/scripts/completion/get_node/local_typehint/class_local_typehint.gd index b6d2074939..3277beccab 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_typehint/class_local_typehint.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_typehint/class_local_typehint.gd @@ -5,3 +5,4 @@ const A := preload("res://completion/class_a.notest.gd") func a(): var test: A = $A test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_typehint/native_local_typehint.gd b/modules/gdscript/tests/scripts/completion/get_node/local_typehint/native_local_typehint.gd index 13b541a35d..e6d22af296 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_typehint/native_local_typehint.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_typehint/native_local_typehint.gd @@ -3,3 +3,4 @@ extends Node func a(): var test: AnimationPlayer = $AnimationPlayer test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene/class_local_typehint_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene/class_local_typehint_scene.gd index b6d2074939..3277beccab 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene/class_local_typehint_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene/class_local_typehint_scene.gd @@ -5,3 +5,4 @@ const A := preload("res://completion/class_a.notest.gd") func a(): var test: A = $A test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene/native_local_typehint_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene/native_local_typehint_scene.gd index 13b541a35d..e6d22af296 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene/native_local_typehint_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene/native_local_typehint_scene.gd @@ -3,3 +3,4 @@ extends Node func a(): var test: AnimationPlayer = $AnimationPlayer test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_broad/class_local_typehint_scene_broad.notest.gd b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_broad/class_local_typehint_scene_broad.notest.gd index 5c785b3ddc..3266679a6a 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_broad/class_local_typehint_scene_broad.notest.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_broad/class_local_typehint_scene_broad.notest.gd @@ -4,3 +4,4 @@ extends Node func a(): var test: Node = $A test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_broad/native_local_typehint_scene_broad.notest.gd b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_broad/native_local_typehint_scene_broad.notest.gd index 57f4e16e3c..f637fc3d0f 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_broad/native_local_typehint_scene_broad.notest.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_broad/native_local_typehint_scene_broad.notest.gd @@ -4,3 +4,4 @@ extends Node func a(): var test: Node = $AnimationPlayer test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_incompatible/class_local_typehint_scene_incompatible.gd b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_incompatible/class_local_typehint_scene_incompatible.gd index c6adfe0dd3..8ce75d9996 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_incompatible/class_local_typehint_scene_incompatible.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_incompatible/class_local_typehint_scene_incompatible.gd @@ -3,3 +3,4 @@ extends Node func a(): var test: Area2D = $A test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_incompatible/native_local_typehint_scene_incompatible.gd b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_incompatible/native_local_typehint_scene_incompatible.gd index f53fce9bfe..2b39e3ada7 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_incompatible/native_local_typehint_scene_incompatible.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_typehint_scene_incompatible/native_local_typehint_scene_incompatible.gd @@ -3,3 +3,4 @@ extends Node func a(): var test: Area2D = $AnimationPlayer test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member/member.gd b/modules/gdscript/tests/scripts/completion/get_node/member/member.gd index 6bcc0a0298..80b4943953 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member/member.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member/member.gd @@ -4,3 +4,4 @@ var test = $AnimationPlayer func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd b/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd index 542197e643..97b288334e 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd @@ -4,3 +4,4 @@ var test := $AnimationPlayer func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd index da0b1b11d4..402fd1d275 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd @@ -4,3 +4,4 @@ var test := $A func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd index 542197e643..97b288334e 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd @@ -4,3 +4,4 @@ var test := $AnimationPlayer func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_scene/class_member_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_scene/class_member_scene.gd index 4a35661e94..6188a6e843 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_scene/class_member_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_scene/class_member_scene.gd @@ -4,3 +4,4 @@ var test = $A func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_scene/native_member_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_scene/native_member_scene.gd index 6bcc0a0298..80b4943953 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_scene/native_member_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_scene/native_member_scene.gd @@ -4,3 +4,4 @@ var test = $AnimationPlayer func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_typehint/class_member_typehint.gd b/modules/gdscript/tests/scripts/completion/get_node/member_typehint/class_member_typehint.gd index e4edc3a4e4..01b7c76dbf 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_typehint/class_member_typehint.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_typehint/class_member_typehint.gd @@ -6,3 +6,4 @@ var test: A = $A func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_typehint/native_member_typehint.gd b/modules/gdscript/tests/scripts/completion/get_node/member_typehint/native_member_typehint.gd index eda94ae34d..7f2cb4e8cc 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_typehint/native_member_typehint.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_typehint/native_member_typehint.gd @@ -4,3 +4,4 @@ var test: AnimationPlayer = $AnimationPlayer func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene/class_member_typehint_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene/class_member_typehint_scene.gd index 8f68f54072..43d8b666ab 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene/class_member_typehint_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene/class_member_typehint_scene.gd @@ -6,3 +6,4 @@ const A := preload("res://completion/class_a.notest.gd") func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene/native_member_typehint_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene/native_member_typehint_scene.gd index eda94ae34d..7f2cb4e8cc 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene/native_member_typehint_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene/native_member_typehint_scene.gd @@ -4,3 +4,4 @@ var test: AnimationPlayer = $AnimationPlayer func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_broad/class_member_typehint_scene_broad.gd b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_broad/class_member_typehint_scene_broad.gd index 7b0ed4ecd8..aac450be9f 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_broad/class_member_typehint_scene_broad.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_broad/class_member_typehint_scene_broad.gd @@ -4,3 +4,4 @@ var test: Node = $A func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_broad/native_member_typehint_scene_broad.gd b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_broad/native_member_typehint_scene_broad.gd index 87342f9a21..9eb10e4933 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_broad/native_member_typehint_scene_broad.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_broad/native_member_typehint_scene_broad.gd @@ -4,3 +4,4 @@ var test: Node = $AnimationPlayer func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_incompatible/class_member_typehint_scene_incompatible.gd b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_incompatible/class_member_typehint_scene_incompatible.gd index 5f78bcdf04..ff8214c463 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_incompatible/class_member_typehint_scene_incompatible.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_incompatible/class_member_typehint_scene_incompatible.gd @@ -4,3 +4,4 @@ var test: Area2D = $A func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_incompatible/native_member_typehint_scene_incompatible.gd b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_incompatible/native_member_typehint_scene_incompatible.gd index c14df5cd1b..30cd7d6a21 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_incompatible/native_member_typehint_scene_incompatible.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_typehint_scene_incompatible/native_member_typehint_scene_incompatible.gd @@ -4,3 +4,4 @@ var test: Area2D = $AnimationPlayer func a(): test.➡ + pass diff --git a/modules/gdscript/tests/scripts/parser/features/constants.out b/modules/gdscript/tests/scripts/parser/features/constants.out index 7ec33470d3..d73c5eb7cd 100644 --- a/modules/gdscript/tests/scripts/parser/features/constants.out +++ b/modules/gdscript/tests/scripts/parser/features/constants.out @@ -1,33 +1 @@ GDTEST_OK ->> WARNING ->> Line: 2 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_TEST" is declared but never used in the block. If this is intended, prefix it with an underscore: "__TEST". ->> WARNING ->> Line: 3 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_STRING" is declared but never used in the block. If this is intended, prefix it with an underscore: "__STRING". ->> WARNING ->> Line: 4 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_VECTOR" is declared but never used in the block. If this is intended, prefix it with an underscore: "__VECTOR". ->> WARNING ->> Line: 5 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_ARRAY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__ARRAY". ->> WARNING ->> Line: 6 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_DICTIONARY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__DICTIONARY". ->> WARNING ->> Line: 9 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_HELLO" is declared but never used in the block. If this is intended, prefix it with an underscore: "__HELLO". ->> WARNING ->> Line: 10 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INFINITY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INFINITY". ->> WARNING ->> Line: 11 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_NOT_A_NUMBER" is declared but never used in the block. If this is intended, prefix it with an underscore: "__NOT_A_NUMBER". diff --git a/modules/gdscript/tests/scripts/parser/features/static_typing.out b/modules/gdscript/tests/scripts/parser/features/static_typing.out index 40a8f97416..d73c5eb7cd 100644 --- a/modules/gdscript/tests/scripts/parser/features/static_typing.out +++ b/modules/gdscript/tests/scripts/parser/features/static_typing.out @@ -1,21 +1 @@ GDTEST_OK ->> WARNING ->> Line: 11 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER". ->> WARNING ->> Line: 12 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_TYPED" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_TYPED". ->> WARNING ->> Line: 13 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_TYPED2" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_TYPED2". ->> WARNING ->> Line: 14 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_INFERRED" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_INFERRED". ->> WARNING ->> Line: 15 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_INFERRED2" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_INFERRED2". diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd new file mode 100644 index 0000000000..3d355197e1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd @@ -0,0 +1,4 @@ +func test(): + const UNUSED = "not used" + + const _UNUSED = "not used, but no warning since the constant name starts with an underscore" diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out new file mode 100644 index 0000000000..99ced48433 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_LOCAL_CONSTANT +>> The local constant "UNUSED" is declared but never used in the block. If this is intended, prefix it with an underscore: "_UNUSED". diff --git a/modules/gdscript/tests/test_completion.h b/modules/gdscript/tests/test_completion.h index ac9ffcd915..327446acee 100644 --- a/modules/gdscript/tests/test_completion.h +++ b/modules/gdscript/tests/test_completion.h @@ -33,6 +33,7 @@ #ifdef TOOLS_ENABLED +#include "core/config/project_settings.h" #include "core/io/config_file.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" @@ -111,7 +112,10 @@ static void test_directory(const String &p_dir) { // For ease of reading ➡ (0x27A1) acts as sentinel char instead of 0xFFFF in the files. code = code.replace_first(String::chr(0x27A1), String::chr(0xFFFF)); // Require pointer sentinel char in scripts. - CHECK(code.find_char(0xFFFF) != -1); + int location = code.find_char(0xFFFF); + CHECK(location != -1); + + String res_path = ProjectSettings::get_singleton()->localize_path(path.path_join(next)); ConfigFile conf; if (conf.load(path.path_join(next.get_basename() + ".cfg")) != OK) { @@ -137,20 +141,46 @@ static void test_directory(const String &p_dir) { String call_hint; bool forced; - Node *owner = nullptr; + Node *scene = nullptr; if (conf.has_section_key("input", "scene")) { - Ref<PackedScene> scene = ResourceLoader::load(conf.get_value("input", "scene"), "PackedScene", ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP); - if (scene.is_valid()) { - owner = scene->instantiate(); + Ref<PackedScene> packed_scene = ResourceLoader::load(conf.get_value("input", "scene"), "PackedScene", ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP); + if (packed_scene.is_valid()) { + scene = packed_scene->instantiate(); } } else if (dir->file_exists(next.get_basename() + ".tscn")) { - Ref<PackedScene> scene = ResourceLoader::load(path.path_join(next.get_basename() + ".tscn"), "PackedScene"); - if (scene.is_valid()) { - owner = scene->instantiate(); + Ref<PackedScene> packed_scene = ResourceLoader::load(path.path_join(next.get_basename() + ".tscn"), "PackedScene"); + if (packed_scene.is_valid()) { + scene = packed_scene->instantiate(); + } + } + Node *owner = nullptr; + if (scene != nullptr) { + owner = scene->get_node(conf.get_value("input", "node_path", ".")); + } + + if (owner != nullptr) { + // Remove the line which contains the sentinel char, to get a valid script. + Ref<GDScript> scr; + scr.instantiate(); + int start = location; + int end = location; + for (; start >= 0; --start) { + if (code.get(start) == '\n') { + break; + } + } + for (; end < code.size(); ++end) { + if (code.get(end) == '\n') { + break; + } } + scr->set_source_code(code.erase(start, end - start)); + scr->reload(); + scr->set_path(res_path); + owner->set_script(scr); } - GDScriptLanguage::get_singleton()->complete_code(code, path.path_join(next), owner, &options, forced, call_hint); + GDScriptLanguage::get_singleton()->complete_code(code, res_path, owner, &options, forced, call_hint); String contains_excluded; for (ScriptLanguage::CodeCompletionOption &option : options) { for (const Dictionary &E : exclude) { @@ -179,8 +209,8 @@ static void test_directory(const String &p_dir) { CHECK(expected_call_hint == call_hint); CHECK(expected_forced == forced); - if (owner) { - memdelete(owner); + if (scene) { + memdelete(scene); } } next = dir->get_next(); diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index f8c35ab6d1..8f0f0d219e 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5685,6 +5685,9 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, const GL active_skeleton = skeleton; current_node = active_skeleton; + if (active_skeleton) { + p_scene_parent = active_skeleton; + } if (requires_extra_node) { current_node = nullptr; @@ -7104,6 +7107,9 @@ Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) { if (p_state->extensions_used.has("GODOT_single_root")) { _generate_scene_node(p_state, 0, nullptr, nullptr); single_root = p_state->scene_nodes[0]; + if (single_root && single_root->get_owner() && single_root->get_owner() != single_root) { + single_root = single_root->get_owner(); + } } else { single_root = memnew(Node3D); for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) { diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index b6f5d6ce57..835fb3e59d 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -1908,7 +1908,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d seams_push_constant.slice = uint32_t(i * subslices + k); seams_push_constant.debug = debug; - RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i * subslices + k], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0); rd->draw_list_bind_uniform_set(draw_list, blendseams_raster_uniform, 1); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index a3464ccfc2..ff2ca9f0ce 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -941,6 +941,31 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { to_reload_state.push_back(scr); } + // Deserialize managed callables. + // This is done before reloading script's internal state, so potential callables invoked in properties work. + { + MutexLock lock(ManagedCallable::instances_mutex); + + for (const KeyValue<ManagedCallable *, Array> &elem : ManagedCallable::instances_pending_reload) { + ManagedCallable *managed_callable = elem.key; + const Array &serialized_data = elem.value; + + GCHandleIntPtr delegate = { nullptr }; + + bool success = GDMonoCache::managed_callbacks.DelegateUtils_TryDeserializeDelegateWithGCHandle( + &serialized_data, &delegate); + + if (success) { + ERR_CONTINUE(delegate.value == nullptr); + managed_callable->delegate_handle = delegate; + } else if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to deserialize delegate\n"); + } + } + + ManagedCallable::instances_pending_reload.clear(); + } + for (Ref<CSharpScript> &scr : to_reload_state) { for (const ObjectID &obj_id : scr->pending_reload_instances) { Object *obj = ObjectDB::get_instance(obj_id); @@ -963,7 +988,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { properties[G.first] = G.second; } - // Restore serialized state and call OnAfterDeserialization + // Restore serialized state and call OnAfterDeserialize. GDMonoCache::managed_callbacks.CSharpInstanceBridge_DeserializeState( csi->get_gchandle_intptr(), &properties, &state_backup.event_signals); } @@ -973,30 +998,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { scr->pending_reload_state.clear(); } - // Deserialize managed callables - { - MutexLock lock(ManagedCallable::instances_mutex); - - for (const KeyValue<ManagedCallable *, Array> &elem : ManagedCallable::instances_pending_reload) { - ManagedCallable *managed_callable = elem.key; - const Array &serialized_data = elem.value; - - GCHandleIntPtr delegate = { nullptr }; - - bool success = GDMonoCache::managed_callbacks.DelegateUtils_TryDeserializeDelegateWithGCHandle( - &serialized_data, &delegate); - - if (success) { - ERR_CONTINUE(delegate.value == nullptr); - managed_callable->delegate_handle = delegate; - } else if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print("Failed to deserialize delegate\n"); - } - } - - ManagedCallable::instances_pending_reload.clear(); - } - #ifdef TOOLS_ENABLED // FIXME: Hack to refresh editor in order to display new properties and signals. See if there is a better alternative. if (Engine::get_singleton()->is_editor_hint()) { @@ -2518,13 +2519,20 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { return MethodInfo(); } + MethodInfo mi; for (const CSharpMethodInfo &E : methods) { if (E.name == p_method) { - return E.method_info; + if (mi.name == p_method) { + // We already found a method with the same name before so + // that means this method has overloads, the best we can do + // is return an empty MethodInfo. + return MethodInfo(); + } + mi = E.method_info; } } - return MethodInfo(); + return mi; } Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index eb45ade285..e1ce41edd5 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -2581,6 +2581,10 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.append("\")]"); } + if (p_iprop.is_hidden) { + p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]"); + } + p_output.append(MEMBER_BEGIN "public "); if (prop_allowed_inherited_member_hiding.has(p_itype.proxy_name + "." + p_iprop.proxy_name)) { @@ -2840,7 +2844,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append("\")]"); } - if (p_imethod.is_compat) { + if (p_imethod.is_hidden) { p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]"); } @@ -3654,11 +3658,16 @@ bool BindingsGenerator::_populate_object_type_interfaces() { iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname); iprop.getter = ClassDB::get_property_getter(type_cname, iprop.cname); - if (iprop.setter != StringName()) { - accessor_methods[iprop.setter] = iprop.cname; - } - if (iprop.getter != StringName()) { - accessor_methods[iprop.getter] = iprop.cname; + // If the property is internal hide it; otherwise, hide the getter and setter. + if (property.usage & PROPERTY_USAGE_INTERNAL) { + iprop.is_hidden = true; + } else { + if (iprop.setter != StringName()) { + accessor_methods[iprop.setter] = iprop.cname; + } + if (iprop.getter != StringName()) { + accessor_methods[iprop.getter] = iprop.cname; + } } bool valid = false; @@ -3860,10 +3869,10 @@ bool BindingsGenerator::_populate_object_type_interfaces() { HashMap<StringName, StringName>::Iterator accessor = accessor_methods.find(imethod.cname); if (accessor) { - // We only make internal an accessor method if it's in the same class as the property. + // We only hide an accessor method if it's in the same class as the property. // It's easier this way, but also we don't know if an accessor method in a different class // could have other purposes, so better leave those untouched. - imethod.is_internal = true; + imethod.is_hidden = true; } if (itype.class_doc) { @@ -3892,6 +3901,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { // after all the non-compat methods have been added. The compat methods are added in // reverse so the most recently added ones take precedence over older compat methods. if (imethod.is_compat) { + imethod.is_hidden = true; compat_methods.push_front(imethod); continue; } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index bb0ba0cb00..a397dcb026 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -88,6 +88,14 @@ class BindingsGenerator { StringName setter; StringName getter; + /** + * Determines if the property will be hidden with the [EditorBrowsable(EditorBrowsableState.Never)] + * attribute. + * We do this for propertyies that have the PROPERTY_USAGE_INTERNAL flag, because they are not meant + * to be exposed to scripting but we can't remove them to prevent breaking compatibility. + */ + bool is_hidden = false; + const DocData::PropertyDoc *prop_doc; bool is_deprecated = false; @@ -180,6 +188,14 @@ class BindingsGenerator { bool is_internal = false; /** + * Determines if the method will be hidden with the [EditorBrowsable(EditorBrowsableState.Never)] + * attribute. + * We do this for methods that we don't want to expose but need to be public to prevent breaking + * compat (i.e: methods with 'is_compat' set to true.) + */ + bool is_hidden = false; + + /** * Determines if the method is a compatibility method added to avoid breaking binary compatibility. * These methods will be generated but hidden and are considered deprecated. */ diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp index 33b05d4cc2..c08ccbe4cc 100644 --- a/modules/multiplayer/scene_cache_interface.cpp +++ b/modules/multiplayer/scene_cache_interface.cpp @@ -52,6 +52,9 @@ void SceneCacheInterface::_remove_node_cache(ObjectID p_oid) { if (!nc) { return; } + if (nc->cache_id) { + assigned_ids.erase(nc->cache_id); + } for (KeyValue<int, int> &E : nc->recv_ids) { PeerInfo *pinfo = peers_info.getptr(E.key); ERR_CONTINUE(!pinfo); @@ -117,16 +120,12 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac NodeCache &cache = _track(node); cache.recv_ids.insert(p_from, id); - // Encode path to send ack. - CharString pname = String(path).utf8(); - int len = encode_cstring(pname.get_data(), nullptr); - + // Send ack. Vector<uint8_t> packet; - - packet.resize(1 + 1 + len); + packet.resize(1 + 1 + 4); packet.write[0] = SceneMultiplayer::NETWORK_COMMAND_CONFIRM_PATH; packet.write[1] = valid_rpc_checksum; - encode_cstring(pname.get_data(), &packet.write[2]); + encode_uint32(id, &packet.write[2]); Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer(); ERR_FAIL_COND(multiplayer_peer.is_null()); @@ -137,26 +136,26 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac } void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < 3, "Invalid packet received. Size too small."); + ERR_FAIL_COND_MSG(p_packet_len != 6, "Invalid packet received. Size too small."); Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); ERR_FAIL_NULL(root_node); const bool valid_rpc_checksum = p_packet[1]; + int id = decode_uint32(&p_packet[2]); - String paths; - paths.parse_utf8((const char *)&p_packet[2], p_packet_len - 2); - - const NodePath path = paths; + const ObjectID *oid = assigned_ids.getptr(id); + if (oid == nullptr) { + return; // May be trying to confirm a node that was removed. + } if (valid_rpc_checksum == false) { - ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); + const Node *node = Object::cast_to<Node>(ObjectDB::get_instance(*oid)); + ERR_FAIL_NULL(node); // Bug. + ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + node->get_path()); } - Node *node = root_node->get_node(path); - ERR_FAIL_NULL(node); - - NodeCache *cache = nodes_cache.getptr(node->get_instance_id()); - ERR_FAIL_NULL_MSG(cache, "Invalid packet received. Tries to confirm a node which was not requested."); + NodeCache *cache = nodes_cache.getptr(*oid); + ERR_FAIL_NULL(cache); // Bug. bool *confirmed = cache->confirmed_peers.getptr(p_from); ERR_FAIL_NULL_MSG(confirmed, "Invalid packet received. Tries to confirm a node which was not requested."); @@ -216,6 +215,7 @@ int SceneCacheInterface::make_object_cache(Object *p_obj) { NodeCache &cache = _track(node); if (cache.cache_id == 0) { cache.cache_id = last_send_cache_id++; + assigned_ids[cache.cache_id] = p_obj->get_instance_id(); } return cache.cache_id; } @@ -227,6 +227,7 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r NodeCache &cache = _track(node); if (cache.cache_id == 0) { cache.cache_id = last_send_cache_id++; + assigned_ids[cache.cache_id] = p_obj->get_instance_id(); } r_id = cache.cache_id; @@ -289,5 +290,6 @@ void SceneCacheInterface::clear() { } peers_info.clear(); nodes_cache.clear(); + assigned_ids.clear(); last_send_cache_id = 1; } diff --git a/modules/multiplayer/scene_cache_interface.h b/modules/multiplayer/scene_cache_interface.h index ab4a20c078..73d6bde6ef 100644 --- a/modules/multiplayer/scene_cache_interface.h +++ b/modules/multiplayer/scene_cache_interface.h @@ -44,7 +44,7 @@ private: //path sent caches struct NodeCache { - int cache_id; + int cache_id = 0; HashMap<int, int> recv_ids; // peer id, remote cache id HashMap<int, bool> confirmed_peers; // peer id, confirmed }; @@ -55,6 +55,7 @@ private: }; HashMap<ObjectID, NodeCache> nodes_cache; + HashMap<int, ObjectID> assigned_ids; HashMap<int, PeerInfo> peers_info; int last_send_cache_id = 1; diff --git a/modules/navigation/2d/godot_navigation_server_2d.cpp b/modules/navigation/2d/godot_navigation_server_2d.cpp index 5eefbe4228..bf69adc14c 100644 --- a/modules/navigation/2d/godot_navigation_server_2d.cpp +++ b/modules/navigation/2d/godot_navigation_server_2d.cpp @@ -389,7 +389,16 @@ bool FORWARD_1_C(agent_is_map_changed, RID, p_agent, rid_to_rid); void FORWARD_2(agent_set_paused, RID, p_agent, bool, p_paused, rid_to_rid, bool_to_bool); bool FORWARD_1_C(agent_get_paused, RID, p_agent, rid_to_rid); -void FORWARD_1(free, RID, p_object, rid_to_rid); +void GodotNavigationServer2D::free(RID p_object) { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d && navmesh_generator_2d->owns(p_object)) { + navmesh_generator_2d->free(p_object); + return; + } +#endif // CLIPPER2_ENABLED + NavigationServer3D::get_singleton()->free(p_object); +} + void FORWARD_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable); bool GodotNavigationServer2D::agent_has_avoidance_callback(RID p_agent) const { return NavigationServer3D::get_singleton()->agent_has_avoidance_callback(p_agent); @@ -453,3 +462,20 @@ void GodotNavigationServer2D::query_path(const Ref<NavigationPathQueryParameters p_query_result->set_path_rids(_query_result.path_rids); p_query_result->set_path_owner_ids(_query_result.path_owner_ids); } + +RID GodotNavigationServer2D::source_geometry_parser_create() { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d) { + return navmesh_generator_2d->source_geometry_parser_create(); + } +#endif // CLIPPER2_ENABLED + return RID(); +} + +void GodotNavigationServer2D::source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d) { + navmesh_generator_2d->source_geometry_parser_set_callback(p_parser, p_callback); + } +#endif // CLIPPER2_ENABLED +} diff --git a/modules/navigation/2d/godot_navigation_server_2d.h b/modules/navigation/2d/godot_navigation_server_2d.h index ba375afd33..ea77fa5e6e 100644 --- a/modules/navigation/2d/godot_navigation_server_2d.h +++ b/modules/navigation/2d/godot_navigation_server_2d.h @@ -253,6 +253,9 @@ public: virtual void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override; virtual bool is_baking_navigation_polygon(Ref<NavigationPolygon> p_navigation_polygon) const override; + virtual RID source_geometry_parser_create() override; + virtual void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) override; + virtual Vector<Vector2> simplify_path(const Vector<Vector2> &p_path, real_t p_epsilon) override; }; diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 9000259524..15a645816c 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -53,11 +53,14 @@ NavMeshGenerator2D *NavMeshGenerator2D::singleton = nullptr; Mutex NavMeshGenerator2D::baking_navmesh_mutex; Mutex NavMeshGenerator2D::generator_task_mutex; +RWLock NavMeshGenerator2D::generator_rid_rwlock; bool NavMeshGenerator2D::use_threads = true; bool NavMeshGenerator2D::baking_use_multiple_threads = true; bool NavMeshGenerator2D::baking_use_high_priority_threads = true; HashSet<Ref<NavigationPolygon>> NavMeshGenerator2D::baking_navmeshes; HashMap<WorkerThreadPool::TaskID, NavMeshGenerator2D::NavMeshGeneratorTask2D *> NavMeshGenerator2D::generator_tasks; +RID_Owner<NavMeshGenerator2D::NavMeshGeometryParser2D> NavMeshGenerator2D::generator_parser_owner; +LocalVector<NavMeshGenerator2D::NavMeshGeometryParser2D *> NavMeshGenerator2D::generator_parsers; NavMeshGenerator2D *NavMeshGenerator2D::get_singleton() { return singleton; @@ -126,6 +129,13 @@ void NavMeshGenerator2D::cleanup() { } generator_tasks.clear(); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser2D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); + generator_task_mutex.unlock(); baking_navmesh_mutex.unlock(); } @@ -236,6 +246,15 @@ void NavMeshGenerator2D::generator_parse_geometry_node(Ref<NavigationPolygon> p_ generator_parse_tilemap_node(p_navigation_mesh, p_source_geometry_data, p_node); generator_parse_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_rid_rwlock.read_lock(); + for (const NavMeshGeometryParser2D *parser : generator_parsers) { + if (!parser->callback.is_valid()) { + continue; + } + parser->callback.call(p_navigation_mesh, p_source_geometry_data, p_node); + } + generator_rid_rwlock.read_unlock(); + if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children); @@ -265,7 +284,7 @@ void NavMeshGenerator2D::generator_parse_meshinstance2d_node(const Ref<Navigatio using namespace Clipper2Lib; - Paths64 subject_paths, dummy_clip_paths; + PathsD subject_paths, dummy_clip_paths; for (int i = 0; i < mesh->get_surface_count(); i++) { if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { @@ -276,7 +295,7 @@ void NavMeshGenerator2D::generator_parse_meshinstance2d_node(const Ref<Navigatio continue; } - Path64 subject_path; + PathD subject_path; int index_count = 0; if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { @@ -295,19 +314,19 @@ void NavMeshGenerator2D::generator_parse_meshinstance2d_node(const Ref<Navigatio Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX]; for (int vertex_index : mesh_indices) { const Vector2 &vertex = mesh_vertices[vertex_index]; - const Point64 &point = Point64(vertex.x, vertex.y); + const PointD &point = PointD(vertex.x, vertex.y); subject_path.push_back(point); } } else { for (const Vector2 &vertex : mesh_vertices) { - const Point64 &point = Point64(vertex.x, vertex.y); + const PointD &point = PointD(vertex.x, vertex.y); subject_path.push_back(point); } } subject_paths.push_back(subject_path); } - Paths64 path_solution; + PathsD path_solution; path_solution = Union(subject_paths, dummy_clip_paths, FillRule::NonZero); @@ -315,9 +334,9 @@ void NavMeshGenerator2D::generator_parse_meshinstance2d_node(const Ref<Navigatio Vector<Vector<Vector2>> polypaths; - for (const Path64 &scaled_path : path_solution) { + for (const PathD &scaled_path : path_solution) { Vector<Vector2> shape_outline; - for (const Point64 &scaled_point : scaled_path) { + for (const PointD &scaled_point : scaled_path) { shape_outline.push_back(Point2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y))); } @@ -353,7 +372,7 @@ void NavMeshGenerator2D::generator_parse_multimeshinstance2d_node(const Ref<Navi using namespace Clipper2Lib; - Paths64 mesh_subject_paths, dummy_clip_paths; + PathsD mesh_subject_paths, dummy_clip_paths; for (int i = 0; i < mesh->get_surface_count(); i++) { if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { @@ -364,7 +383,7 @@ void NavMeshGenerator2D::generator_parse_multimeshinstance2d_node(const Ref<Navi continue; } - Path64 subject_path; + PathD subject_path; int index_count = 0; if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { @@ -383,19 +402,19 @@ void NavMeshGenerator2D::generator_parse_multimeshinstance2d_node(const Ref<Navi Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX]; for (int vertex_index : mesh_indices) { const Vector2 &vertex = mesh_vertices[vertex_index]; - const Point64 &point = Point64(vertex.x, vertex.y); + const PointD &point = PointD(vertex.x, vertex.y); subject_path.push_back(point); } } else { for (const Vector2 &vertex : mesh_vertices) { - const Point64 &point = Point64(vertex.x, vertex.y); + const PointD &point = PointD(vertex.x, vertex.y); subject_path.push_back(point); } } mesh_subject_paths.push_back(subject_path); } - Paths64 mesh_path_solution = Union(mesh_subject_paths, dummy_clip_paths, FillRule::NonZero); + PathsD mesh_path_solution = Union(mesh_subject_paths, dummy_clip_paths, FillRule::NonZero); //path_solution = RamerDouglasPeucker(path_solution, 0.025); @@ -409,10 +428,10 @@ void NavMeshGenerator2D::generator_parse_multimeshinstance2d_node(const Ref<Navi for (int i = 0; i < multimesh_instance_count; i++) { const Transform2D multimesh_instance_mesh_instance_xform = multimesh_instance_xform * multimesh->get_instance_transform_2d(i); - for (const Path64 &mesh_path : mesh_path_solution) { + for (const PathD &mesh_path : mesh_path_solution) { Vector<Vector2> shape_outline; - for (const Point64 &mesh_path_point : mesh_path) { + for (const PointD &mesh_path_point : mesh_path) { shape_outline.push_back(Point2(static_cast<real_t>(mesh_path_point.x), static_cast<real_t>(mesh_path_point.y))); } @@ -774,12 +793,12 @@ void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref<NavigationPoly } }; -static void generator_recursive_process_polytree_items(List<TPPLPoly> &p_tppl_in_polygon, const Clipper2Lib::PolyPath64 *p_polypath_item) { +static void generator_recursive_process_polytree_items(List<TPPLPoly> &p_tppl_in_polygon, const Clipper2Lib::PolyPathD *p_polypath_item) { using namespace Clipper2Lib; Vector<Vector2> polygon_vertices; - for (const Point64 &polypath_point : p_polypath_item->Polygon()) { + for (const PointD &polypath_point : p_polypath_item->Polygon()) { polygon_vertices.push_back(Vector2(static_cast<real_t>(polypath_point.x), static_cast<real_t>(polypath_point.y))); } @@ -798,7 +817,7 @@ static void generator_recursive_process_polytree_items(List<TPPLPoly> &p_tppl_in p_tppl_in_polygon.push_back(tp); for (size_t i = 0; i < p_polypath_item->Count(); i++) { - const PolyPath64 *polypath_item = p_polypath_item->Child(i); + const PolyPathD *polypath_item = p_polypath_item->Child(i); generator_recursive_process_polytree_items(p_tppl_in_polygon, polypath_item); } } @@ -813,6 +832,47 @@ bool NavMeshGenerator2D::generator_emit_callback(const Callable &p_callback) { return ce.error == Callable::CallError::CALL_OK; } +RID NavMeshGenerator2D::source_geometry_parser_create() { + RWLockWrite write_lock(generator_rid_rwlock); + + RID rid = generator_parser_owner.make_rid(); + + NavMeshGeometryParser2D *parser = generator_parser_owner.get_or_null(rid); + parser->self = rid; + + generator_parsers.push_back(parser); + + return rid; +} + +void NavMeshGenerator2D::source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) { + RWLockWrite write_lock(generator_rid_rwlock); + + NavMeshGeometryParser2D *parser = generator_parser_owner.get_or_null(p_parser); + ERR_FAIL_NULL(parser); + + parser->callback = p_callback; +} + +bool NavMeshGenerator2D::owns(RID p_object) { + RWLockRead read_lock(generator_rid_rwlock); + return generator_parser_owner.owns(p_object); +} + +void NavMeshGenerator2D::free(RID p_object) { + RWLockWrite write_lock(generator_rid_rwlock); + + if (generator_parser_owner.owns(p_object)) { + NavMeshGeometryParser2D *parser = generator_parser_owner.get_or_null(p_object); + + generator_parsers.erase(parser); + + generator_parser_owner.free(p_object); + } else { + ERR_PRINT("Attempted to free a NavMeshGenerator2D RID that did not exist (or was already freed)."); + } +} + void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data) { if (p_navigation_mesh.is_null() || p_source_geometry_data.is_null()) { return; @@ -832,38 +892,38 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation using namespace Clipper2Lib; - Paths64 traversable_polygon_paths; - Paths64 obstruction_polygon_paths; + PathsD traversable_polygon_paths; + PathsD obstruction_polygon_paths; traversable_polygon_paths.reserve(outline_count + traversable_outlines.size()); obstruction_polygon_paths.reserve(obstruction_outlines.size()); for (int i = 0; i < outline_count; i++) { const Vector<Vector2> &traversable_outline = p_navigation_mesh->get_outline(i); - Path64 subject_path; + PathD subject_path; subject_path.reserve(traversable_outline.size()); for (const Vector2 &traversable_point : traversable_outline) { - const Point64 &point = Point64(traversable_point.x, traversable_point.y); + const PointD &point = PointD(traversable_point.x, traversable_point.y); subject_path.push_back(point); } traversable_polygon_paths.push_back(subject_path); } for (const Vector<Vector2> &traversable_outline : traversable_outlines) { - Path64 subject_path; + PathD subject_path; subject_path.reserve(traversable_outline.size()); for (const Vector2 &traversable_point : traversable_outline) { - const Point64 &point = Point64(traversable_point.x, traversable_point.y); + const PointD &point = PointD(traversable_point.x, traversable_point.y); subject_path.push_back(point); } traversable_polygon_paths.push_back(subject_path); } for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) { - Path64 clip_path; + PathD clip_path; clip_path.reserve(obstruction_outline.size()); for (const Vector2 &obstruction_point : obstruction_outline) { - const Point64 &point = Point64(obstruction_point.x, obstruction_point.y); + const PointD &point = PointD(obstruction_point.x, obstruction_point.y); clip_path.push_back(point); } obstruction_polygon_paths.push_back(clip_path); @@ -880,10 +940,10 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation continue; } - Path64 clip_path; + PathD clip_path; clip_path.reserve(projected_obstruction.vertices.size() / 2); for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) { - const Point64 &point = Point64(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); + const PointD &point = PointD(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); clip_path.push_back(point); } if (!IsPositive(clip_path)) { @@ -902,17 +962,16 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation const int rect_end_x = baking_rect.position[0] + baking_rect.size[0] + baking_rect_offset.x; const int rect_end_y = baking_rect.position[1] + baking_rect.size[1] + baking_rect_offset.y; - Rect64 clipper_rect = Rect64(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y); - RectClip64 rect_clip = RectClip64(clipper_rect); + RectD clipper_rect = RectD(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y); - traversable_polygon_paths = rect_clip.Execute(traversable_polygon_paths); - obstruction_polygon_paths = rect_clip.Execute(obstruction_polygon_paths); + traversable_polygon_paths = RectClip(clipper_rect, traversable_polygon_paths); + obstruction_polygon_paths = RectClip(clipper_rect, obstruction_polygon_paths); } - Paths64 path_solution; + PathsD path_solution; // first merge all traversable polygons according to user specified fill rule - Paths64 dummy_clip_path; + PathsD dummy_clip_path; traversable_polygon_paths = Union(traversable_polygon_paths, dummy_clip_path, FillRule::NonZero); // merge all obstruction polygons, don't allow holes for what is considered "solid" 2D geometry obstruction_polygon_paths = Union(obstruction_polygon_paths, dummy_clip_path, FillRule::NonZero); @@ -934,10 +993,10 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation continue; } - Path64 clip_path; + PathD clip_path; clip_path.reserve(projected_obstruction.vertices.size() / 2); for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) { - const Point64 &point = Point64(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); + const PointD &point = PointD(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); clip_path.push_back(point); } if (!IsPositive(clip_path)) { @@ -961,17 +1020,16 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation const int rect_end_x = baking_rect.position[0] + baking_rect.size[0] + baking_rect_offset.x - border_size; const int rect_end_y = baking_rect.position[1] + baking_rect.size[1] + baking_rect_offset.y - border_size; - Rect64 clipper_rect = Rect64(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y); - RectClip64 rect_clip = RectClip64(clipper_rect); + RectD clipper_rect = RectD(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y); - path_solution = rect_clip.Execute(path_solution); + path_solution = RectClip(clipper_rect, path_solution); } Vector<Vector<Vector2>> new_baked_outlines; - for (const Path64 &scaled_path : path_solution) { + for (const PathD &scaled_path : path_solution) { Vector<Vector2> polypath; - for (const Point64 &scaled_point : scaled_path) { + for (const PointD &scaled_point : scaled_path) { polypath.push_back(Vector2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y))); } new_baked_outlines.push_back(polypath); @@ -983,13 +1041,13 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation return; } - Paths64 polygon_paths; + PathsD polygon_paths; polygon_paths.reserve(new_baked_outlines.size()); for (const Vector<Vector2> &baked_outline : new_baked_outlines) { - Path64 polygon_path; + PathD polygon_path; for (const Vector2 &baked_outline_point : baked_outline) { - const Point64 &point = Point64(baked_outline_point.x, baked_outline_point.y); + const PointD &point = PointD(baked_outline_point.x, baked_outline_point.y); polygon_path.push_back(point); } polygon_paths.push_back(polygon_path); @@ -999,14 +1057,14 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation List<TPPLPoly> tppl_in_polygon, tppl_out_polygon; - PolyTree64 polytree; - Clipper64 clipper_64; + PolyTreeD polytree; + ClipperD clipper_D; - clipper_64.AddSubject(polygon_paths); - clipper_64.Execute(clipper_cliptype, FillRule::NonZero, polytree); + clipper_D.AddSubject(polygon_paths); + clipper_D.Execute(clipper_cliptype, FillRule::NonZero, polytree); for (size_t i = 0; i < polytree.Count(); i++) { - const PolyPath64 *polypath_item = polytree[i]; + const PolyPathD *polypath_item = polytree[i]; generator_recursive_process_polytree_items(tppl_in_polygon, polypath_item); } diff --git a/modules/navigation/2d/nav_mesh_generator_2d.h b/modules/navigation/2d/nav_mesh_generator_2d.h index 2567a170ef..235a84d548 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.h +++ b/modules/navigation/2d/nav_mesh_generator_2d.h @@ -35,6 +35,7 @@ #include "core/object/class_db.h" #include "core/object/worker_thread_pool.h" +#include "core/templates/rid_owner.h" class Node; class NavigationPolygon; @@ -46,6 +47,14 @@ class NavMeshGenerator2D : public Object { static Mutex baking_navmesh_mutex; static Mutex generator_task_mutex; + static RWLock generator_rid_rwlock; + struct NavMeshGeometryParser2D { + RID self; + Callable callback; + }; + static RID_Owner<NavMeshGeometryParser2D> generator_parser_owner; + static LocalVector<NavMeshGeometryParser2D *> generator_parsers; + static bool use_threads; static bool baking_use_multiple_threads; static bool baking_use_high_priority_threads; @@ -97,6 +106,12 @@ public: static void bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable()); static bool is_baking(Ref<NavigationPolygon> p_navigation_polygon); + static RID source_geometry_parser_create(); + static void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback); + + static bool owns(RID p_object); + static void free(RID p_object); + NavMeshGenerator2D(); ~NavMeshGenerator2D(); }; diff --git a/modules/navigation/3d/godot_navigation_server_3d.cpp b/modules/navigation/3d/godot_navigation_server_3d.cpp index 301b4aa8f3..61a128e004 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.cpp +++ b/modules/navigation/3d/godot_navigation_server_3d.cpp @@ -1202,6 +1202,11 @@ COMMAND_1(free, RID, p_object) { } else if (obstacle_owner.owns(p_object)) { internal_free_obstacle(p_object); +#ifndef _3D_DISABLED + } else if (navmesh_generator_3d && navmesh_generator_3d->owns(p_object)) { + navmesh_generator_3d->free(p_object); +#endif // _3D_DISABLED + } else { ERR_PRINT("Attempted to free a NavigationServer RID that did not exist (or was already freed)."); } @@ -1428,6 +1433,23 @@ PathQueryResult GodotNavigationServer3D::_query_path(const PathQueryParameters & return r_query_result; } +RID GodotNavigationServer3D::source_geometry_parser_create() { +#ifndef _3D_DISABLED + if (navmesh_generator_3d) { + return navmesh_generator_3d->source_geometry_parser_create(); + } +#endif // _3D_DISABLED + return RID(); +} + +void GodotNavigationServer3D::source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) { +#ifndef _3D_DISABLED + if (navmesh_generator_3d) { + navmesh_generator_3d->source_geometry_parser_set_callback(p_parser, p_callback); + } +#endif // _3D_DISABLED +} + Vector<Vector3> GodotNavigationServer3D::simplify_path(const Vector<Vector3> &p_path, real_t p_epsilon) { if (p_path.size() <= 2) { return p_path; diff --git a/modules/navigation/3d/godot_navigation_server_3d.h b/modules/navigation/3d/godot_navigation_server_3d.h index 89839ff459..5ba7ed1088 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.h +++ b/modules/navigation/3d/godot_navigation_server_3d.h @@ -264,6 +264,9 @@ public: virtual void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override; virtual bool is_baking_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) const override; + virtual RID source_geometry_parser_create() override; + virtual void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) override; + virtual Vector<Vector3> simplify_path(const Vector<Vector3> &p_path, real_t p_epsilon) override; private: diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index b1b3cbed5d..cc3bbdbf01 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -66,11 +66,14 @@ NavMeshGenerator3D *NavMeshGenerator3D::singleton = nullptr; Mutex NavMeshGenerator3D::baking_navmesh_mutex; Mutex NavMeshGenerator3D::generator_task_mutex; +RWLock NavMeshGenerator3D::generator_rid_rwlock; bool NavMeshGenerator3D::use_threads = true; bool NavMeshGenerator3D::baking_use_multiple_threads = true; bool NavMeshGenerator3D::baking_use_high_priority_threads = true; HashSet<Ref<NavigationMesh>> NavMeshGenerator3D::baking_navmeshes; HashMap<WorkerThreadPool::TaskID, NavMeshGenerator3D::NavMeshGeneratorTask3D *> NavMeshGenerator3D::generator_tasks; +RID_Owner<NavMeshGenerator3D::NavMeshGeometryParser3D> NavMeshGenerator3D::generator_parser_owner; +LocalVector<NavMeshGenerator3D::NavMeshGeometryParser3D *> NavMeshGenerator3D::generator_parsers; NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() { return singleton; @@ -139,6 +142,13 @@ void NavMeshGenerator3D::cleanup() { } generator_tasks.clear(); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser3D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); + generator_task_mutex.unlock(); baking_navmesh_mutex.unlock(); } @@ -254,6 +264,15 @@ void NavMeshGenerator3D::generator_parse_geometry_node(const Ref<NavigationMesh> #endif generator_parse_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_rid_rwlock.read_lock(); + for (const NavMeshGeometryParser3D *parser : generator_parsers) { + if (!parser->callback.is_valid()) { + continue; + } + parser->callback.call(p_navigation_mesh, p_source_geometry_data, p_node); + } + generator_rid_rwlock.read_unlock(); + if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children); @@ -920,4 +939,45 @@ bool NavMeshGenerator3D::generator_emit_callback(const Callable &p_callback) { return ce.error == Callable::CallError::CALL_OK; } +RID NavMeshGenerator3D::source_geometry_parser_create() { + RWLockWrite write_lock(generator_rid_rwlock); + + RID rid = generator_parser_owner.make_rid(); + + NavMeshGeometryParser3D *parser = generator_parser_owner.get_or_null(rid); + parser->self = rid; + + generator_parsers.push_back(parser); + + return rid; +} + +void NavMeshGenerator3D::source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) { + RWLockWrite write_lock(generator_rid_rwlock); + + NavMeshGeometryParser3D *parser = generator_parser_owner.get_or_null(p_parser); + ERR_FAIL_NULL(parser); + + parser->callback = p_callback; +} + +bool NavMeshGenerator3D::owns(RID p_object) { + RWLockRead read_lock(generator_rid_rwlock); + return generator_parser_owner.owns(p_object); +} + +void NavMeshGenerator3D::free(RID p_object) { + RWLockWrite write_lock(generator_rid_rwlock); + + if (generator_parser_owner.owns(p_object)) { + NavMeshGeometryParser3D *parser = generator_parser_owner.get_or_null(p_object); + + generator_parsers.erase(parser); + + generator_parser_owner.free(p_object); + } else { + ERR_PRINT("Attempted to free a NavMeshGenerator3D RID that did not exist (or was already freed)."); + } +} + #endif // _3D_DISABLED diff --git a/modules/navigation/3d/nav_mesh_generator_3d.h b/modules/navigation/3d/nav_mesh_generator_3d.h index 9c9b3bdefe..b46a1736e0 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.h +++ b/modules/navigation/3d/nav_mesh_generator_3d.h @@ -35,6 +35,7 @@ #include "core/object/class_db.h" #include "core/object/worker_thread_pool.h" +#include "core/templates/rid_owner.h" #include "modules/modules_enabled.gen.h" // For csg, gridmap. class Node; @@ -47,6 +48,14 @@ class NavMeshGenerator3D : public Object { static Mutex baking_navmesh_mutex; static Mutex generator_task_mutex; + static RWLock generator_rid_rwlock; + struct NavMeshGeometryParser3D { + RID self; + Callable callback; + }; + static RID_Owner<NavMeshGeometryParser3D> generator_parser_owner; + static LocalVector<NavMeshGeometryParser3D *> generator_parsers; + static bool use_threads; static bool baking_use_multiple_threads; static bool baking_use_high_priority_threads; @@ -102,6 +111,12 @@ public: static void bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable()); static bool is_baking(Ref<NavigationMesh> p_navigation_mesh); + static RID source_geometry_parser_create(); + static void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback); + + static bool owns(RID p_object); + static void free(RID p_object); + NavMeshGenerator3D(); ~NavMeshGenerator3D(); }; diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml index 168e0bf077..b9c69075e1 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayer" inherits="Node3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayer" inherits="Node3D" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> The parent class of all OpenXR composition layer nodes. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml index 2de1977671..dd8a11e2b9 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerCylinder" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerCylinder" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as an internal slice of a cylinder. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml index f6eba7e228..716ea72854 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerEquirect" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerEquirect" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as an internal slice of a sphere. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml index fff592bc4f..6632f90ed2 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerQuad" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerQuad" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as a quad. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 1136ac1b69..05dff7d6ae 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -23,7 +23,7 @@ Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the OpenXR runtime and after the interface has been initialized. </description> </method> - <method name="get_hand_joint_angular_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_angular_velocity] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_angular_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_angular_velocity] obtained from [method XRServer.get_tracker] instead."> <return type="Vector3" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -31,7 +31,7 @@ If handtracking is enabled, returns the angular velocity of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D]! </description> </method> - <method name="get_hand_joint_flags" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_flags] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_flags" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_flags] obtained from [method XRServer.get_tracker] instead."> <return type="int" enum="OpenXRInterface.HandJointFlags" is_bitfield="true" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -39,7 +39,7 @@ If handtracking is enabled, returns flags that inform us of the validity of the tracking data. </description> </method> - <method name="get_hand_joint_linear_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_linear_velocity] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_linear_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_linear_velocity] obtained from [method XRServer.get_tracker] instead."> <return type="Vector3" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -47,7 +47,7 @@ If handtracking is enabled, returns the linear velocity of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D] without worldscale applied! </description> </method> - <method name="get_hand_joint_position" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_position" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_tracker] instead."> <return type="Vector3" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -55,7 +55,7 @@ If handtracking is enabled, returns the position of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D] without worldscale applied! </description> </method> - <method name="get_hand_joint_radius" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_radius] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_radius" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_radius] obtained from [method XRServer.get_tracker] instead."> <return type="float" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -63,7 +63,7 @@ If handtracking is enabled, returns the radius of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is without worldscale applied! </description> </method> - <method name="get_hand_joint_rotation" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_rotation" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_tracker] instead."> <return type="Quaternion" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -71,7 +71,7 @@ If handtracking is enabled, returns the rotation of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. </description> </method> - <method name="get_hand_tracking_source" qualifiers="const" deprecated="Use [member XRHandTracker.hand_tracking_source] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_tracking_source" qualifiers="const" deprecated="Use [member XRHandTracker.hand_tracking_source] obtained from [method XRServer.get_tracker] instead."> <return type="int" enum="OpenXRInterface.HandTrackedSource" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <description> diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp index b3c20ef8b9..f8cc3d1d8c 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -196,7 +196,8 @@ void OpenXRHandTrackingExtension::on_process() { Ref<XRHandTracker> godot_tracker; godot_tracker.instantiate(); godot_tracker->set_hand(i == 0 ? XRHandTracker::HAND_LEFT : XRHandTracker::HAND_RIGHT); - XRServer::get_singleton()->add_hand_tracker(i == 0 ? "/user/left" : "/user/right", godot_tracker); + godot_tracker->set_tracker_name(i == 0 ? "/user/hand_tracker/left" : "/user/hand_tracker/right"); + XRServer::get_singleton()->add_tracker(godot_tracker); hand_trackers[i].godot_tracker = godot_tracker; hand_trackers[i].is_initialized = true; @@ -229,8 +230,7 @@ void OpenXRHandTrackingExtension::on_process() { // For some reason an inactive controller isn't coming back as inactive but has coordinates either as NAN or very large const XrPosef &palm = hand_trackers[i].joint_locations[XR_HAND_JOINT_PALM_EXT].pose; - if ( - !hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) { + if (!hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) { hand_trackers[i].locations.isActive = false; // workaround, make sure its inactive } @@ -249,6 +249,8 @@ void OpenXRHandTrackingExtension::on_process() { const XrPosef &pose = location.pose; Transform3D transform; + Vector3 linear_velocity; + Vector3 angular_velocity; BitField<XRHandTracker::HandJointFlags> flags; if (location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) { @@ -269,27 +271,34 @@ void OpenXRHandTrackingExtension::on_process() { } if (location.locationFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) { flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_LINEAR_VELOCITY_VALID); - godot_tracker->set_hand_joint_linear_velocity((XRHandTracker::HandJoint)joint, Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z)); + linear_velocity = Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z); + godot_tracker->set_hand_joint_linear_velocity((XRHandTracker::HandJoint)joint, linear_velocity); } if (location.locationFlags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) { flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_ANGULAR_VELOCITY_VALID); - godot_tracker->set_hand_joint_angular_velocity((XRHandTracker::HandJoint)joint, Vector3(velocity.angularVelocity.x, velocity.angularVelocity.y, velocity.angularVelocity.z)); + angular_velocity = Vector3(velocity.angularVelocity.x, velocity.angularVelocity.y, velocity.angularVelocity.z); + godot_tracker->set_hand_joint_angular_velocity((XRHandTracker::HandJoint)joint, angular_velocity); } godot_tracker->set_hand_joint_flags((XRHandTracker::HandJoint)joint, flags); godot_tracker->set_hand_joint_transform((XRHandTracker::HandJoint)joint, transform); godot_tracker->set_hand_joint_radius((XRHandTracker::HandJoint)joint, location.radius); - XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN; - if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) { - source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED; - } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) { - source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER; + if (joint == XR_HAND_JOINT_PALM_EXT) { + XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN; + if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) { + source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED; + } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) { + source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER; + } + + godot_tracker->set_hand_tracking_source(source); + godot_tracker->set_pose("default", transform, linear_velocity, angular_velocity); } - godot_tracker->set_hand_tracking_source(source); } } else { godot_tracker->set_has_tracking_data(false); + godot_tracker->invalidate_pose("default"); } } } @@ -311,7 +320,7 @@ void OpenXRHandTrackingExtension::cleanup_hand_tracking() { hand_trackers[i].is_initialized = false; hand_trackers[i].hand_tracker = XR_NULL_HANDLE; - XRServer::get_singleton()->remove_hand_tracker(i == 0 ? "/user/left" : "/user/right"); + XRServer::get_singleton()->remove_tracker(hand_trackers[i].godot_tracker); } } } diff --git a/modules/openxr/extensions/platform/openxr_android_extension.cpp b/modules/openxr/extensions/platform/openxr_android_extension.cpp index de542828c3..04404923ef 100644 --- a/modules/openxr/extensions/platform/openxr_android_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_android_extension.cpp @@ -36,7 +36,6 @@ #include "os_android.h" #include "thread_jandroid.h" -#include <jni.h> #include <openxr/openxr.h> #include <openxr/openxr_platform.h> @@ -48,6 +47,12 @@ OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() { OpenXRAndroidExtension::OpenXRAndroidExtension() { singleton = this; + + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->GetJavaVM(&vm); + activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity()); } HashMap<String, bool *> OpenXRAndroidExtension::get_requested_extensions() { @@ -66,11 +71,6 @@ void OpenXRAndroidExtension::on_before_instance_created() { } loader_init_extension_available = true; - JNIEnv *env = get_jni_env(); - JavaVM *vm; - env->GetJavaVM(&vm); - jobject activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity()); - XrLoaderInitInfoAndroidKHR loader_init_info_android = { .type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR, .next = nullptr, @@ -93,11 +93,6 @@ void *OpenXRAndroidExtension::set_instance_create_info_and_get_next_pointer(void return nullptr; } - JNIEnv *env = get_jni_env(); - JavaVM *vm; - env->GetJavaVM(&vm); - jobject activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity()); - instance_create_info = { .type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR, .next = p_next_pointer, @@ -109,4 +104,9 @@ void *OpenXRAndroidExtension::set_instance_create_info_and_get_next_pointer(void OpenXRAndroidExtension::~OpenXRAndroidExtension() { singleton = nullptr; + + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(activity_object); } diff --git a/modules/openxr/extensions/platform/openxr_android_extension.h b/modules/openxr/extensions/platform/openxr_android_extension.h index e51b5824e8..61f4b02ab6 100644 --- a/modules/openxr/extensions/platform/openxr_android_extension.h +++ b/modules/openxr/extensions/platform/openxr_android_extension.h @@ -34,6 +34,8 @@ #include "../../util.h" #include "../openxr_extension_wrapper.h" +#include <jni.h> + class OpenXRAndroidExtension : public OpenXRExtensionWrapper { public: static OpenXRAndroidExtension *get_singleton(); @@ -49,6 +51,8 @@ public: private: static OpenXRAndroidExtension *singleton; + JavaVM *vm; + jobject activity_object; bool loader_init_extension_available = false; bool create_instance_extension_available = false; diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 7eb9a6ebe1..aa68441f03 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -35,6 +35,7 @@ #include "servers/rendering/rendering_server_globals.h" #include "extensions/openxr_eye_gaze_interaction.h" +#include "thirdparty/openxr/include/openxr/openxr.h" void OpenXRInterface::_bind_methods() { // lifecycle signals @@ -154,9 +155,14 @@ PackedStringArray OpenXRInterface::get_suggested_tracker_names() const { // These are hardcoded in OpenXR, note that they will only be available if added to our action map PackedStringArray arr = { - "left_hand", // /user/hand/left is mapped to our defaults - "right_hand", // /user/hand/right is mapped to our defaults - "/user/treadmill", + "head", // XRPositionalTracker for the users head (Mapped from OpenXR /user/head) + "left_hand", // XRControllerTracker for the users left hand (Mapped from OpenXR /user/hand/left) + "right_hand", // XRControllerTracker for the users right hand (Mapped from OpenXR /user/hand/right) + "/user/hand_tracker/left", // XRHandTracker for the users left hand + "/user/hand_tracker/right", // XRHandTracker for the users right hand + "/user/body_tracker", // XRBodyTracker for the users body + "/user/face_tracker", // XRFaceTracker for the users face + "/user/treadmill" }; for (OpenXRExtensionWrapper *wrapper : OpenXRAPI::get_singleton()->get_registered_extension_wrappers()) { @@ -430,34 +436,31 @@ OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_tracker_ RID tracker_rid = openxr_api->tracker_create(p_tracker_name); ERR_FAIL_COND_V(tracker_rid.is_null(), nullptr); - // create our positional tracker - Ref<XRPositionalTracker> positional_tracker; - positional_tracker.instantiate(); + // Create our controller tracker. + Ref<XRControllerTracker> controller_tracker; + controller_tracker.instantiate(); // We have standardized some names to make things nicer to the user so lets recognize the toplevel paths related to these. if (p_tracker_name == "/user/hand/left") { - positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); - positional_tracker->set_tracker_name("left_hand"); - positional_tracker->set_tracker_desc("Left hand controller"); - positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); + controller_tracker->set_tracker_name("left_hand"); + controller_tracker->set_tracker_desc("Left hand controller"); + controller_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); } else if (p_tracker_name == "/user/hand/right") { - positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); - positional_tracker->set_tracker_name("right_hand"); - positional_tracker->set_tracker_desc("Right hand controller"); - positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); + controller_tracker->set_tracker_name("right_hand"); + controller_tracker->set_tracker_desc("Right hand controller"); + controller_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); } else { - positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); - positional_tracker->set_tracker_name(p_tracker_name); - positional_tracker->set_tracker_desc(p_tracker_name); + controller_tracker->set_tracker_name(p_tracker_name); + controller_tracker->set_tracker_desc(p_tracker_name); } - positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); - xr_server->add_tracker(positional_tracker); + controller_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); + xr_server->add_tracker(controller_tracker); // create a new entry tracker = memnew(Tracker); tracker->tracker_name = p_tracker_name; tracker->tracker_rid = tracker_rid; - tracker->positional_tracker = positional_tracker; + tracker->controller_tracker = controller_tracker; tracker->interaction_profile = RID(); trackers.push_back(tracker); @@ -477,17 +480,17 @@ void OpenXRInterface::tracker_profile_changed(RID p_tracker, RID p_interaction_p if (p_interaction_profile.is_null()) { print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + INTERACTION_PROFILE_NONE); - tracker->positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); + tracker->controller_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); } else { String name = openxr_api->interaction_profile_get_name(p_interaction_profile); print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + name); - tracker->positional_tracker->set_tracker_profile(name); + tracker->controller_tracker->set_tracker_profile(name); } } void OpenXRInterface::handle_tracker(Tracker *p_tracker) { ERR_FAIL_NULL(openxr_api); - ERR_FAIL_COND(p_tracker->positional_tracker.is_null()); + ERR_FAIL_COND(p_tracker->controller_tracker.is_null()); // Note, which actions are actually bound to inputs are handled by our interaction profiles however interaction // profiles are suggested bindings for controller types we know about. OpenXR runtimes can stray away from these @@ -506,15 +509,15 @@ void OpenXRInterface::handle_tracker(Tracker *p_tracker) { switch (action->action_type) { case OpenXRAction::OPENXR_ACTION_BOOL: { bool pressed = openxr_api->get_action_bool(action->action_rid, p_tracker->tracker_rid); - p_tracker->positional_tracker->set_input(action->action_name, Variant(pressed)); + p_tracker->controller_tracker->set_input(action->action_name, Variant(pressed)); } break; case OpenXRAction::OPENXR_ACTION_FLOAT: { real_t value = openxr_api->get_action_float(action->action_rid, p_tracker->tracker_rid); - p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); + p_tracker->controller_tracker->set_input(action->action_name, Variant(value)); } break; case OpenXRAction::OPENXR_ACTION_VECTOR2: { Vector2 value = openxr_api->get_action_vector2(action->action_rid, p_tracker->tracker_rid); - p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); + p_tracker->controller_tracker->set_input(action->action_name, Variant(value)); } break; case OpenXRAction::OPENXR_ACTION_POSE: { Transform3D transform; @@ -523,9 +526,9 @@ void OpenXRInterface::handle_tracker(Tracker *p_tracker) { XRPose::TrackingConfidence confidence = openxr_api->get_action_pose(action->action_rid, p_tracker->tracker_rid, transform, linear, angular); if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { - p_tracker->positional_tracker->set_pose(action->action_name, transform, linear, angular, confidence); + p_tracker->controller_tracker->set_pose(action->action_name, transform, linear, angular, confidence); } else { - p_tracker->positional_tracker->invalidate_pose(action->action_name); + p_tracker->controller_tracker->invalidate_pose(action->action_name); } } break; default: { @@ -567,8 +570,8 @@ void OpenXRInterface::free_trackers() { Tracker *tracker = trackers[i]; openxr_api->tracker_free(tracker->tracker_rid); - xr_server->remove_tracker(tracker->positional_tracker); - tracker->positional_tracker.unref(); + xr_server->remove_tracker(tracker->controller_tracker); + tracker->controller_tracker.unref(); memdelete(tracker); } @@ -1005,7 +1008,7 @@ void OpenXRInterface::handle_hand_tracking(const String &p_path, OpenXRHandTrack OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { OpenXRInterface::Tracker *tracker = find_tracker(p_path); - if (tracker && tracker->positional_tracker.is_valid()) { + if (tracker && tracker->controller_tracker.is_valid()) { XrSpaceLocationFlags location_flags = hand_tracking_ext->get_hand_joint_location_flags(p_hand, XR_HAND_JOINT_PALM_EXT); if (location_flags & (XR_SPACE_LOCATION_ORIENTATION_VALID_BIT + XR_SPACE_LOCATION_POSITION_VALID_BIT)) { @@ -1035,9 +1038,9 @@ void OpenXRInterface::handle_hand_tracking(const String &p_path, OpenXRHandTrack angular_velocity = hand_tracking_ext->get_hand_joint_angular_velocity(p_hand, XR_HAND_JOINT_PALM_EXT); } - tracker->positional_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, confidence); + tracker->controller_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, confidence); } else { - tracker->positional_tracker->invalidate_pose("skeleton"); + tracker->controller_tracker->invalidate_pose("skeleton"); } } } diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index 737f22d642..e916c7dac2 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -35,8 +35,8 @@ #include "extensions/openxr_hand_tracking_extension.h" #include "openxr_api.h" +#include "servers/xr/xr_controller_tracker.h" #include "servers/xr/xr_interface.h" -#include "servers/xr/xr_positional_tracker.h" // declare some default strings #define INTERACTION_PROFILE_NONE "/interaction_profiles/none" @@ -73,7 +73,7 @@ private: struct Tracker { // A tracker we've registered with OpenXR String tracker_name; // Name of our tracker (can be altered from the action map) Vector<Action *> actions; // Actions related to this tracker - Ref<XRPositionalTracker> positional_tracker; // Our positional tracker object that holds our tracker state + Ref<XRControllerTracker> controller_tracker; // Our positional tracker object that holds our tracker state RID tracker_rid; // RID of the tracker registered with our OpenXR API RID interaction_profile; // RID of the interaction profile bound to this tracker (can be null) }; diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml index caf7958f6b..9fd4511d2b 100644 --- a/modules/webxr/doc_classes/WebXRInterface.xml +++ b/modules/webxr/doc_classes/WebXRInterface.xml @@ -114,10 +114,10 @@ </description> </method> <method name="get_input_source_tracker" qualifiers="const"> - <return type="XRPositionalTracker" /> + <return type="XRControllerTracker" /> <param index="0" name="input_source_id" type="int" /> <description> - Gets an [XRPositionalTracker] for the given [param input_source_id]. + Gets an [XRControllerTracker] for the given [param input_source_id]. In the context of WebXR, an input source can be an advanced VR controller like the Oculus Touch or Index controllers, or even a tap on the screen, a spoken voice command or a button press on the device itself. When a non-traditional input source is used, interpret the position and orientation of the [XRPositionalTracker] as a ray pointing at the object the user wishes to interact with. Use this method to get information about the input source that triggered one of these signals: - [signal selectstart] diff --git a/modules/webxr/webxr_interface.compat.inc b/modules/webxr/webxr_interface.compat.inc new file mode 100644 index 0000000000..97a9d44ca9 --- /dev/null +++ b/modules/webxr/webxr_interface.compat.inc @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* webxr_interface.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 DISABLE_DEPRECATED + +Ref<XRPositionalTracker> WebXRInterface::_get_input_source_tracker_bind_compat_90645(int p_input_source_id) const { + return get_input_source_tracker(p_input_source_id); +} + +void WebXRInterface::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("get_input_source_tracker", "input_source_id"), &WebXRInterface::_get_input_source_tracker_bind_compat_90645); +} + +#endif // DISABLE_DEPRECATED diff --git a/modules/webxr/webxr_interface.cpp b/modules/webxr/webxr_interface.cpp index c3efebef0f..4795fcdcd6 100644 --- a/modules/webxr/webxr_interface.cpp +++ b/modules/webxr/webxr_interface.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "webxr_interface.h" +#include "webxr_interface.compat.inc" #include <stdlib.h> diff --git a/modules/webxr/webxr_interface.h b/modules/webxr/webxr_interface.h index 06c18d0486..241dc9fe76 100644 --- a/modules/webxr/webxr_interface.h +++ b/modules/webxr/webxr_interface.h @@ -31,8 +31,8 @@ #ifndef WEBXR_INTERFACE_H #define WEBXR_INTERFACE_H +#include "servers/xr/xr_controller_tracker.h" #include "servers/xr/xr_interface.h" -#include "servers/xr/xr_positional_tracker.h" /** The WebXR interface is a VR/AR interface that can be used on the web. @@ -44,6 +44,11 @@ class WebXRInterface : public XRInterface { protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + static void _bind_compatibility_methods(); + Ref<XRPositionalTracker> _get_input_source_tracker_bind_compat_90645(int p_input_source_id) const; +#endif + public: enum TargetRayMode { TARGET_RAY_MODE_UNKNOWN, @@ -64,7 +69,7 @@ public: virtual String get_reference_space_type() const = 0; virtual String get_enabled_features() const = 0; virtual bool is_input_source_active(int p_input_source_id) const = 0; - virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const = 0; + virtual Ref<XRControllerTracker> get_input_source_tracker(int p_input_source_id) const = 0; virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const = 0; virtual String get_visibility_state() const = 0; virtual float get_display_refresh_rate() const = 0; diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index c6213d1aae..535d464d6f 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -164,8 +164,8 @@ bool WebXRInterfaceJS::is_input_source_active(int p_input_source_id) const { return input_sources[p_input_source_id].active; } -Ref<XRPositionalTracker> WebXRInterfaceJS::get_input_source_tracker(int p_input_source_id) const { - ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, Ref<XRPositionalTracker>()); +Ref<XRControllerTracker> WebXRInterfaceJS::get_input_source_tracker(int p_input_source_id) const { + ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, Ref<XRControllerTracker>()); return input_sources[p_input_source_id].tracker; } @@ -307,7 +307,7 @@ void WebXRInterfaceJS::uninitialize() { for (int i = 0; i < HAND_MAX; i++) { if (hand_trackers[i].is_valid()) { - xr_server->remove_hand_tracker(i == 0 ? "/user/left" : "/user/right"); + xr_server->remove_tracker(hand_trackers[i]); hand_trackers[i].unref(); } @@ -616,7 +616,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { input_source.target_ray_mode = (WebXRInterface::TargetRayMode)tmp_target_ray_mode; input_source.touch_index = touch_index; - Ref<XRPositionalTracker> &tracker = input_source.tracker; + Ref<XRControllerTracker> &tracker = input_source.tracker; if (tracker.is_null()) { tracker.instantiate(); @@ -630,7 +630,6 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { // Input source id's 0 and 1 are always the left and right hands. if (p_input_source_id < 2) { - tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); tracker->set_tracker_name(tracker_name); tracker->set_tracker_desc(p_input_source_id == 0 ? "Left hand controller" : "Right hand controller"); tracker->set_tracker_hand(p_input_source_id == 0 ? XRPositionalTracker::TRACKER_HAND_LEFT : XRPositionalTracker::TRACKER_HAND_RIGHT); @@ -715,6 +714,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { if (unlikely(hand_tracker.is_null())) { hand_tracker.instantiate(); hand_tracker->set_hand(p_input_source_id == 0 ? XRHandTracker::HAND_LEFT : XRHandTracker::HAND_RIGHT); + hand_tracker->set_tracker_name(p_input_source_id == 0 ? "/user/hand_tracker/left" : "/user/hand_tracker/right"); // These flags always apply, since WebXR doesn't give us enough insight to be more fine grained. BitField<XRHandTracker::HandJointFlags> joint_flags(XRHandTracker::HAND_JOINT_FLAG_POSITION_VALID | XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID | XRHandTracker::HAND_JOINT_FLAG_POSITION_TRACKED | XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_TRACKED); @@ -723,7 +723,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { } hand_trackers[p_input_source_id] = hand_tracker; - xr_server->add_hand_tracker(p_input_source_id == 0 ? "/user/left" : "/user/right", hand_tracker); + xr_server->add_tracker(hand_tracker); } hand_tracker->set_has_tracking_data(true); @@ -746,10 +746,12 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { Transform3D palm_transform; palm_transform.origin = (Vector3(start_pos[0], start_pos[1], start_pos[2]) + Vector3(end_pos[0], end_pos[1], end_pos[2])) / 2.0; hand_tracker->set_hand_joint_transform(XRHandTracker::HAND_JOINT_PALM, palm_transform); + hand_tracker->set_pose("default", palm_transform, Vector3(), Vector3()); } } else if (hand_tracker.is_valid()) { hand_tracker->set_has_tracking_data(false); + hand_tracker->invalidate_pose("default"); } } } diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index fc5df3a59b..afce28d410 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -33,6 +33,8 @@ #ifdef WEB_ENABLED +#include "servers/xr/xr_controller_tracker.h" +#include "servers/xr/xr_hand_tracker.h" #include "webxr_interface.h" /** @@ -68,7 +70,7 @@ private: static constexpr uint8_t input_source_count = 16; struct InputSource { - Ref<XRPositionalTracker> tracker; + Ref<XRControllerTracker> tracker; bool active = false; TargetRayMode target_ray_mode; int touch_index = -1; @@ -102,7 +104,7 @@ public: virtual String get_reference_space_type() const override; virtual String get_enabled_features() const override; virtual bool is_input_source_active(int p_input_source_id) const override; - virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const override; + virtual Ref<XRControllerTracker> get_input_source_tracker(int p_input_source_id) const override; virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const override; virtual String get_visibility_state() const override; virtual PackedVector3Array get_play_area() const override; diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h index b1481ebf7b..e21a331ab9 100644 --- a/platform/android/api/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -209,8 +209,6 @@ class JavaClassWrapper : public Object { #ifdef ANDROID_ENABLED RBMap<String, Ref<JavaClass>> class_cache; friend class JavaClass; - jclass activityClass; - jmethodID findClass; jmethodID getDeclaredMethods; jmethodID getFields; jmethodID getParameterTypes; @@ -229,7 +227,6 @@ class JavaClassWrapper : public Object { jmethodID Long_longValue; jmethodID Float_floatValue; jmethodID Double_doubleValue; - jobject classLoader; bool _get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig); #endif diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h index a2d1c08168..5b30c392e7 100644 --- a/platform/android/api/jni_singleton.h +++ b/platform/android/api/jni_singleton.h @@ -241,6 +241,17 @@ public: instance = nullptr; #endif } + + ~JNISingleton() { +#ifdef ANDROID_ENABLED + if (instance) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(instance); + } +#endif + } }; #endif // JNI_SINGLETON_H diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 972a7dbe6a..ab90527bfa 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -321,6 +321,14 @@ void DirAccessJAndroid::setup(jobject p_dir_access_handler) { _current_is_hidden = env->GetMethodID(cls, "isCurrentHidden", "(II)Z"); } +void DirAccessJAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(dir_access_handler); +} + DirAccessJAndroid::DirAccessJAndroid() { } diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index 9aaa78f38c..68578b0fa9 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -89,6 +89,7 @@ public: virtual uint64_t get_space_left() override; static void setup(jobject p_dir_access_handler); + static void terminate(); DirAccessJAndroid(); ~DirAccessJAndroid(); diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 138714634f..6a6d7149ff 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -33,6 +33,7 @@ #include "export_plugin.h" #include "core/os/os.h" +#include "editor/editor_paths.h" #include "editor/editor_settings.h" #include "editor/export/editor_export.h" @@ -46,10 +47,10 @@ void register_android_exporter() { EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); EDITOR_DEF("export/android/android_sdk_path", OS::get_singleton()->get_environment("ANDROID_HOME")); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); - EDITOR_DEF("export/android/debug_keystore", ""); + EDITOR_DEF("export/android/debug_keystore", EditorPaths::get_singleton()->get_debug_keystore_path()); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks")); - EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey"); - EDITOR_DEF("export/android/debug_keystore_pass", "android"); + EDITOR_DEF("export/android/debug_keystore_user", DEFAULT_ANDROID_KEYSTORE_DEBUG_USER); + EDITOR_DEF("export/android/debug_keystore_pass", DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore_pass", PROPERTY_HINT_PASSWORD)); EDITOR_DEF("export/android/force_system_user", false); diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 7cab5e8d90..3b1a534daf 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -831,14 +831,82 @@ bool EditorExportPlatformAndroid::_uses_vulkan() { void EditorExportPlatformAndroid::_notification(int p_what) { #ifndef ANDROID_ENABLED - if (p_what == NOTIFICATION_POSTINITIALIZE) { - if (EditorExport::get_singleton()) { - EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAndroid::_update_preset_status)); - } + switch (p_what) { + case NOTIFICATION_POSTINITIALIZE: { + if (EditorExport::get_singleton()) { + EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAndroid::_update_preset_status)); + } + } break; + + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + if (EditorSettings::get_singleton()->check_changed_settings_in_group("export/android")) { + _create_editor_debug_keystore_if_needed(); + } + } break; } #endif } +void EditorExportPlatformAndroid::_create_editor_debug_keystore_if_needed() { + // Check if we have a valid keytool path. + String keytool_path = get_keytool_path(); + if (!FileAccess::exists(keytool_path)) { + return; + } + + // Check if the current editor debug keystore exists. + String editor_debug_keystore = EDITOR_GET("export/android/debug_keystore"); + if (FileAccess::exists(editor_debug_keystore)) { + return; + } + + // Generate the debug keystore. + String keystore_path = EditorPaths::get_singleton()->get_debug_keystore_path(); + String keystores_dir = keystore_path.get_base_dir(); + if (!DirAccess::exists(keystores_dir)) { + Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Error err = dir_access->make_dir_recursive(keystores_dir); + if (err != OK) { + WARN_PRINT(TTR("Error creating keystores directory:") + "\n" + keystores_dir); + return; + } + } + + if (!FileAccess::exists(keystore_path)) { + String output; + List<String> args; + args.push_back("-genkey"); + args.push_back("-keystore"); + args.push_back(keystore_path); + args.push_back("-storepass"); + args.push_back("android"); + args.push_back("-alias"); + args.push_back(DEFAULT_ANDROID_KEYSTORE_DEBUG_USER); + args.push_back("-keypass"); + args.push_back(DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD); + args.push_back("-keyalg"); + args.push_back("RSA"); + args.push_back("-keysize"); + args.push_back("2048"); + args.push_back("-validity"); + args.push_back("10000"); + args.push_back("-dname"); + args.push_back("cn=Godot, ou=Godot Engine, o=Stichting Godot, c=NL"); + Error error = OS::get_singleton()->execute(keytool_path, args, &output, nullptr, true); + print_verbose(output); + if (error != OK) { + WARN_PRINT("Error: Unable to create debug keystore"); + return; + } + } + + // Update the editor settings. + EditorSettings::get_singleton()->set("export/android/debug_keystore", keystore_path); + EditorSettings::get_singleton()->set("export/android/debug_keystore_user", DEFAULT_ANDROID_KEYSTORE_DEBUG_USER); + EditorSettings::get_singleton()->set("export/android/debug_keystore_pass", DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD); + print_verbose("Updated editor debug keystore to " + keystore_path); +} + void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) { const char **aperms = android_perms; while (*aperms) { @@ -2210,6 +2278,15 @@ String EditorExportPlatformAndroid::get_java_path() { return java_sdk_path.path_join("bin/java" + exe_ext); } +String EditorExportPlatformAndroid::get_keytool_path() { + String exe_ext; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = ".exe"; + } + String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); + return java_sdk_path.path_join("bin/keytool" + exe_ext); +} + String EditorExportPlatformAndroid::get_adb_path() { String exe_ext; if (OS::get_singleton()->get_name() == "Windows") { @@ -3655,6 +3732,7 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() { android_plugins_changed.set(); #endif // DISABLE_DEPRECATED #ifndef ANDROID_ENABLED + _create_editor_debug_keystore_if_needed(); _update_preset_status(); check_for_changes_thread.start(_check_for_changes_poll_thread, this); #endif diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 7bc7bbf9e9..679afdc50f 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -60,6 +60,9 @@ const String ENV_ANDROID_KEYSTORE_RELEASE_PATH = "GODOT_ANDROID_KEYSTORE_RELEASE const String ENV_ANDROID_KEYSTORE_RELEASE_USER = "GODOT_ANDROID_KEYSTORE_RELEASE_USER"; const String ENV_ANDROID_KEYSTORE_RELEASE_PASS = "GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD"; +const String DEFAULT_ANDROID_KEYSTORE_DEBUG_USER = "androiddebugkey"; +const String DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD = "android"; + struct LauncherIcon { const char *export_path; int dimensions = 0; @@ -188,6 +191,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { const Ref<Image> &foreground, const Ref<Image> &background); + static void _create_editor_debug_keystore_if_needed(); + static Vector<ABI> get_enabled_abis(const Ref<EditorExportPreset> &p_preset); static bool _uses_vulkan(); @@ -236,6 +241,8 @@ public: static String get_java_path(); + static String get_keytool_path(); + virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; static bool has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error); diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index f56eda4694..ae336d6f9d 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -31,8 +31,12 @@ #include "file_access_android.h" #include "core/string/print_string.h" +#include "thread_jandroid.h" + +#include <android/asset_manager_jni.h> AAssetManager *FileAccessAndroid::asset_manager = nullptr; +jobject FileAccessAndroid::j_asset_manager = nullptr; String FileAccessAndroid::get_path() const { return path_src; @@ -257,3 +261,16 @@ void FileAccessAndroid::close() { FileAccessAndroid::~FileAccessAndroid() { _close(); } + +void FileAccessAndroid::setup(jobject p_asset_manager) { + JNIEnv *env = get_jni_env(); + j_asset_manager = env->NewGlobalRef(p_asset_manager); + asset_manager = AAssetManager_fromJava(env, j_asset_manager); +} + +void FileAccessAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(j_asset_manager); +} diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index ec613b6687..e79daeafb3 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -35,9 +35,13 @@ #include <android/asset_manager.h> #include <android/log.h> +#include <jni.h> #include <stdio.h> class FileAccessAndroid : public FileAccess { + static AAssetManager *asset_manager; + static jobject j_asset_manager; + mutable AAsset *asset = nullptr; mutable uint64_t len = 0; mutable uint64_t pos = 0; @@ -48,8 +52,6 @@ class FileAccessAndroid : public FileAccess { void _close(); public: - static AAssetManager *asset_manager; - virtual Error open_internal(const String &p_path, int p_mode_flags) override; // open a file virtual bool is_open() const override; // true when file is open @@ -65,6 +67,7 @@ public: virtual bool eof_reached() const override; // reading passed EOF + virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual uint8_t get_8() const override; // get a byte virtual uint16_t get_16() const override; virtual uint32_t get_32() const override; @@ -92,6 +95,10 @@ public: virtual void close() override; + static void setup(jobject p_asset_manager); + + static void terminate(); + ~FileAccessAndroid(); }; diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp index 46d9728632..f28d469d07 100644 --- a/platform/android/file_access_filesystem_jandroid.cpp +++ b/platform/android/file_access_filesystem_jandroid.cpp @@ -53,6 +53,7 @@ jmethodID FileAccessFilesystemJAndroid::_file_write = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_flush = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_exists = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_last_modified = nullptr; +jmethodID FileAccessFilesystemJAndroid::_file_resize = nullptr; String FileAccessFilesystemJAndroid::get_path() const { return path_src; @@ -82,7 +83,7 @@ Error FileAccessFilesystemJAndroid::open_internal(const String &p_path, int p_mo default: return ERR_FILE_CANT_OPEN; - case -1: + case -2: return ERR_FILE_NOT_FOUND; } } @@ -324,6 +325,30 @@ Error FileAccessFilesystemJAndroid::get_error() const { return OK; } +Error FileAccessFilesystemJAndroid::resize(int64_t p_length) { + if (_file_resize) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, FAILED); + ERR_FAIL_COND_V_MSG(!is_open(), FAILED, "File must be opened before use."); + int res = env->CallIntMethod(file_access_handler, _file_resize, id, p_length); + switch (res) { + case 0: + return OK; + case -4: + return ERR_INVALID_PARAMETER; + case -3: + return ERR_FILE_CANT_OPEN; + case -2: + return ERR_FILE_NOT_FOUND; + case -1: + default: + return FAILED; + } + } else { + return ERR_UNAVAILABLE; + } +} + void FileAccessFilesystemJAndroid::flush() { if (_file_flush) { JNIEnv *env = get_jni_env(); @@ -383,6 +408,15 @@ void FileAccessFilesystemJAndroid::setup(jobject p_file_access_handler) { _file_flush = env->GetMethodID(cls, "fileFlush", "(I)V"); _file_exists = env->GetMethodID(cls, "fileExists", "(Ljava/lang/String;)Z"); _file_last_modified = env->GetMethodID(cls, "fileLastModified", "(Ljava/lang/String;)J"); + _file_resize = env->GetMethodID(cls, "fileResize", "(IJ)I"); +} + +void FileAccessFilesystemJAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(file_access_handler); } void FileAccessFilesystemJAndroid::close() { diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h index f33aa64ebe..6a8fc524b7 100644 --- a/platform/android/file_access_filesystem_jandroid.h +++ b/platform/android/file_access_filesystem_jandroid.h @@ -52,6 +52,7 @@ class FileAccessFilesystemJAndroid : public FileAccess { static jmethodID _file_close; static jmethodID _file_exists; static jmethodID _file_last_modified; + static jmethodID _file_resize; int id; String absolute_path; @@ -76,6 +77,7 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF + virtual Error resize(int64_t p_length) override; virtual uint8_t get_8() const override; ///< get a byte virtual uint16_t get_16() const override; virtual uint32_t get_32() const override; @@ -95,6 +97,7 @@ public: virtual bool file_exists(const String &p_path) override; ///< return true if a file exists static void setup(jobject p_file_access_handler); + static void terminate(); virtual uint64_t _get_modified_time(const String &p_file) override; virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; } diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 7797f4bc9d..b83ef1471c 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -205,36 +205,66 @@ android { } task copyAndRenameDebugApk(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/apk/debug/android_debug.apk" into getExportPath() rename "android_debug.apk", getExportFilename() } task copyAndRenameDevApk(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/apk/dev/android_dev.apk" into getExportPath() rename "android_dev.apk", getExportFilename() } task copyAndRenameReleaseApk(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/apk/release/android_release.apk" into getExportPath() rename "android_release.apk", getExportFilename() } task copyAndRenameDebugAab(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/bundle/debug/build-debug.aab" into getExportPath() rename "build-debug.aab", getExportFilename() } task copyAndRenameDevAab(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/bundle/dev/build-dev.aab" into getExportPath() rename "build-dev.aab", getExportFilename() } task copyAndRenameReleaseAab(type: Copy) { + // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files + // and directories. Otherwise this check may cause permissions access failures on Windows + // machines. + doNotTrackState("No need for up-to-date checks for the copy-and-rename operation") + from "$buildDir/outputs/bundle/release/build-release.aab" into getExportPath() rename "build-release.aab", getExportFilename() diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt index 0f447f0b05..11cf7b3566 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt @@ -36,7 +36,9 @@ import android.util.Log import org.godotengine.godot.io.StorageScope import java.io.IOException import java.nio.ByteBuffer +import java.nio.channels.ClosedChannelException import java.nio.channels.FileChannel +import java.nio.channels.NonWritableChannelException import kotlin.math.max /** @@ -135,6 +137,21 @@ internal abstract class DataAccess(private val filePath: String) { seek(positionFromBeginning) } + fun resize(length: Long): Int { + return try { + fileChannel.truncate(length) + FileErrors.OK.nativeValue + } catch (e: NonWritableChannelException) { + FileErrors.FILE_CANT_OPEN.nativeValue + } catch (e: ClosedChannelException) { + FileErrors.FILE_CANT_OPEN.nativeValue + } catch (e: IllegalArgumentException) { + FileErrors.INVALID_PARAMETER.nativeValue + } catch (e: IOException) { + FileErrors.FAILED.nativeValue + } + } + fun position(): Long { return try { fileChannel.position() diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt index 50741c1aab..1d773467e8 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt @@ -45,7 +45,6 @@ class FileAccessHandler(val context: Context) { companion object { private val TAG = FileAccessHandler::class.java.simpleName - private const val FILE_NOT_FOUND_ERROR_ID = -1 internal const val INVALID_FILE_ID = 0 private const val STARTING_FILE_ID = 1 @@ -118,7 +117,7 @@ class FileAccessHandler(val context: Context) { lastFileId } ?: INVALID_FILE_ID } catch (e: FileNotFoundException) { - FILE_NOT_FOUND_ERROR_ID + FileErrors.FILE_NOT_FOUND.nativeValue } catch (e: Exception) { Log.w(TAG, "Error while opening $path", e) INVALID_FILE_ID @@ -190,6 +189,14 @@ class FileAccessHandler(val context: Context) { } } + fun fileResize(fileId: Int, length: Long): Int { + if (!hasFileId(fileId)) { + return FileErrors.FAILED.nativeValue + } + + return files[fileId].resize(length) + } + fun fileGetPosition(fileId: Int): Long { if (!hasFileId(fileId)) { return 0L diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt new file mode 100644 index 0000000000..2df0195de7 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt @@ -0,0 +1,53 @@ +/**************************************************************************/ +/* FileErrors.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +package org.godotengine.godot.io.file + +/** + * Set of errors that may occur when performing data access. + */ +internal enum class FileErrors(val nativeValue: Int) { + OK(0), + FAILED(-1), + FILE_NOT_FOUND(-2), + FILE_CANT_OPEN(-3), + INVALID_PARAMETER(-4); + + companion object { + fun fromNativeError(error: Int): FileErrors? { + for (fileError in entries) { + if (fileError.nativeValue == error) { + return fileError + } + } + return null + } + } +} diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index d6455cbf1c..a309a6ab74 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -1157,50 +1157,54 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL(env); - jclass activity = env->FindClass("android/app/Activity"); - jmethodID getClassLoader = env->GetMethodID(activity, "getClassLoader", "()Ljava/lang/ClassLoader;"); - classLoader = env->CallObjectMethod(p_activity, getClassLoader); - classLoader = (jclass)env->NewGlobalRef(classLoader); - jclass classLoaderClass = env->FindClass("java/lang/ClassLoader"); - findClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); - jclass bclass = env->FindClass("java/lang/Class"); getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;"); getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;"); Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/reflect/Method"); getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;"); getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;"); getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); getModifiers = env->GetMethodID(bclass, "getModifiers", "()I"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/reflect/Field"); Field_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); Field_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I"); Field_get = env->GetMethodID(bclass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Boolean"); Boolean_booleanValue = env->GetMethodID(bclass, "booleanValue", "()Z"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Byte"); Byte_byteValue = env->GetMethodID(bclass, "byteValue", "()B"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Character"); Character_characterValue = env->GetMethodID(bclass, "charValue", "()C"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Short"); Short_shortValue = env->GetMethodID(bclass, "shortValue", "()S"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Integer"); Integer_integerValue = env->GetMethodID(bclass, "intValue", "()I"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Long"); Long_longValue = env->GetMethodID(bclass, "longValue", "()J"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Float"); Float_floatValue = env->GetMethodID(bclass, "floatValue", "()F"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Double"); Double_doubleValue = env->GetMethodID(bclass, "doubleValue", "()D"); + env->DeleteLocalRef(bclass); } diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 10716a5c79..49913b9c30 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -70,7 +70,11 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc } GodotIOJavaWrapper::~GodotIOJavaWrapper() { - // nothing to do here for now + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(godot_io_instance); } jobject GodotIOJavaWrapper::get_instance() { @@ -82,7 +86,9 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL_V(env, ERR_UNAVAILABLE); jstring jStr = env->NewStringUTF(p_uri.utf8().get_data()); - return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK; + Error result = env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK; + env->DeleteLocalRef(jStr); + return result; } else { return ERR_UNAVAILABLE; } @@ -220,6 +226,7 @@ void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_type, int p_max ERR_FAIL_NULL(env); jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_type, p_max_input_length, p_cursor_start, p_cursor_end); + env->DeleteLocalRef(jStr); } } diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 2215f706c5..6cab7e74fd 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -95,6 +95,13 @@ static void _terminate(JNIEnv *env, bool p_restart = false) { if (godot_io_java) { delete godot_io_java; } + + TTS_Android::terminate(); + FileAccessAndroid::terminate(); + DirAccessJAndroid::terminate(); + FileAccessFilesystemJAndroid::terminate(); + NetSocketAndroid::terminate(); + if (godot_java) { if (!restart_on_cleanup) { if (p_restart) { @@ -125,10 +132,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv init_thread_jandroid(jvm, env); - jobject amgr = env->NewGlobalRef(p_asset_manager); - - FileAccessAndroid::asset_manager = AAssetManager_fromJava(env, amgr); - + FileAccessAndroid::setup(p_asset_manager); DirAccessJAndroid::setup(p_directory_access_handler); FileAccessFilesystemJAndroid::setup(p_file_access_handler); NetSocketAndroid::setup(p_net_utils); diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp index a95f762e01..04424c1179 100644 --- a/platform/android/java_godot_view_wrapper.cpp +++ b/platform/android/java_godot_view_wrapper.cpp @@ -95,6 +95,7 @@ void GodotJavaViewWrapper::configure_pointer_icon(int pointer_type, const String jstring jImagePath = env->NewStringUTF(image_path.utf8().get_data()); env->CallVoidMethod(_godot_view, _configure_pointer_icon, pointer_type, jImagePath, p_hotspot.x, p_hotspot.y); + env->DeleteLocalRef(jImagePath); } } diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 3c950bb1b1..61be6fc5db 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -172,6 +172,8 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data()); jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data()); env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle); + env->DeleteLocalRef(jStrMessage); + env->DeleteLocalRef(jStrTitle); } } @@ -231,6 +233,7 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) { ERR_FAIL_NULL(env); jstring jStr = env->NewStringUTF(p_text.utf8().get_data()); env->CallVoidMethod(godot_instance, _set_clipboard, jStr); + env->DeleteLocalRef(jStr); } } @@ -253,7 +256,9 @@ bool GodotJavaWrapper::request_permission(const String &p_name) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL_V(env, false); jstring jStrName = env->NewStringUTF(p_name.utf8().get_data()); - return env->CallBooleanMethod(godot_instance, _request_permission, jStrName); + bool result = env->CallBooleanMethod(godot_instance, _request_permission, jStrName); + env->DeleteLocalRef(jStrName); + return result; } else { return false; } @@ -340,7 +345,9 @@ int GodotJavaWrapper::create_new_godot_instance(const List<String> &args) { ERR_FAIL_NULL_V(env, 0); jobjectArray jargs = env->NewObjectArray(args.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")); for (int i = 0; i < args.size(); i++) { - env->SetObjectArrayElement(jargs, i, env->NewStringUTF(args[i].utf8().get_data())); + jstring j_arg = env->NewStringUTF(args[i].utf8().get_data()); + env->SetObjectArrayElement(jargs, i, j_arg); + env->DeleteLocalRef(j_arg); } return env->CallIntMethod(godot_instance, _create_new_godot_instance, jargs); } else { @@ -355,6 +362,8 @@ void GodotJavaWrapper::begin_benchmark_measure(const String &p_context, const St jstring j_context = env->NewStringUTF(p_context.utf8().get_data()); jstring j_label = env->NewStringUTF(p_label.utf8().get_data()); env->CallVoidMethod(godot_instance, _begin_benchmark_measure, j_context, j_label); + env->DeleteLocalRef(j_context); + env->DeleteLocalRef(j_label); } } @@ -365,6 +374,8 @@ void GodotJavaWrapper::end_benchmark_measure(const String &p_context, const Stri jstring j_context = env->NewStringUTF(p_context.utf8().get_data()); jstring j_label = env->NewStringUTF(p_label.utf8().get_data()); env->CallVoidMethod(godot_instance, _end_benchmark_measure, j_context, j_label); + env->DeleteLocalRef(j_context); + env->DeleteLocalRef(j_label); } } @@ -374,6 +385,7 @@ void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) { ERR_FAIL_NULL(env); jstring j_benchmark_file = env->NewStringUTF(benchmark_file.utf8().get_data()); env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file); + env->DeleteLocalRef(j_benchmark_file); } } @@ -383,7 +395,9 @@ bool GodotJavaWrapper::has_feature(const String &p_feature) const { ERR_FAIL_NULL_V(env, false); jstring j_feature = env->NewStringUTF(p_feature.utf8().get_data()); - return env->CallBooleanMethod(godot_instance, _has_feature, j_feature); + bool result = env->CallBooleanMethod(godot_instance, _has_feature, j_feature); + env->DeleteLocalRef(j_feature); + return result; } else { return false; } diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp index a2befdc9be..8f0ee51fac 100644 --- a/platform/android/net_socket_android.cpp +++ b/platform/android/net_socket_android.cpp @@ -49,6 +49,14 @@ void NetSocketAndroid::setup(jobject p_net_utils) { _multicast_lock_release = env->GetMethodID(cls, "multicastLockRelease", "()V"); } +void NetSocketAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(net_utils); +} + void NetSocketAndroid::multicast_lock_acquire() { if (_multicast_lock_acquire) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/net_socket_android.h b/platform/android/net_socket_android.h index e5f46d3236..26cb2d4e3d 100644 --- a/platform/android/net_socket_android.h +++ b/platform/android/net_socket_android.h @@ -63,6 +63,7 @@ protected: public: static void make_default(); static void setup(jobject p_net_utils); + static void terminate(); virtual void close(); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index bf6b7a7372..463a307854 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -162,7 +162,39 @@ Vector<String> OS_Android::get_granted_permissions() const { return godot_java->get_granted_permissions(); } -Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) { +bool OS_Android::copy_dynamic_library(const String &p_library_path, const String &p_target_dir, String *r_copy_path) { + if (!FileAccess::exists(p_library_path)) { + return false; + } + + Ref<DirAccess> da_ref = DirAccess::create_for_path(p_library_path); + if (!da_ref.is_valid()) { + return false; + } + + String copy_path = p_target_dir.path_join(p_library_path.get_file()); + bool copy_exists = FileAccess::exists(copy_path); + if (copy_exists) { + print_verbose("Deleting existing library copy " + copy_path); + if (da_ref->remove(copy_path) != OK) { + print_verbose("Unable to delete " + copy_path); + } + } + + print_verbose("Copying " + p_library_path + " to " + p_target_dir); + Error create_dir_result = da_ref->make_dir_recursive(p_target_dir); + if (create_dir_result == OK || create_dir_result == ERR_ALREADY_EXISTS) { + copy_exists = da_ref->copy(p_library_path, copy_path) == OK; + } + + if (copy_exists && r_copy_path != nullptr) { + *r_copy_path = copy_path; + } + + return copy_exists; +} + +Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) { String path = p_path; bool so_file_exists = true; if (!FileAccess::exists(path)) { @@ -172,24 +204,32 @@ Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_ha p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); if (!p_library_handle && so_file_exists) { - // The library may be on the sdcard and thus inaccessible. Try to copy it to the internal - // directory. - uint64_t so_modified_time = FileAccess::get_modified_time(p_path); - String dynamic_library_path = get_dynamic_libraries_path().path_join(String::num_uint64(so_modified_time)); - String internal_path = dynamic_library_path.path_join(p_path.get_file()); - - bool internal_so_file_exists = FileAccess::exists(internal_path); - if (!internal_so_file_exists) { - Ref<DirAccess> da_ref = DirAccess::create_for_path(p_path); - if (da_ref.is_valid()) { - Error create_dir_result = da_ref->make_dir_recursive(dynamic_library_path); - if (create_dir_result == OK || create_dir_result == ERR_ALREADY_EXISTS) { - internal_so_file_exists = da_ref->copy(path, internal_path) == OK; + // The library (and its dependencies) may be on the sdcard and thus inaccessible. + // Try to copy to the internal directory for access. + const String dynamic_library_path = get_dynamic_libraries_path(); + + if (p_data != nullptr && p_data->library_dependencies != nullptr && !p_data->library_dependencies->is_empty()) { + // Copy the library dependencies + print_verbose("Copying library dependencies.."); + for (const String &library_dependency_path : *p_data->library_dependencies) { + String internal_library_dependency_path; + if (!copy_dynamic_library(library_dependency_path, dynamic_library_path.path_join(library_dependency_path.get_base_dir()), &internal_library_dependency_path)) { + ERR_PRINT(vformat("Unable to copy library dependency %s", library_dependency_path)); + } else { + void *lib_dependency_handle = dlopen(internal_library_dependency_path.utf8().get_data(), RTLD_NOW); + if (!lib_dependency_handle) { + ERR_PRINT(vformat("Can't open dynamic library dependency: %s. Error: %s.", internal_library_dependency_path, dlerror())); + } } } } + String internal_path; + print_verbose("Copying library " + p_path); + const bool internal_so_file_exists = copy_dynamic_library(p_path, dynamic_library_path.path_join(p_path.get_base_dir()), &internal_path); + if (internal_so_file_exists) { + print_verbose("Opening library " + internal_path); p_library_handle = dlopen(internal_path.utf8().get_data(), RTLD_NOW); if (p_library_handle) { path = internal_path; @@ -199,8 +239,8 @@ Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_ha ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror())); - if (r_resolved_path != nullptr) { - *r_resolved_path = path; + if (p_data != nullptr && p_data->r_resolved_path != nullptr) { + *p_data->r_resolved_path = path; } return OK; @@ -736,6 +776,10 @@ void OS_Android::benchmark_dump() { } bool OS_Android::_check_internal_feature_support(const String &p_feature) { + if (p_feature == "macos" || p_feature == "web_ios" || p_feature == "web_macos" || p_feature == "windows") { + return false; + } + if (p_feature == "system_fonts") { return true; } diff --git a/platform/android/os_android.h b/platform/android/os_android.h index e01d759494..7bdbeef77a 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -113,7 +113,7 @@ public: virtual void alert(const String &p_alert, const String &p_title) override; - virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override; + virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override; virtual String get_name() const override; virtual String get_distribution_name() const override; @@ -178,6 +178,8 @@ public: private: // Location where we relocate external dynamic libraries to make them accessible. String get_dynamic_libraries_path() const; + // Copy a dynamic library to the given location to make it accessible for loading. + bool copy_dynamic_library(const String &p_library_path, const String &p_target_dir, String *r_copy_path = nullptr); }; #endif // OS_ANDROID_H diff --git a/platform/android/tts_android.cpp b/platform/android/tts_android.cpp index 93517d8045..be85e47972 100644 --- a/platform/android/tts_android.cpp +++ b/platform/android/tts_android.cpp @@ -77,6 +77,14 @@ void TTS_Android::setup(jobject p_tts) { } } +void TTS_Android::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(tts); +} + void TTS_Android::_java_utterance_callback(int p_event, int p_id, int p_pos) { ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); if (ids.has(p_id)) { @@ -170,6 +178,8 @@ void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volum jstring jStrT = env->NewStringUTF(p_text.utf8().get_data()); jstring jStrV = env->NewStringUTF(p_voice.utf8().get_data()); env->CallVoidMethod(tts, _speak, jStrT, jStrV, CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, p_interrupt); + env->DeleteLocalRef(jStrT); + env->DeleteLocalRef(jStrV); } } diff --git a/platform/android/tts_android.h b/platform/android/tts_android.h index 39efef6ed1..4cc7c12846 100644 --- a/platform/android/tts_android.h +++ b/platform/android/tts_android.h @@ -57,6 +57,7 @@ class TTS_Android { public: static void setup(jobject p_tts); + static void terminate(); static void _java_utterance_callback(int p_event, int p_id, int p_pos); static bool is_speaking(); diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h index 3f9211c572..c6015a058c 100644 --- a/platform/ios/display_server_ios.h +++ b/platform/ios/display_server_ios.h @@ -129,10 +129,10 @@ public: // MARK: Motion - void update_gravity(float p_x, float p_y, float p_z); - void update_accelerometer(float p_x, float p_y, float p_z); - void update_magnetometer(float p_x, float p_y, float p_z); - void update_gyroscope(float p_x, float p_y, float p_z); + void update_gravity(const Vector3 &p_gravity); + void update_accelerometer(const Vector3 &p_accelerometer); + void update_magnetometer(const Vector3 &p_magnetometer); + void update_gyroscope(const Vector3 &p_gyroscope); // MARK: - diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index f84fb01ad0..cd6f855d77 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -289,26 +289,20 @@ void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_ph // MARK: Motion -void DisplayServerIOS::update_gravity(float p_x, float p_y, float p_z) { - Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z)); +void DisplayServerIOS::update_gravity(const Vector3 &p_gravity) { + Input::get_singleton()->set_gravity(p_gravity); } -void DisplayServerIOS::update_accelerometer(float p_x, float p_y, float p_z) { - // Found out the Z should not be negated! Pass as is! - Vector3 v_accelerometer = Vector3( - p_x / kDisplayServerIOSAcceleration, - p_y / kDisplayServerIOSAcceleration, - p_z / kDisplayServerIOSAcceleration); - - Input::get_singleton()->set_accelerometer(v_accelerometer); +void DisplayServerIOS::update_accelerometer(const Vector3 &p_accelerometer) { + Input::get_singleton()->set_accelerometer(p_accelerometer / kDisplayServerIOSAcceleration); } -void DisplayServerIOS::update_magnetometer(float p_x, float p_y, float p_z) { - Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z)); +void DisplayServerIOS::update_magnetometer(const Vector3 &p_magnetometer) { + Input::get_singleton()->set_magnetometer(p_magnetometer); } -void DisplayServerIOS::update_gyroscope(float p_x, float p_y, float p_z) { - Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z)); +void DisplayServerIOS::update_gyroscope(const Vector3 &p_gyroscope) { + Input::get_singleton()->set_gyroscope(p_gyroscope); } // MARK: - diff --git a/platform/ios/godot_view.mm b/platform/ios/godot_view.mm index 4b87863fc5..1dddc9306e 100644 --- a/platform/ios/godot_view.mm +++ b/platform/ios/godot_view.mm @@ -451,28 +451,28 @@ static const float earth_gravity = 9.80665; switch (interfaceOrientation) { case UIInterfaceOrientationLandscapeLeft: { - DisplayServerIOS::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z); - DisplayServerIOS::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z); - DisplayServerIOS::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z); - DisplayServerIOS::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z); + DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), -Math_PI * 0.5)); + DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), -Math_PI * 0.5)); + DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), -Math_PI * 0.5)); + DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), -Math_PI * 0.5)); } break; case UIInterfaceOrientationLandscapeRight: { - DisplayServerIOS::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z); - DisplayServerIOS::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z); - DisplayServerIOS::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z); - DisplayServerIOS::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z); + DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math_PI * 0.5)); + DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math_PI * 0.5)); + DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math_PI * 0.5)); + DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math_PI * 0.5)); } break; case UIInterfaceOrientationPortraitUpsideDown: { - DisplayServerIOS::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z); - DisplayServerIOS::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z); - DisplayServerIOS::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z); - DisplayServerIOS::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z); + DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math_PI)); + DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math_PI)); + DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math_PI)); + DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math_PI)); } break; default: { // assume portrait - DisplayServerIOS::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z); - DisplayServerIOS::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z); - DisplayServerIOS::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z); - DisplayServerIOS::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z); + DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z)); + DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z)); + DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z)); + DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z)); } break; } } diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h index 6950720b38..c4782a4768 100644 --- a/platform/ios/os_ios.h +++ b/platform/ios/os_ios.h @@ -103,7 +103,7 @@ public: virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; - virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override; + virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override; virtual Error close_dynamic_library(void *p_library_handle) override; virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override; diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm index bcf21bc802..52d496d641 100644 --- a/platform/ios/os_ios.mm +++ b/platform/ios/os_ios.mm @@ -217,13 +217,13 @@ _FORCE_INLINE_ String OS_IOS::get_framework_executable(const String &p_path) { return p_path; } -Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) { +Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) { if (p_path.length() == 0) { // Static xcframework. p_library_handle = RTLD_SELF; - if (r_resolved_path != nullptr) { - *r_resolved_path = p_path; + if (p_data != nullptr && p_data->r_resolved_path != nullptr) { + *p_data->r_resolved_path = p_path; } return OK; @@ -256,8 +256,8 @@ Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror())); - if (r_resolved_path != nullptr) { - *r_resolved_path = path; + if (p_data != nullptr && p_data->r_resolved_path != nullptr) { + *p_data->r_resolved_path = path; } return OK; diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index 439e7d2fff..912a682a6b 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -85,7 +85,7 @@ public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override; + virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override; virtual MainLoop *get_main_loop() const override; diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 261777a4e8..9f0bea5951 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -217,7 +217,7 @@ _FORCE_INLINE_ String OS_MacOS::get_framework_executable(const String &p_path) { return p_path; } -Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) { +Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) { String path = get_framework_executable(p_path); if (!FileAccess::exists(path)) { @@ -235,8 +235,8 @@ Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_hand p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror())); - if (r_resolved_path != nullptr) { - *r_resolved_path = path; + if (p_data != nullptr && p_data->r_resolved_path != nullptr) { + *p_data->r_resolved_path = path; } return OK; diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp index 4158295520..ab4e7f8470 100644 --- a/platform/web/os_web.cpp +++ b/platform/web/os_web.cpp @@ -247,13 +247,13 @@ bool OS_Web::is_userfs_persistent() const { return idb_available; } -Error OS_Web::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) { +Error OS_Web::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) { String path = p_path.get_file(); p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror())); - if (r_resolved_path != nullptr) { - *r_resolved_path = path; + if (p_data != nullptr && p_data->r_resolved_path != nullptr) { + *p_data->r_resolved_path = path; } return OK; diff --git a/platform/web/os_web.h b/platform/web/os_web.h index eeeafdac34..a825938e96 100644 --- a/platform/web/os_web.h +++ b/platform/web/os_web.h @@ -109,7 +109,7 @@ public: void alert(const String &p_alert, const String &p_title = "ALERT!") override; - Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override; + Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override; void resume_audio(); diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index f2a9989606..abed93d414 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -359,7 +359,7 @@ void debug_dynamic_library_check_dependencies(const String &p_root_path, const S } #endif -Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) { +Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) { String path = p_path.replace("/", "\\"); if (!FileAccess::exists(path)) { @@ -371,7 +371,7 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha // Here we want a copy to be loaded. // This is so the original file isn't locked and can be updated by a compiler. - if (p_generate_temp_files) { + if (p_data != nullptr && p_data->generate_temp_files) { // Copy the file to the same directory as the original with a prefix in the name. // This is so relative path to dependencies are satisfied. String copy_path = path.get_base_dir().path_join("~" + path.get_file()); @@ -407,13 +407,13 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha bool has_dll_directory_api = ((add_dll_directory != nullptr) && (remove_dll_directory != nullptr)); DLL_DIRECTORY_COOKIE cookie = nullptr; - if (p_also_set_library_path && has_dll_directory_api) { + if (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) { cookie = add_dll_directory((LPCWSTR)(path.get_base_dir().utf16().get_data())); } - p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(path.utf16().get_data()), nullptr, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); + p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(path.utf16().get_data()), nullptr, (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); if (!p_library_handle) { - if (p_generate_temp_files) { + if (p_data != nullptr && p_data->generate_temp_files) { DirAccess::remove_absolute(path); } @@ -446,11 +446,11 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha remove_dll_directory(cookie); } - if (r_resolved_path != nullptr) { - *r_resolved_path = path; + if (p_data != nullptr && p_data->r_resolved_path != nullptr) { + *p_data->r_resolved_path = path; } - if (p_generate_temp_files) { + if (p_data != nullptr && p_data->generate_temp_files) { temp_libraries[p_library_handle] = path; } diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 288154745f..b6a21ed42d 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -161,7 +161,7 @@ public: virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override; - virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override; + virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override; virtual Error close_dynamic_library(void *p_library_handle) override; virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override; diff --git a/platform/windows/rendering_context_driver_vulkan_windows.h b/platform/windows/rendering_context_driver_vulkan_windows.h index 34546c9ea6..1bb70cb0ff 100644 --- a/platform/windows/rendering_context_driver_vulkan_windows.h +++ b/platform/windows/rendering_context_driver_vulkan_windows.h @@ -52,7 +52,7 @@ public: }; RenderingContextDriverVulkanWindows(); - ~RenderingContextDriverVulkanWindows() override final; + ~RenderingContextDriverVulkanWindows() override; }; #endif // VULKAN_ENABLED diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index cbd3c244d9..4fc0fe0268 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -196,6 +196,7 @@ Ref<AudioStream> AudioStreamPlayer2D::get_stream() const { } void AudioStreamPlayer2D::set_volume_db(float p_volume) { + ERR_FAIL_COND_MSG(Math::is_nan(p_volume), "Volume can't be set to NaN."); internal->volume_db = p_volume; } diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp index e898dc7fab..f8a8aa487c 100644 --- a/scene/2d/line_builder.cpp +++ b/scene/2d/line_builder.cpp @@ -353,7 +353,20 @@ void LineBuilder::build() { } else if (current_joint_mode == Line2D::LINE_JOINT_ROUND && !(wrap_around && i == segments_count)) { Vector2 vbegin = cbegin - pos1; Vector2 vend = cend - pos1; - strip_add_arc(pos1, vbegin.angle_to(vend), orientation); + // We want to use vbegin.angle_to(vend) below, which evaluates to + // Math::atan2(vbegin.cross(vend), vbegin.dot(vend)) but we need to + // calculate this ourselves as we need to check if the cross product + // in that calculation ends up being -0.f and flip it if so, effectively + // flipping the resulting angle_delta to not return -PI but +PI instead + float cross_product = vbegin.cross(vend); + float dot_product = vbegin.dot(vend); + // Note that we're comparing against -0.f for clarity but 0.f would + // match as well, therefore we need the explicit signbit check too. + if (cross_product == -0.f && signbit(cross_product)) { + cross_product = 0.f; + } + float angle_delta = Math::atan2(cross_product, dot_product); + strip_add_arc(pos1, angle_delta, orientation); } if (!is_intersecting) { diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 69e0414855..fe21c7f21b 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -635,36 +635,47 @@ Bone2D *Skeleton2D::get_bone(int p_idx) { } void Skeleton2D::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - if (bone_setup_dirty) { - _update_bone_setup(); - } - if (transform_dirty) { - _update_transform(); - } - request_ready(); - } + switch (p_what) { + case NOTIFICATION_READY: { + if (bone_setup_dirty) { + _update_bone_setup(); + } + if (transform_dirty) { + _update_transform(); + } + request_ready(); + } break; - if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { - RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform()); - } else if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - if (modification_stack.is_valid()) { - execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process); - } - } else if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { - if (modification_stack.is_valid()) { - execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process); - } - } -#ifdef TOOLS_ENABLED - else if (p_what == NOTIFICATION_DRAW) { - if (Engine::get_singleton()->is_editor_hint()) { + case NOTIFICATION_TRANSFORM_CHANGED: { + RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform()); + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { if (modification_stack.is_valid()) { - modification_stack->draw_editor_gizmos(); + execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process); } - } - } + } break; + + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (modification_stack.is_valid()) { + execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process); + } + } break; + + case NOTIFICATION_POST_ENTER_TREE: { + set_modification_stack(modification_stack); + } break; + +#ifdef TOOLS_ENABLED + case NOTIFICATION_DRAW: { + if (Engine::get_singleton()->is_editor_hint()) { + if (modification_stack.is_valid()) { + modification_stack->draw_editor_gizmos(); + } + } + } break; #endif // TOOLS_ENABLED + } } RID Skeleton2D::get_skeleton() const { @@ -692,7 +703,7 @@ void Skeleton2D::set_modification_stack(Ref<SkeletonModificationStack2D> p_stack set_physics_process_internal(false); } modification_stack = p_stack; - if (modification_stack.is_valid()) { + if (modification_stack.is_valid() && is_inside_tree()) { modification_stack->set_skeleton(this); modification_stack->setup(); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 84d3a5f7fa..165d4d5a67 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -161,6 +161,10 @@ Vector<int> TileMap::_get_tile_map_data_using_compatibility_format(int p_layer) return tile_data; } +void TileMap::_set_layer_tile_data(int p_layer, const PackedInt32Array &p_data) { + _set_tile_map_data_using_compatibility_format(p_layer, format, p_data); +} + void TileMap::_notification(int p_what) { switch (p_what) { case TileMap::NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { @@ -740,41 +744,23 @@ Rect2 TileMap::_edit_get_rect() const { #endif bool TileMap::_set(const StringName &p_name, const Variant &p_value) { + int index; + const String sname = p_name; + Vector<String> components = String(p_name).split("/", true, 2); - if (p_name == "format") { + if (sname == "format") { if (p_value.get_type() == Variant::INT) { format = (TileMapDataFormat)(p_value.operator int64_t()); // Set format used for loading. return true; } } #ifndef DISABLE_DEPRECATED - else if (p_name == "tile_data") { // Kept for compatibility reasons. - if (p_value.is_array()) { - if (layers.size() == 0) { - TileMapLayer *new_layer = memnew(TileMapLayer); - add_child(new_layer, false, INTERNAL_MODE_FRONT); - new_layer->set_as_tile_map_internal_node(0); - new_layer->set_name("Layer0"); - new_layer->set_tile_set(tile_set); - new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); - layers.push_back(new_layer); - } - _set_tile_map_data_using_compatibility_format(0, format, p_value); - _emit_changed(); - return true; - } - return false; - } else if (p_name == "cell_quadrant_size") { + else if (sname == "cell_quadrant_size") { set_rendering_quadrant_size(p_value); return true; } #endif // DISABLE_DEPRECATED - else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index < 0) { - return false; - } - + else if (property_helper.is_property_valid(sname, &index)) { if (index >= (int)layers.size()) { while (index >= (int)layers.size()) { TileMapLayer *new_layer = memnew(TileMapLayer); @@ -791,172 +777,38 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { update_configuration_warnings(); } - if (components[1] == "name") { - set_layer_name(index, p_value); - return true; - } else if (components[1] == "enabled") { - set_layer_enabled(index, p_value); - return true; - } else if (components[1] == "modulate") { - set_layer_modulate(index, p_value); - return true; - } else if (components[1] == "y_sort_enabled") { - set_layer_y_sort_enabled(index, p_value); - return true; - } else if (components[1] == "y_sort_origin") { - set_layer_y_sort_origin(index, p_value); - return true; - } else if (components[1] == "z_index") { - set_layer_z_index(index, p_value); - return true; - } else if (components[1] == "navigation_enabled") { - set_layer_navigation_enabled(index, p_value); - return true; - } else if (components[1] == "tile_data") { - _set_tile_map_data_using_compatibility_format(index, format, p_value); - _emit_changed(); + if (property_helper.property_set_value(sname, p_value)) { + if (components[1] == "tile_data") { + _emit_changed(); + } return true; - } else { - return false; } } return false; } bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { + const String sname = p_name; + Vector<String> components = String(p_name).split("/", true, 2); if (p_name == "format") { r_ret = TileMapDataFormat::TILE_MAP_DATA_FORMAT_MAX - 1; // When saving, always save highest format. return true; } #ifndef DISABLE_DEPRECATED - else if (p_name == "cell_quadrant_size") { // Kept for compatibility reasons. + else if (sname == "cell_quadrant_size") { // Kept for compatibility reasons. r_ret = get_rendering_quadrant_size(); return true; } #endif - else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index < 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - r_ret = get_layer_name(index); - return true; - } else if (components[1] == "enabled") { - r_ret = is_layer_enabled(index); - return true; - } else if (components[1] == "modulate") { - r_ret = get_layer_modulate(index); - return true; - } else if (components[1] == "y_sort_enabled") { - r_ret = is_layer_y_sort_enabled(index); - return true; - } else if (components[1] == "y_sort_origin") { - r_ret = get_layer_y_sort_origin(index); - return true; - } else if (components[1] == "z_index") { - r_ret = get_layer_z_index(index); - return true; - } else if (components[1] == "navigation_enabled") { - r_ret = is_layer_navigation_enabled(index); - return true; - } else if (components[1] == "tile_data") { - r_ret = _get_tile_map_data_using_compatibility_format(index); - return true; - } else { - return false; - } + else { + return property_helper.property_get_value(sname, r_ret); } - return false; } void TileMap::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::NIL, "Layers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - -#define MAKE_LAYER_PROPERTY(m_type, m_name, m_hint) \ - { \ - const String property_name = vformat("layer_%d/" m_name, i); \ - p_list->push_back(PropertyInfo(m_type, property_name, PROPERTY_HINT_NONE, m_hint, (get(property_name) == property_get_revert(property_name)) ? PROPERTY_USAGE_EDITOR : PROPERTY_USAGE_DEFAULT)); \ - } - - for (uint32_t i = 0; i < layers.size(); i++) { - MAKE_LAYER_PROPERTY(Variant::STRING, "name", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "enabled", ""); - MAKE_LAYER_PROPERTY(Variant::COLOR, "modulate", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "y_sort_enabled", ""); - MAKE_LAYER_PROPERTY(Variant::INT, "y_sort_origin", "suffix:px"); - MAKE_LAYER_PROPERTY(Variant::INT, "z_index", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "navigation_enabled", ""); - p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - } - -#undef MAKE_LAYER_PROPERTY -} - -bool TileMap::_property_can_revert(const StringName &p_name) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_")) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index <= 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - return layers[index]->get_name() != default_layer->get_name(); - } else if (components[1] == "enabled") { - return layers[index]->is_enabled() != default_layer->is_enabled(); - } else if (components[1] == "modulate") { - return layers[index]->get_modulate() != default_layer->get_modulate(); - } else if (components[1] == "y_sort_enabled") { - return layers[index]->is_y_sort_enabled() != default_layer->is_y_sort_enabled(); - } else if (components[1] == "y_sort_origin") { - return layers[index]->get_y_sort_origin() != default_layer->get_y_sort_origin(); - } else if (components[1] == "z_index") { - return layers[index]->get_z_index() != default_layer->get_z_index(); - } else if (components[1] == "navigation_enabled") { - return layers[index]->is_navigation_enabled() != default_layer->is_navigation_enabled(); - } - } - - return false; -} - -bool TileMap::_property_get_revert(const StringName &p_name, Variant &r_property) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_")) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index <= 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - r_property = default_layer->get_name(); - return true; - } else if (components[1] == "enabled") { - r_property = default_layer->is_enabled(); - return true; - } else if (components[1] == "modulate") { - r_property = default_layer->get_modulate(); - return true; - } else if (components[1] == "y_sort_enabled") { - r_property = default_layer->is_y_sort_enabled(); - return true; - } else if (components[1] == "y_sort_origin") { - r_property = default_layer->get_y_sort_origin(); - return true; - } else if (components[1] == "z_index") { - r_property = default_layer->get_z_index(); - return true; - } else if (components[1] == "navigation_enabled") { - r_property = default_layer->is_navigation_enabled(); - return true; - } - } - - return false; + property_helper.get_property_list(p_list, layers.size()); } Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { @@ -1214,11 +1066,26 @@ TileMap::TileMap() { new_layer->set_tile_set(tile_set); new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); layers.push_back(new_layer); - default_layer = memnew(TileMapLayer); -} -TileMap::~TileMap() { - memdelete(default_layer); + if (!base_property_helper.is_initialized()) { + // Initialize static PropertyListHelper if it wasn't yet. This has to be done here, + // because creating TileMapLayer in a static context is not always safe. + TileMapLayer *defaults = memnew(TileMapLayer); + + base_property_helper.set_prefix("layer_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults->get_name(), &TileMap::set_layer_name, &TileMap::get_layer_name); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "enabled"), defaults->is_enabled(), &TileMap::set_layer_enabled, &TileMap::is_layer_enabled); + base_property_helper.register_property(PropertyInfo(Variant::COLOR, "modulate"), defaults->get_modulate(), &TileMap::set_layer_modulate, &TileMap::get_layer_modulate); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "y_sort_enabled"), defaults->is_y_sort_enabled(), &TileMap::set_layer_y_sort_enabled, &TileMap::is_layer_y_sort_enabled); + base_property_helper.register_property(PropertyInfo(Variant::INT, "y_sort_origin", PROPERTY_HINT_NONE, "suffix:px"), defaults->get_y_sort_origin(), &TileMap::set_layer_y_sort_origin, &TileMap::get_layer_y_sort_origin); + base_property_helper.register_property(PropertyInfo(Variant::INT, "z_index"), defaults->get_z_index(), &TileMap::set_layer_z_index, &TileMap::get_layer_z_index); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "navigation_enabled"), defaults->is_navigation_enabled(), &TileMap::set_layer_navigation_enabled, &TileMap::is_layer_navigation_enabled); + base_property_helper.register_property(PropertyInfo(Variant::PACKED_INT32_ARRAY, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), Vector<int>(), &TileMap::_set_layer_tile_data, &TileMap::_get_tile_map_data_using_compatibility_format); + + memdelete(defaults); + } + + property_helper.setup_for_instance(base_property_helper, this); } #undef TILEMAP_CALL_FOR_LAYER diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 41068ea978..45604bfb8a 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -32,6 +32,7 @@ #define TILE_MAP_H #include "scene/2d/tile_map_layer.h" +#include "scene/property_list_helper.h" #include "scene/resources/2d/tile_set.h" class Control; @@ -73,7 +74,9 @@ private: // Layers. LocalVector<TileMapLayer *> layers; - TileMapLayer *default_layer; // Dummy layer to fetch default values. + + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; // Transforms for collision_animatable. Transform2D last_valid_transform; @@ -86,13 +89,14 @@ private: // Kept for compatibility with TileMap. With TileMapLayers as individual nodes, the format is stored directly in the array. void _set_tile_map_data_using_compatibility_format(int p_layer, TileMapDataFormat p_format, const Vector<int> &p_data); Vector<int> _get_tile_map_data_using_compatibility_format(int p_layer) const; + void _set_layer_tile_data(int p_layer, const PackedInt32Array &p_data); protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - bool _property_can_revert(const StringName &p_name) const; - bool _property_get_revert(const StringName &p_name, Variant &r_property) const; + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _notification(int p_what); static void _bind_methods(); @@ -235,7 +239,6 @@ public: PackedStringArray get_configuration_warnings() const override; TileMap(); - ~TileMap(); }; VARIANT_ENUM_CAST(TileMap::VisibilityMode); diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index 873cfeebda..ab3c48562c 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -2588,6 +2588,11 @@ TileMapLayer::HighlightMode TileMapLayer::get_highlight_mode() const { } void TileMapLayer::set_tile_map_data_from_array(const Vector<uint8_t> &p_data) { + if (p_data.is_empty()) { + clear(); + return; + } + const int cell_data_struct_size = 12; int size = p_data.size(); @@ -2630,6 +2635,10 @@ Vector<uint8_t> TileMapLayer::get_tile_map_data_as_array() const { const int cell_data_struct_size = 12; Vector<uint8_t> tile_map_data_array; + if (tile_map_layer_data.is_empty()) { + return tile_map_data_array; + } + tile_map_data_array.resize(2 + tile_map_layer_data.size() * cell_data_struct_size); uint8_t *ptr = tile_map_data_array.ptrw(); diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index f1f9a04ea0..0cef56dbf2 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -496,6 +496,7 @@ Ref<AudioStream> AudioStreamPlayer3D::get_stream() const { } void AudioStreamPlayer3D::set_volume_db(float p_volume) { + ERR_FAIL_COND_MSG(Math::is_nan(p_volume), "Volume can't be set to NaN."); internal->volume_db = p_volume; } diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 86ff6d15dd..cc923b6676 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -397,7 +397,10 @@ int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const Loc const BSPSimplex &s = p_simplices[p_simplex]; for (int i = 0; i < 4; i++) { const Vector3 v = p_points[s.vertices[i]]; - if (p_plane.has_point(v)) { + // The tolerance used here comes from experiments on scenes up to + // 1000x1000x100 meters. If it's any smaller, some simplices will + // appear to self-intersect due to a lack of precision in Plane. + if (p_plane.has_point(v, 1.0 / (1 << 13))) { // Coplanar. } else if (p_plane.is_point_over(v)) { over++; @@ -419,7 +422,8 @@ int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const Loc //#define DEBUG_BSP int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const LocalVector<Plane> &p_planes, LocalVector<int32_t> &planes_tested, const LocalVector<BSPSimplex> &p_simplices, const LocalVector<int32_t> &p_simplex_indices, LocalVector<BSPNode> &bsp_nodes) { - //if we reach here, it means there is more than one simplex + ERR_FAIL_COND_V(p_simplex_indices.size() < 2, -1); + int32_t node_index = (int32_t)bsp_nodes.size(); bsp_nodes.push_back(BSPNode()); @@ -477,13 +481,14 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc float score = 0; //by default, score is 0 (worst) if (over_count > 0) { - //give score mainly based on ratio (under / over), this means that this plane is splitting simplices a lot, but its balanced - score = float(under_count) / over_count; + // Simplices that are intersected by the plane are moved into both the over + // and under subtrees which makes the entire tree deeper, so the best plane + // will have the least intersections while separating the simplices evenly. + float balance = float(under_count) / over_count; + float separation = float(over_count + under_count) / p_simplex_indices.size(); + score = balance * separation * separation; } - //adjusting priority over least splits, probably not a great idea - //score *= Math::sqrt(float(over_count + under_count) / p_simplex_indices.size()); //also multiply score - if (score > best_plane_score) { best_plane = plane; best_plane_score = score; @@ -491,6 +496,44 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc } } + // We often end up with two (or on rare occasions, three) simplices that are + // either disjoint or share one vertex and don't have a separating plane + // among their faces. The fallback is to loop through new planes created + // with one vertex of the first simplex and two vertices of the second until + // we find a winner. + if (best_plane_score == 0) { + const BSPSimplex &simplex0 = p_simplices[p_simplex_indices[0]]; + const BSPSimplex &simplex1 = p_simplices[p_simplex_indices[1]]; + + for (uint32_t i = 0; i < 4 && !best_plane_score; i++) { + Vector3 v0 = p_points[simplex0.vertices[i]]; + for (uint32_t j = 0; j < 3 && !best_plane_score; j++) { + if (simplex0.vertices[i] == simplex1.vertices[j]) { + break; + } + Vector3 v1 = p_points[simplex1.vertices[j]]; + for (uint32_t k = j + 1; k < 4; k++) { + if (simplex0.vertices[i] == simplex1.vertices[k]) { + break; + } + Vector3 v2 = p_points[simplex1.vertices[k]]; + + Plane plane = Plane(v0, v1, v2); + if (plane == Plane()) { // When v0, v1, and v2 are collinear, they can't form a plane. + continue; + } + int32_t side0 = _bsp_get_simplex_side(p_points, p_simplices, plane, p_simplex_indices[0]); + int32_t side1 = _bsp_get_simplex_side(p_points, p_simplices, plane, p_simplex_indices[1]); + if ((side0 == 1 && side1 == -1) || (side0 == -1 && side1 == 1)) { + best_plane = plane; + best_plane_score = 1.0; + break; + } + } + } + } + } + LocalVector<int32_t> indices_over; LocalVector<int32_t> indices_under; @@ -515,8 +558,6 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc #endif if (best_plane_score < 0.0 || indices_over.size() == p_simplex_indices.size() || indices_under.size() == p_simplex_indices.size()) { - ERR_FAIL_COND_V(p_simplex_indices.size() <= 1, 0); //should not happen, this is a bug - // Failed to separate the tetrahedrons using planes // this means Delaunay broke at some point. // Luckily, because we are using tetrahedrons, we can resort to diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 616fb18d53..85bf8846b9 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -515,6 +515,162 @@ bool MeshInstance3D::_property_get_revert(const StringName &p_name, Variant &r_p return false; } +Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing) { + Ref<ArrayMesh> source_mesh = get_mesh(); + ERR_FAIL_NULL_V_MSG(source_mesh, Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh."); + + Ref<ArrayMesh> bake_mesh; + + if (p_existing.is_valid()) { + ERR_FAIL_NULL_V_MSG(p_existing, Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh."); + ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh."); + + bake_mesh = p_existing; + } else { + bake_mesh.instantiate(); + } + + Mesh::BlendShapeMode blend_shape_mode = source_mesh->get_blend_shape_mode(); + int mesh_surface_count = source_mesh->get_surface_count(); + + bake_mesh->clear_surfaces(); + bake_mesh->set_blend_shape_mode(blend_shape_mode); + + for (int surface_index = 0; surface_index < mesh_surface_count; surface_index++) { + uint32_t surface_format = source_mesh->surface_get_format(surface_index); + + ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_VERTEX)); + + const Array &source_mesh_arrays = source_mesh->surface_get_arrays(surface_index); + + ERR_FAIL_COND_V(source_mesh_arrays.size() != RS::ARRAY_MAX, Ref<ArrayMesh>()); + + const Vector<Vector3> &source_mesh_vertex_array = source_mesh_arrays[Mesh::ARRAY_VERTEX]; + const Vector<Vector3> &source_mesh_normal_array = source_mesh_arrays[Mesh::ARRAY_NORMAL]; + const Vector<float> &source_mesh_tangent_array = source_mesh_arrays[Mesh::ARRAY_TANGENT]; + + Array new_mesh_arrays; + new_mesh_arrays.resize(Mesh::ARRAY_MAX); + for (int i = 0; i < source_mesh_arrays.size(); i++) { + if (i == Mesh::ARRAY_VERTEX || i == Mesh::ARRAY_NORMAL || i == Mesh::ARRAY_TANGENT) { + continue; + } + new_mesh_arrays[i] = source_mesh_arrays[i]; + } + + bool use_normal_array = source_mesh_normal_array.size() == source_mesh_vertex_array.size(); + bool use_tangent_array = source_mesh_tangent_array.size() / 4 == source_mesh_vertex_array.size(); + + Vector<Vector3> lerped_vertex_array = source_mesh_vertex_array; + Vector<Vector3> lerped_normal_array = source_mesh_normal_array; + Vector<float> lerped_tangent_array = source_mesh_tangent_array; + + const Vector3 *source_vertices_ptr = source_mesh_vertex_array.ptr(); + const Vector3 *source_normals_ptr = source_mesh_normal_array.ptr(); + const float *source_tangents_ptr = source_mesh_tangent_array.ptr(); + + Vector3 *lerped_vertices_ptrw = lerped_vertex_array.ptrw(); + Vector3 *lerped_normals_ptrw = lerped_normal_array.ptrw(); + float *lerped_tangents_ptrw = lerped_tangent_array.ptrw(); + + const Array &blendshapes_mesh_arrays = source_mesh->surface_get_blend_shape_arrays(surface_index); + int blend_shape_count = source_mesh->get_blend_shape_count(); + ERR_FAIL_COND_V(blendshapes_mesh_arrays.size() != blend_shape_count, Ref<ArrayMesh>()); + + for (int blendshape_index = 0; blendshape_index < blend_shape_count; blendshape_index++) { + float blend_weight = get_blend_shape_value(blendshape_index); + if (abs(blend_weight) <= 0.0001) { + continue; + } + + const Array &blendshape_mesh_arrays = blendshapes_mesh_arrays[blendshape_index]; + + const Vector<Vector3> &blendshape_vertex_array = blendshape_mesh_arrays[Mesh::ARRAY_VERTEX]; + const Vector<Vector3> &blendshape_normal_array = blendshape_mesh_arrays[Mesh::ARRAY_NORMAL]; + const Vector<float> &blendshape_tangent_array = blendshape_mesh_arrays[Mesh::ARRAY_TANGENT]; + + ERR_FAIL_COND_V(source_mesh_vertex_array.size() != blendshape_vertex_array.size(), Ref<ArrayMesh>()); + ERR_FAIL_COND_V(source_mesh_normal_array.size() != blendshape_normal_array.size(), Ref<ArrayMesh>()); + ERR_FAIL_COND_V(source_mesh_tangent_array.size() != blendshape_tangent_array.size(), Ref<ArrayMesh>()); + + const Vector3 *blendshape_vertices_ptr = blendshape_vertex_array.ptr(); + const Vector3 *blendshape_normals_ptr = blendshape_normal_array.ptr(); + const float *blendshape_tangents_ptr = blendshape_tangent_array.ptr(); + + if (blend_shape_mode == Mesh::BLEND_SHAPE_MODE_NORMALIZED) { + for (int i = 0; i < source_mesh_vertex_array.size(); i++) { + const Vector3 &source_vertex = source_vertices_ptr[i]; + const Vector3 &blendshape_vertex = blendshape_vertices_ptr[i]; + Vector3 lerped_vertex = source_vertex.lerp(blendshape_vertex, blend_weight) - source_vertex; + lerped_vertices_ptrw[i] += lerped_vertex; + + if (use_normal_array) { + const Vector3 &source_normal = source_normals_ptr[i]; + const Vector3 &blendshape_normal = blendshape_normals_ptr[i]; + Vector3 lerped_normal = source_normal.lerp(blendshape_normal, blend_weight) - source_normal; + lerped_normals_ptrw[i] += lerped_normal; + } + + if (use_tangent_array) { + int tangent_index = i * 4; + const Vector4 source_tangent = Vector4( + source_tangents_ptr[tangent_index], + source_tangents_ptr[tangent_index + 1], + source_tangents_ptr[tangent_index + 2], + source_tangents_ptr[tangent_index + 3]); + const Vector4 blendshape_tangent = Vector4( + blendshape_tangents_ptr[tangent_index], + blendshape_tangents_ptr[tangent_index + 1], + blendshape_tangents_ptr[tangent_index + 2], + blendshape_tangents_ptr[tangent_index + 3]); + Vector4 lerped_tangent = source_tangent.lerp(blendshape_tangent, blend_weight); + lerped_tangents_ptrw[tangent_index] += lerped_tangent.x; + lerped_tangents_ptrw[tangent_index + 1] += lerped_tangent.y; + lerped_tangents_ptrw[tangent_index + 2] += lerped_tangent.z; + lerped_tangents_ptrw[tangent_index + 3] += lerped_tangent.w; + } + } + } else if (blend_shape_mode == Mesh::BLEND_SHAPE_MODE_RELATIVE) { + for (int i = 0; i < source_mesh_vertex_array.size(); i++) { + const Vector3 &blendshape_vertex = blendshape_vertices_ptr[i]; + lerped_vertices_ptrw[i] += blendshape_vertex * blend_weight; + + if (use_normal_array) { + const Vector3 &blendshape_normal = blendshape_normals_ptr[i]; + lerped_normals_ptrw[i] += blendshape_normal * blend_weight; + } + + if (use_tangent_array) { + int tangent_index = i * 4; + const Vector4 blendshape_tangent = Vector4( + blendshape_tangents_ptr[tangent_index], + blendshape_tangents_ptr[tangent_index + 1], + blendshape_tangents_ptr[tangent_index + 2], + blendshape_tangents_ptr[tangent_index + 3]); + Vector4 lerped_tangent = blendshape_tangent * blend_weight; + lerped_tangents_ptrw[tangent_index] += lerped_tangent.x; + lerped_tangents_ptrw[tangent_index + 1] += lerped_tangent.y; + lerped_tangents_ptrw[tangent_index + 2] += lerped_tangent.z; + lerped_tangents_ptrw[tangent_index + 3] += lerped_tangent.w; + } + } + } + } + + new_mesh_arrays[Mesh::ARRAY_VERTEX] = lerped_vertex_array; + if (use_normal_array) { + new_mesh_arrays[Mesh::ARRAY_NORMAL] = lerped_normal_array; + } + if (use_tangent_array) { + new_mesh_arrays[Mesh::ARRAY_TANGENT] = lerped_tangent_array; + } + + bake_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, new_mesh_arrays, Array(), Dictionary(), surface_format); + } + + return bake_mesh; +} + void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance3D::set_mesh); ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance3D::get_mesh); @@ -542,6 +698,9 @@ void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_blend_shape_value", "blend_shape_idx", "value"), &MeshInstance3D::set_blend_shape_value); ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents); + + ClassDB::bind_method(D_METHOD("bake_mesh_from_current_blend_shape_mix", "existing"), &MeshInstance3D::bake_mesh_from_current_blend_shape_mix, DEFVAL(Ref<ArrayMesh>())); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); ADD_GROUP("Skeleton", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skin", PROPERTY_HINT_RESOURCE_TYPE, "Skin"), "set_skin", "get_skin"); diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index d6ae1291d3..8a7e03c5b3 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -101,6 +101,8 @@ public: virtual AABB get_aabb() const override; + Ref<ArrayMesh> bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>()); + MeshInstance3D(); ~MeshInstance3D(); }; diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 5c081a0b47..3b788b2fd0 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -293,7 +293,7 @@ Vector3 Node3D::get_global_rotation_degrees() const { void Node3D::set_global_rotation(const Vector3 &p_euler_rad) { ERR_THREAD_GUARD; Transform3D transform = get_global_transform(); - transform.basis = Basis::from_euler(p_euler_rad); + transform.basis = Basis::from_euler(p_euler_rad) * Basis::from_scale(transform.basis.get_scale()); set_global_transform(transform); } @@ -753,6 +753,15 @@ void Node3D::set_as_top_level(bool p_enabled) { data.top_level = p_enabled; } +void Node3D::set_as_top_level_keep_local(bool p_enabled) { + ERR_THREAD_GUARD; + if (data.top_level == p_enabled) { + return; + } + data.top_level = p_enabled; + _propagate_transform_changed(this); +} + bool Node3D::is_set_as_top_level() const { ERR_READ_THREAD_GUARD_V(false); return data.top_level; diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index 7f8c3e6e68..c1667221df 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -227,6 +227,7 @@ public: void clear_gizmos(); void set_as_top_level(bool p_enabled); + void set_as_top_level_keep_local(bool p_enabled); bool is_set_as_top_level() const; void set_disable_scale(bool p_enabled); diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index 9581ae58d8..78a21ba9e1 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -366,6 +366,12 @@ void SkeletonIK3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node"), "set_target_node", "get_target_node"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_min_distance", "get_min_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_iterations"), "set_max_iterations", "get_max_iterations"); + +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("set_interpolation", "interpolation"), &SkeletonIK3D::_set_interpolation); + ClassDB::bind_method(D_METHOD("get_interpolation"), &SkeletonIK3D::_get_interpolation); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interpolation", PROPERTY_HINT_RANGE, "0,1,0.001", PROPERTY_USAGE_NONE), "set_interpolation", "get_interpolation"); +#endif } void SkeletonIK3D::_process_modification() { @@ -415,6 +421,16 @@ StringName SkeletonIK3D::get_tip_bone() const { return tip_bone; } +#ifndef DISABLE_DEPRECATED +void SkeletonIK3D::_set_interpolation(real_t p_interpolation) { + set_influence(p_interpolation); +} + +real_t SkeletonIK3D::_get_interpolation() const { + return get_influence(); +} +#endif + void SkeletonIK3D::set_target_transform(const Transform3D &p_target) { target = p_target; reload_goal(); diff --git a/scene/3d/skeleton_ik_3d.h b/scene/3d/skeleton_ik_3d.h index eff018f2cc..5d6020194e 100644 --- a/scene/3d/skeleton_ik_3d.h +++ b/scene/3d/skeleton_ik_3d.h @@ -134,6 +134,11 @@ class SkeletonIK3D : public SkeletonModifier3D { Variant target_node_override_ref = Variant(); FabrikInverseKinematic::Task *task = nullptr; +#ifndef DISABLE_DEPRECATED + void _set_interpolation(real_t p_interpolation); + real_t _get_interpolation() const; +#endif // DISABLE_DEPRECATED + protected: void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 503c39ae3e..89f7ab2391 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -377,6 +377,7 @@ void GeometryInstance3D::set_custom_aabb(AABB p_aabb) { } custom_aabb = p_aabb; RS::get_singleton()->instance_set_custom_aabb(get_instance(), custom_aabb); + update_gizmos(); } AABB GeometryInstance3D::get_custom_aabb() const { diff --git a/scene/3d/xr_body_modifier_3d.cpp b/scene/3d/xr_body_modifier_3d.cpp index 0099784a05..8aec3e856e 100644 --- a/scene/3d/xr_body_modifier_3d.cpp +++ b/scene/3d/xr_body_modifier_3d.cpp @@ -44,13 +44,9 @@ void XRBodyModifier3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &XRBodyModifier3D::set_bone_update); ClassDB::bind_method(D_METHOD("get_bone_update"), &XRBodyModifier3D::get_bone_update); - ClassDB::bind_method(D_METHOD("set_show_when_tracked", "show"), &XRBodyModifier3D::set_show_when_tracked); - ClassDB::bind_method(D_METHOD("get_show_when_tracked"), &XRBodyModifier3D::get_show_when_tracked); - - ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/body"), "set_body_tracker", "get_body_tracker"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/body_tracker"), "set_body_tracker", "get_body_tracker"); ADD_PROPERTY(PropertyInfo(Variant::INT, "body_update", PROPERTY_HINT_FLAGS, "Upper Body,Lower Body,Hands"), "set_body_update", "get_body_update"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_when_tracked"), "set_show_when_tracked", "get_show_when_tracked"); BIND_BITFIELD_FLAG(BODY_UPDATE_UPPER_BODY); BIND_BITFIELD_FLAG(BODY_UPDATE_LOWER_BODY); @@ -86,14 +82,6 @@ XRBodyModifier3D::BoneUpdate XRBodyModifier3D::get_bone_update() const { return bone_update; } -void XRBodyModifier3D::set_show_when_tracked(bool p_show_when_tracked) { - show_when_tracked = p_show_when_tracked; -} - -bool XRBodyModifier3D::get_show_when_tracked() const { - return show_when_tracked; -} - void XRBodyModifier3D::_get_joint_data() { // Table of Godot Humanoid bone names. static const String bone_names[XRBodyTracker::JOINT_MAX] = { @@ -189,7 +177,7 @@ void XRBodyModifier3D::_get_joint_data() { joints[i].parent_joint = -1; } - Skeleton3D *skeleton = get_skeleton(); + const Skeleton3D *skeleton = get_skeleton(); if (!skeleton) { return; } @@ -257,27 +245,22 @@ void XRBodyModifier3D::_process_modification() { return; } - XRServer *xr_server = XRServer::get_singleton(); + const XRServer *xr_server = XRServer::get_singleton(); if (!xr_server) { return; } - Ref<XRBodyTracker> tracker = xr_server->get_body_tracker(tracker_name); - if (tracker.is_null()) { + const Ref<XRBodyTracker> tracker = xr_server->get_tracker(tracker_name); + if (!tracker.is_valid()) { return; } - // Handle no tracking data. + // Skip if no tracking data. if (!tracker->get_has_tracking_data()) { - // If tracking-state determines visibility then hide the node. - if (show_when_tracked) { - set_visible(false); - } return; } // Get the world and skeleton scale. - const float ws = xr_server->get_world_scale(); const float ss = skeleton->get_motion_scale(); // Read the relevant tracking data. This applies the skeleton motion scale to @@ -296,12 +279,8 @@ void XRBodyModifier3D::_process_modification() { } } - // Handle root joint not tracked. + // Skip if root joint not tracked. if (!has_valid_data[XRBodyTracker::JOINT_ROOT]) { - // If tracking-state determines visibility then hide the node. - if (show_when_tracked) { - set_visible(false); - } return; } @@ -331,16 +310,6 @@ void XRBodyModifier3D::_process_modification() { // Always update the bone rotation. skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis)); } - - // Transform to the tracking data root pose. This also applies the XR world-scale to allow - // scaling the avatars mesh and skeleton appropriately (if they are child nodes). - set_transform( - transforms[XRBodyTracker::JOINT_ROOT] * ws); - - // If tracking-state determines visibility then show the node. - if (show_when_tracked) { - set_visible(true); - } } void XRBodyModifier3D::_tracker_changed(const StringName &p_tracker_name, const Ref<XRBodyTracker> &p_tracker) { diff --git a/scene/3d/xr_body_modifier_3d.h b/scene/3d/xr_body_modifier_3d.h index 03b1c07d53..9ff0cd7207 100644 --- a/scene/3d/xr_body_modifier_3d.h +++ b/scene/3d/xr_body_modifier_3d.h @@ -66,9 +66,6 @@ public: void set_bone_update(BoneUpdate p_bone_update); BoneUpdate get_bone_update() const; - void set_show_when_tracked(bool p_show_when_tracked); - bool get_show_when_tracked() const; - void _notification(int p_what); protected: @@ -83,10 +80,9 @@ private: int parent_joint = -1; }; - StringName tracker_name = "/user/body"; + StringName tracker_name = "/user/body_tracker"; BitField<BodyUpdate> body_update = BODY_UPDATE_UPPER_BODY | BODY_UPDATE_LOWER_BODY | BODY_UPDATE_HANDS; BoneUpdate bone_update = BONE_UPDATE_FULL; - bool show_when_tracked = true; JointData joints[XRBodyTracker::JOINT_MAX]; void _get_joint_data(); diff --git a/scene/3d/xr_face_modifier_3d.cpp b/scene/3d/xr_face_modifier_3d.cpp index be92a587b0..43cef95fb9 100644 --- a/scene/3d/xr_face_modifier_3d.cpp +++ b/scene/3d/xr_face_modifier_3d.cpp @@ -495,7 +495,7 @@ static void remove_driven_unified_blend_shapes(RBMap<int, int> &p_blend_mapping) void XRFaceModifier3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_face_tracker", "tracker_name"), &XRFaceModifier3D::set_face_tracker); ClassDB::bind_method(D_METHOD("get_face_tracker"), &XRFaceModifier3D::get_face_tracker); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "face_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/head"), "set_face_tracker", "get_face_tracker"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "face_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/face_tracker"), "set_face_tracker", "get_face_tracker"); ClassDB::bind_method(D_METHOD("set_target", "target"), &XRFaceModifier3D::set_target); ClassDB::bind_method(D_METHOD("get_target"), &XRFaceModifier3D::get_target); @@ -576,8 +576,8 @@ void XRFaceModifier3D::_update_face_blends() const { } // Get the face tracker. - const Ref<XRFaceTracker> p = xr_server->get_face_tracker(tracker_name); - if (!p.is_valid()) { + const Ref<XRFaceTracker> tracker = xr_server->get_tracker(tracker_name); + if (!tracker.is_valid()) { return; } @@ -588,7 +588,7 @@ void XRFaceModifier3D::_update_face_blends() const { } // Get the blend weights. - const PackedFloat32Array weights = p->get_blend_shapes(); + const PackedFloat32Array weights = tracker->get_blend_shapes(); // Apply all the face blend weights to the mesh. for (const KeyValue<int, int> &it : blend_mapping) { diff --git a/scene/3d/xr_face_modifier_3d.h b/scene/3d/xr_face_modifier_3d.h index 147c374e95..e5e59afe1d 100644 --- a/scene/3d/xr_face_modifier_3d.h +++ b/scene/3d/xr_face_modifier_3d.h @@ -47,7 +47,7 @@ class XRFaceModifier3D : public Node3D { GDCLASS(XRFaceModifier3D, Node3D); private: - StringName tracker_name = "/user/head"; + StringName tracker_name = "/user/face_tracker"; NodePath target; // Map from XRFaceTracker blend shape index to mesh blend shape index. diff --git a/scene/3d/xr_hand_modifier_3d.cpp b/scene/3d/xr_hand_modifier_3d.cpp index 7fecb53008..1e78a4630f 100644 --- a/scene/3d/xr_hand_modifier_3d.cpp +++ b/scene/3d/xr_hand_modifier_3d.cpp @@ -40,7 +40,7 @@ void XRHandModifier3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &XRHandModifier3D::set_bone_update); ClassDB::bind_method(D_METHOD("get_bone_update"), &XRHandModifier3D::get_bone_update); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "hand_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/left,/user/right"), "set_hand_tracker", "get_hand_tracker"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "hand_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/hand_tracker/left,/user/hand_tracker/right"), "set_hand_tracker", "get_hand_tracker"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update"); BIND_ENUM_CONSTANT(BONE_UPDATE_FULL); @@ -111,22 +111,30 @@ void XRHandModifier3D::_get_joint_data() { joints[i].parent_joint = -1; } - Skeleton3D *skeleton = get_skeleton(); + const Skeleton3D *skeleton = get_skeleton(); if (!skeleton) { return; } - XRServer *xr_server = XRServer::get_singleton(); + const XRServer *xr_server = XRServer::get_singleton(); if (!xr_server) { return; } - Ref<XRHandTracker> tracker = xr_server->get_hand_tracker(tracker_name); + const Ref<XRHandTracker> tracker = xr_server->get_tracker(tracker_name); if (tracker.is_null()) { return; } - XRHandTracker::Hand hand = tracker->get_hand(); + // Verify we have a left or right hand tracker. + const XRPositionalTracker::TrackerHand tracker_hand = tracker->get_tracker_hand(); + if (tracker_hand != XRPositionalTracker::TRACKER_HAND_LEFT && + tracker_hand != XRPositionalTracker::TRACKER_HAND_RIGHT) { + return; + } + + // Get the hand index (0 = left, 1 = right). + const int hand = tracker_hand == XRPositionalTracker::TRACKER_HAND_LEFT ? 0 : 1; // Find the skeleton-bones associated with each joint. int bones[XRHandTracker::HAND_JOINT_MAX]; @@ -176,18 +184,22 @@ void XRHandModifier3D::_process_modification() { return; } - XRServer *xr_server = XRServer::get_singleton(); + const XRServer *xr_server = XRServer::get_singleton(); if (!xr_server) { return; } - Ref<XRHandTracker> tracker = xr_server->get_hand_tracker(tracker_name); + const Ref<XRHandTracker> tracker = xr_server->get_tracker(tracker_name); if (tracker.is_null()) { return; } + // Skip if no tracking data + if (!tracker->get_has_tracking_data()) { + return; + } + // Get the world and skeleton scale. - const float ws = xr_server->get_world_scale(); const float ss = skeleton->get_motion_scale(); // We cache our transforms so we can quickly calculate local transforms. @@ -195,55 +207,44 @@ void XRHandModifier3D::_process_modification() { Transform3D transforms[XRHandTracker::HAND_JOINT_MAX]; Transform3D inv_transforms[XRHandTracker::HAND_JOINT_MAX]; - if (tracker->get_has_tracking_data()) { - for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { - BitField<XRHandTracker::HandJointFlags> flags = tracker->get_hand_joint_flags((XRHandTracker::HandJoint)joint); - has_valid_data[joint] = flags.has_flag(XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID); + for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { + BitField<XRHandTracker::HandJointFlags> flags = tracker->get_hand_joint_flags((XRHandTracker::HandJoint)joint); + has_valid_data[joint] = flags.has_flag(XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID); - if (has_valid_data[joint]) { - transforms[joint] = tracker->get_hand_joint_transform((XRHandTracker::HandJoint)joint); - transforms[joint].origin *= ss; - inv_transforms[joint] = transforms[joint].inverse(); - } + if (has_valid_data[joint]) { + transforms[joint] = tracker->get_hand_joint_transform((XRHandTracker::HandJoint)joint); + transforms[joint].origin *= ss; + inv_transforms[joint] = transforms[joint].inverse(); } + } - if (has_valid_data[XRHandTracker::HAND_JOINT_PALM]) { - for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { - // Get the skeleton bone (skip if none). - const int bone = joints[joint].bone; - if (bone == -1) { - continue; - } - - // Calculate the relative relationship to the parent bone joint. - const int parent_joint = joints[joint].parent_joint; - const Transform3D relative_transform = inv_transforms[parent_joint] * transforms[joint]; - - // Update the bone position if enabled by update mode. - if (bone_update == BONE_UPDATE_FULL) { - skeleton->set_bone_pose_position(joints[joint].bone, relative_transform.origin); - } - - // Always update the bone rotation. - skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis)); - } + // Skip if palm has no tracking data + if (!has_valid_data[XRHandTracker::HAND_JOINT_PALM]) { + return; + } + + for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { + // Get the skeleton bone (skip if none). + const int bone = joints[joint].bone; + if (bone == -1) { + continue; + } - // Transform to the skeleton pose. This uses the HAND_JOINT_PALM position without skeleton-scaling, as it - // must be positioned to match the physical hand location. It is scaled with the world space to match - // the scaling done to the camera and eyes. - set_transform( - tracker->get_hand_joint_transform(XRHandTracker::HAND_JOINT_PALM) * ws); + // Calculate the relative relationship to the parent bone joint. + const int parent_joint = joints[joint].parent_joint; + const Transform3D relative_transform = inv_transforms[parent_joint] * transforms[joint]; - set_visible(true); - } else { - set_visible(false); + // Update the bone position if enabled by update mode. + if (bone_update == BONE_UPDATE_FULL) { + skeleton->set_bone_pose_position(joints[joint].bone, relative_transform.origin); } - } else { - set_visible(false); + + // Always update the bone rotation. + skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis)); } } -void XRHandModifier3D::_tracker_changed(StringName p_tracker_name, const Ref<XRHandTracker> &p_tracker) { +void XRHandModifier3D::_tracker_changed(StringName p_tracker_name, XRServer::TrackerType p_tracker_type) { if (tracker_name == p_tracker_name) { _get_joint_data(); } @@ -258,9 +259,9 @@ void XRHandModifier3D::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { - xr_server->connect("hand_tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed)); - xr_server->connect("hand_tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed)); - xr_server->connect("hand_tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed).bind(Ref<XRHandTracker>())); + xr_server->connect("tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->connect("tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->connect("tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed)); } _get_joint_data(); @@ -268,9 +269,9 @@ void XRHandModifier3D::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { - xr_server->disconnect("hand_tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed)); - xr_server->disconnect("hand_tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed)); - xr_server->disconnect("hand_tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed).bind(Ref<XRHandTracker>())); + xr_server->disconnect("tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->disconnect("tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->disconnect("tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed)); } for (int i = 0; i < XRHandTracker::HAND_JOINT_MAX; i++) { diff --git a/scene/3d/xr_hand_modifier_3d.h b/scene/3d/xr_hand_modifier_3d.h index 9f7ce45c9d..67d1694d41 100644 --- a/scene/3d/xr_hand_modifier_3d.h +++ b/scene/3d/xr_hand_modifier_3d.h @@ -69,12 +69,12 @@ private: int parent_joint = -1; }; - StringName tracker_name = "/user/left"; + StringName tracker_name = "/user/hand_tracker/left"; BoneUpdate bone_update = BONE_UPDATE_FULL; JointData joints[XRHandTracker::HAND_JOINT_MAX]; void _get_joint_data(); - void _tracker_changed(StringName p_tracker_name, const Ref<XRHandTracker> &p_tracker); + void _tracker_changed(StringName p_tracker_name, XRServer::TrackerType p_tracker_type); }; VARIANT_ENUM_CAST(XRHandModifier3D::BoneUpdate) diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index 12a9f50ed7..3f4b962641 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -80,10 +80,11 @@ PackedStringArray XRCamera3D::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { - // must be child node of XROrigin3D! - XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent()); - if (origin == nullptr) { - warnings.push_back(RTR("XRCamera3D must have an XROrigin3D node as its parent.")); + // Warn if the node has a parent which isn't an XROrigin3D! + Node *parent = get_parent(); + XROrigin3D *origin = Object::cast_to<XROrigin3D>(parent); + if (parent && origin == nullptr) { + warnings.push_back(RTR("XRCamera3D may not function as expected without an XROrigin3D node as its parent.")); }; } @@ -229,6 +230,10 @@ void XRNode3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pose_name"), &XRNode3D::get_pose_name); ADD_PROPERTY(PropertyInfo(Variant::STRING, "pose", PROPERTY_HINT_ENUM_SUGGESTION), "set_pose_name", "get_pose_name"); + ClassDB::bind_method(D_METHOD("set_show_when_tracked", "show"), &XRNode3D::set_show_when_tracked); + ClassDB::bind_method(D_METHOD("get_show_when_tracked"), &XRNode3D::get_show_when_tracked); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_when_tracked"), "set_show_when_tracked", "get_show_when_tracked"); + ClassDB::bind_method(D_METHOD("get_is_active"), &XRNode3D::get_is_active); ClassDB::bind_method(D_METHOD("get_has_tracking_data"), &XRNode3D::get_has_tracking_data); ClassDB::bind_method(D_METHOD("get_pose"), &XRNode3D::get_pose); @@ -296,6 +301,14 @@ StringName XRNode3D::get_pose_name() const { return pose_name; } +void XRNode3D::set_show_when_tracked(bool p_show) { + show_when_tracked = p_show; +} + +bool XRNode3D::get_show_when_tracked() const { + return show_when_tracked; +} + bool XRNode3D::get_is_active() const { if (tracker.is_null()) { return false; @@ -402,6 +415,11 @@ void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) { // Handle change of has_tracking_data. has_tracking_data = p_has_tracking_data; emit_signal(SNAME("tracking_changed"), has_tracking_data); + + // If configured, show or hide the node based on tracking data. + if (show_when_tracked) { + set_visible(has_tracking_data); + } } XRNode3D::XRNode3D() { @@ -428,11 +446,12 @@ PackedStringArray XRNode3D::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { - // must be child node of XROrigin! - XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent()); - if (origin == nullptr) { - warnings.push_back(RTR("XRController3D must have an XROrigin3D node as its parent.")); - } + // Warn if the node has a parent which isn't an XROrigin3D! + Node *parent = get_parent(); + XROrigin3D *origin = Object::cast_to<XROrigin3D>(parent); + if (parent && origin == nullptr) { + warnings.push_back(RTR("XRNode3D may not function as expected without an XROrigin3D node as its parent.")); + }; if (tracker_name == "") { warnings.push_back(RTR("No tracker name is set.")); diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h index bdcccd51ea..a42f6d470f 100644 --- a/scene/3d/xr_nodes.h +++ b/scene/3d/xr_nodes.h @@ -79,6 +79,7 @@ private: StringName tracker_name; StringName pose_name = "default"; bool has_tracking_data = false; + bool show_when_tracked = false; protected: Ref<XRPositionalTracker> tracker; @@ -105,6 +106,9 @@ public: bool get_is_active() const; bool get_has_tracking_data() const; + void set_show_when_tracked(bool p_show); + bool get_show_when_tracked() const; + void trigger_haptic_pulse(const String &p_action_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec = 0); Ref<XRPose> get_pose(); diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index dadcfab69f..0c2bd64e84 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -59,6 +59,7 @@ Ref<AudioStream> AudioStreamPlayer::get_stream() const { } void AudioStreamPlayer::set_volume_db(float p_volume) { + ERR_FAIL_COND_MSG(Math::is_nan(p_volume), "Volume can't be set to NaN."); internal->volume_db = p_volume; Vector<AudioFrame> volume_vector = _get_volume_vector(); diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index b1870eea55..ad3f607661 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -358,7 +358,7 @@ void Button::_notification(int p_what) { if (!xl_text.is_empty()) { text_buf->set_alignment(align_rtl_checked); - float text_buf_width = MAX(1.0f, drawable_size_remained.width); // The space's width filled by the text_buf. + float text_buf_width = Math::ceil(MAX(1.0f, drawable_size_remained.width)); // The space's width filled by the text_buf. text_buf->set_width(text_buf_width); Point2 text_ofs; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 3b62f6c23d..848a598ebb 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -403,8 +403,9 @@ void ColorPicker::add_mode(ColorMode *p_mode) { } void ColorPicker::create_slider(GridContainer *gc, int idx) { - Label *lbl = memnew(Label()); + Label *lbl = memnew(Label); lbl->set_v_size_flags(SIZE_SHRINK_CENTER); + lbl->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); gc->add_child(lbl); HSlider *slider = memnew(HSlider); diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index c13a2e281a..4d2080dda2 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -202,7 +202,7 @@ void AcceptDialog::register_text_enter(LineEdit *p_line_edit) { } void AcceptDialog::_update_child_rects() { - Size2 dlg_size = get_size(); + Size2 dlg_size = Vector2(get_size()) / get_content_scale_factor(); float h_margins = theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT); float v_margins = theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM); @@ -210,6 +210,15 @@ void AcceptDialog::_update_child_rects() { bg_panel->set_position(Point2()); bg_panel->set_size(dlg_size); + for (int i = 0; i < buttons_hbox->get_child_count(); i++) { + Button *b = Object::cast_to<Button>(buttons_hbox->get_child(i)); + if (!b) { + continue; + } + + b->set_custom_minimum_size(Size2(theme_cache.buttons_min_width, theme_cache.buttons_min_height)); + } + // Place the buttons from the bottom edge to their minimum required size. Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size(); Size2 buttons_size = Size2(dlg_size.x - h_margins, buttons_minsize.y); @@ -255,12 +264,6 @@ Size2 AcceptDialog::_get_contents_minimum_size() const { content_minsize = child_minsize.max(content_minsize); } - // Then we take the background panel as it provides the offsets, - // which are always added to the minimum size. - if (theme_cache.panel_style.is_valid()) { - content_minsize += theme_cache.panel_style->get_minimum_size(); - } - // Then we add buttons. Horizontally we're interested in whichever // value is the biggest. Vertically buttons add to the overall size. Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size(); @@ -269,6 +272,12 @@ Size2 AcceptDialog::_get_contents_minimum_size() const { // Plus there is a separation size added on top. content_minsize.y += theme_cache.buttons_separation; + // Then we take the background panel as it provides the offsets, + // which are always added to the minimum size. + if (theme_cache.panel_style.is_valid()) { + content_minsize += theme_cache.panel_style->get_minimum_size(); + } + return content_minsize; } @@ -389,6 +398,8 @@ void AcceptDialog::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, AcceptDialog, panel_style, "panel"); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_separation); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_width); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_height); } bool AcceptDialog::swap_cancel_ok = false; diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 6f9f450778..12b48c903a 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -57,6 +57,8 @@ class AcceptDialog : public Window { struct ThemeCache { Ref<StyleBox> panel_style; int buttons_separation = 0; + int buttons_min_width = 0; + int buttons_min_height = 0; } theme_cache; void _custom_action(const String &p_action); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 12b2364ddf..c3a586a1ee 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -1255,52 +1255,6 @@ int FileDialog::get_option_count() const { return options.size(); } -bool FileDialog::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) { - int item_index = components[0].trim_prefix("option_").to_int(); - String property = components[1]; - if (property == "name") { - set_option_name(item_index, p_value); - return true; - } else if (property == "values") { - set_option_values(item_index, p_value); - return true; - } else if (property == "default") { - set_option_default(item_index, p_value); - return true; - } - } - return false; -} - -bool FileDialog::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) { - int item_index = components[0].trim_prefix("option_").to_int(); - String property = components[1]; - if (property == "name") { - r_ret = get_option_name(item_index); - return true; - } else if (property == "values") { - r_ret = get_option_values(item_index); - return true; - } else if (property == "default") { - r_ret = get_option_default(item_index); - return true; - } - } - return false; -} - -void FileDialog::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < options.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("option_%d/name", i))); - p_list->push_back(PropertyInfo(Variant::PACKED_STRING_ARRAY, vformat("option_%d/values", i))); - p_list->push_back(PropertyInfo(Variant::INT, vformat("option_%d/default", i))); - } -} - void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed); @@ -1386,6 +1340,13 @@ void FileDialog::_bind_methods() { BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_hover_color, "font_hover_color", "Button"); BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_focus_color, "font_focus_color", "Button"); BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_pressed_color, "font_pressed_color", "Button"); + + Option defaults; + + base_property_helper.set_prefix("option_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &FileDialog::set_option_name, &FileDialog::get_option_name); + base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &FileDialog::set_option_values, &FileDialog::get_option_values); + base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &FileDialog::set_option_default, &FileDialog::get_option_default); } void FileDialog::set_show_hidden_files(bool p_show) { @@ -1563,6 +1524,8 @@ FileDialog::FileDialog() { if (register_func) { register_func(this); } + + property_helper.setup_for_instance(base_property_helper, this); } FileDialog::~FileDialog() { diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 7caae7e216..4236f0a56b 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -37,6 +37,7 @@ #include "scene/gui/line_edit.h" #include "scene/gui/option_button.h" #include "scene/gui/tree.h" +#include "scene/property_list_helper.h" class GridContainer; @@ -137,6 +138,10 @@ private: Vector<String> values; int default_idx = 0; }; + + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + Vector<Option> options; Dictionary selected_options; bool options_dirty = false; @@ -187,9 +192,11 @@ private: protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); } + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, options.size()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } static void _bind_methods(); public: diff --git a/scene/gui/graph_frame.cpp b/scene/gui/graph_frame.cpp index 288b8ba66d..ca9f7e6fcf 100644 --- a/scene/gui/graph_frame.cpp +++ b/scene/gui/graph_frame.cpp @@ -118,7 +118,7 @@ void GraphFrame::_notification(int p_what) { sb_panel_flat->set_border_color(selected ? original_border_color : tint_color.lightened(0.3)); draw_style_box(sb_panel_flat, body_rect); } else if (sb_panel_texture.is_valid()) { - sb_panel_texture = sb_panel_flat->duplicate(); + sb_panel_texture = sb_panel_texture->duplicate(); sb_panel_texture->set_modulate(tint_color); draw_style_box(sb_panel_texture, body_rect); } diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 89c627a7a8..e83d9c7c1b 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -153,54 +153,25 @@ void MenuButton::_notification(int p_what) { } bool MenuButton::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { + const String sname = p_name; + if (property_helper.is_property_valid(sname)) { bool valid; - popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid); + popup->set(sname.trim_prefix("popup/"), p_value, &valid); return valid; } return false; } bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { + const String sname = p_name; + if (property_helper.is_property_valid(sname)) { bool valid; - r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid); + r_ret = popup->get(sname.trim_prefix("popup/"), &valid); return valid; } return false; } -void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < popup->get_item_count(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"); - pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/checked", i)); - pi.usage &= ~(!popup->is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i)); - pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i)); - pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void MenuButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup); ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup); @@ -215,6 +186,17 @@ void MenuButton::_bind_methods() { ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_"); ADD_SIGNAL(MethodInfo("about_to_popup")); + + PopupMenu::Item defaults(true); + + base_property_helper.set_prefix("popup/item_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon); + base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"), defaults.checkable_type); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "checked"), defaults.checked); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator); } void MenuButton::set_disable_shortcuts(bool p_disabled) { @@ -235,6 +217,8 @@ MenuButton::MenuButton(const String &p_text) : add_child(popup, false, INTERNAL_MODE_FRONT); popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(true)); popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(false)); + + property_helper.setup_for_instance(base_property_helper, this); } MenuButton::~MenuButton() { diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index eea6b8e877..2bd577ddd0 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -33,6 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" +#include "scene/property_list_helper.h" class MenuButton : public Button { GDCLASS(MenuButton, Button); @@ -42,13 +43,18 @@ class MenuButton : public Button { bool disable_shortcuts = false; PopupMenu *popup = nullptr; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + void _popup_visibility_changed(bool p_visible); protected: void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } static void _bind_methods(); virtual void shortcut_input(const Ref<InputEvent> &p_event) override; diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 0e10652f07..509c6aca99 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -154,23 +154,20 @@ void OptionButton::_notification(int p_what) { } bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { - const String &property = components[2]; - if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { - return false; - } + int index; + const String sname = p_name; + if (property_helper.is_property_valid(sname, &index)) { bool valid; - popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid); + popup->set(sname.trim_prefix("popup/"), p_value, &valid); - int idx = components[1].get_slice("_", 1).to_int(); - if (idx == current) { + if (index == current) { // Force refreshing currently displayed item. current = NONE_SELECTED; - _select(idx, false); + _select(index, false); } + const String property = sname.get_slice("/", 2); if (property == "text" || property == "icon") { _queue_update_size_cache(); } @@ -180,42 +177,6 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { return false; } -bool OptionButton::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { - const String &property = components[2]; - if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { - return false; - } - - bool valid; - r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid); - return valid; - } - return false; -} - -void OptionButton::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < popup->get_item_count(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i)); - pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i)); - pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void OptionButton::_focused(int p_which) { emit_signal(SNAME("item_focused"), p_which); } @@ -606,6 +567,15 @@ void OptionButton::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, OptionButton, arrow_icon, "arrow"); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, arrow_margin); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, modulate_arrow); + + PopupMenu::Item defaults(true); + + base_property_helper.set_prefix("popup/item_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &OptionButton::_dummy_setter, &OptionButton::get_item_text); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &OptionButton::_dummy_setter, &OptionButton::get_item_icon); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &OptionButton::_dummy_setter, &OptionButton::is_item_disabled); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &OptionButton::_dummy_setter, &OptionButton::is_item_separator); } void OptionButton::set_disable_shortcuts(bool p_disabled) { @@ -625,6 +595,8 @@ OptionButton::OptionButton(const String &p_text) : popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected)); popup->connect("id_focused", callable_mp(this, &OptionButton::_focused)); popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed).bind(false)); + + property_helper.setup_for_instance(base_property_helper, this); } OptionButton::~OptionButton() { diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 9c15b295a9..4b5164161a 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -33,6 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" +#include "scene/property_list_helper.h" class OptionButton : public Button { GDCLASS(OptionButton, Button); @@ -64,11 +65,15 @@ class OptionButton : public Button { int modulate_arrow = 0; } theme_cache; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + void _focused(int p_which); void _selected(int p_which); void _select(int p_which, bool p_emit = false); void _select_int(int p_which); void _refresh_size_cache(); + void _dummy_setter() {} // Stub for PropertyListHelper (_set() doesn't use it). virtual void pressed() override; @@ -78,8 +83,10 @@ protected: void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index d90d5da2e9..87383283fd 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -230,7 +230,8 @@ Size2 PopupPanel::_get_contents_minimum_size() const { void PopupPanel::_update_child_rects() { Vector2 cpos(theme_cache.panel_style->get_offset()); - Vector2 csize(get_size() - theme_cache.panel_style->get_minimum_size()); + Vector2 panel_size = Vector2(get_size()) / get_content_scale_factor(); + Vector2 csize = panel_size - theme_cache.panel_style->get_minimum_size(); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -244,7 +245,7 @@ void PopupPanel::_update_child_rects() { if (c == panel) { c->set_position(Vector2()); - c->set_size(get_size()); + c->set_size(panel_size); } else { c->set_position(cpos); c->set_size(csize); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index b6dd3ac0b4..260956a775 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -312,18 +312,17 @@ int PopupMenu::_get_items_total_height() const { } int PopupMenu::_get_mouse_over(const Point2 &p_over) const { - if (p_over.x < 0 || p_over.x >= get_size().width || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP)) { + float win_scale = get_content_scale_factor(); + if (p_over.x < 0 || p_over.x >= get_size().width * win_scale || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP) * win_scale) { return -1; } - Point2 ofs; + Point2 ofs = Point2(0, theme_cache.v_separation * 0.5) * win_scale; for (int i = 0; i < items.size(); i++) { - ofs.y += theme_cache.v_separation; - - ofs.y += _get_item_height(i); - - if (p_over.y - control->get_position().y < ofs.y) { + ofs.y += i > 0 ? (float)theme_cache.v_separation * win_scale : (float)theme_cache.v_separation * win_scale * 0.5; + ofs.y += _get_item_height(i) * win_scale; + if (p_over.y - control->get_position().y * win_scale < ofs.y) { return i; } } @@ -341,15 +340,17 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { Rect2 this_rect(this_pos, get_size()); float scroll_offset = control->get_position().y; + float scaled_ofs_cache = items[p_over]._ofs_cache * get_content_scale_factor(); + float scaled_height_cache = items[p_over]._height_cache * get_content_scale_factor(); submenu_popup->reset_size(); // Shrink the popup size to its contents. Size2 submenu_size = submenu_popup->get_size(); Point2 submenu_pos; if (control->is_layout_rtl()) { - submenu_pos = this_pos + Point2(-submenu_size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2); + submenu_pos = this_pos + Point2(-submenu_size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); } else { - submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2); + submenu_pos = this_pos + Point2(this_rect.size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); } // Fix pos if going outside parent rect. @@ -386,8 +387,8 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Set autohide areas. Rect2 safe_area = this_rect; - safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; - safe_area.size.y = items[p_over]._height_cache + theme_cache.v_separation; + safe_area.position.y += scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; + safe_area.size.y = scaled_height_cache + theme_cache.v_separation; Viewport *vp = submenu_popup->get_embedder(); if (vp) { vp->subwindow_set_popup_safe_rect(submenu_popup, safe_area); @@ -400,11 +401,11 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Autohide area above the submenu item. submenu_pum->clear_autohide_areas(); - submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); + submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); // If there is an area below the submenu item, add an autohide area there. - if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) { - int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; + if (scaled_ofs_cache + scaled_height_cache + scroll_offset <= control->get_size().height) { + int from = scaled_ofs_cache + scaled_height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); } } @@ -576,6 +577,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { } item_clickable_area.size.width -= scroll_container->get_v_scroll_bar()->get_size().width; } + item_clickable_area.size = item_clickable_area.size * get_content_scale_factor(); Ref<InputEventMouseButton> b = p_event; @@ -2827,6 +2829,14 @@ void PopupMenu::popup(const Rect2i &p_bounds) { } else { moved = Vector2(); popup_time_msec = OS::get_singleton()->get_ticks_msec(); + if (!is_embedded()) { + float win_scale = get_parent_visible_window()->get_content_scale_factor(); + set_content_scale_factor(win_scale); + Size2 minsize = get_contents_minimum_size(); + minsize.height += 0.5 * win_scale; // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing. + set_min_size(minsize * win_scale); + set_size(Vector2(0, 0)); // Shrinkwraps to min size. + } Popup::popup(p_bounds); } } diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index f7097fffaf..832c1bcc8b 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -97,6 +97,10 @@ class PopupMenu : public Popup { static inline PropertyListHelper base_property_helper; PropertyListHelper property_helper; + // To make Item available. + friend class OptionButton; + friend class MenuButton; + RID global_menu; RID system_menu; NativeMenu::SystemMenus system_menu_id = NativeMenu::INVALID_MENU_ID; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 4a838fc7f6..e177bed20a 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -2149,12 +2149,12 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (b->get_button_index() == MouseButton::WHEEL_UP) { if (scroll_active) { - vscroll->set_value(vscroll->get_value() - vscroll->get_page() * b->get_factor() * 0.5 / 8); + vscroll->scroll(-vscroll->get_page() * b->get_factor() * 0.5 / 8); } } if (b->get_button_index() == MouseButton::WHEEL_DOWN) { if (scroll_active) { - vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8); + vscroll->scroll(vscroll->get_page() * b->get_factor() * 0.5 / 8); } } if (b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) { @@ -2169,7 +2169,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventPanGesture> pan_gesture = p_event; if (pan_gesture.is_valid()) { if (scroll_active) { - vscroll->set_value(vscroll->get_value() + vscroll->get_page() * pan_gesture->get_delta().y * 0.5 / 8); + vscroll->scroll(vscroll->get_page() * pan_gesture->get_delta().y * 0.5 / 8); } return; @@ -2182,27 +2182,27 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { bool handled = false; if (k->is_action("ui_page_up", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() - vscroll->get_page()); + vscroll->scroll(-vscroll->get_page()); handled = true; } if (k->is_action("ui_page_down", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() + vscroll->get_page()); + vscroll->scroll(vscroll->get_page()); handled = true; } if (k->is_action("ui_up", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() - theme_cache.normal_font->get_height(theme_cache.normal_font_size)); + vscroll->scroll(-theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } if (k->is_action("ui_down", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size)); + vscroll->scroll(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } if (k->is_action("ui_home", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(0); + vscroll->scroll_to(0); handled = true; } if (k->is_action("ui_end", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_max()); + vscroll->scroll_to(vscroll->get_max()); handled = true; } if (is_shortcut_keys_enabled()) { @@ -3212,33 +3212,6 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) queue_redraw(); } -void RichTextLabel::_remove_item(Item *p_item, const int p_line) { - int size = p_item->subitems.size(); - if (size == 0) { - p_item->parent->subitems.erase(p_item); - // If a newline was erased, all lines AFTER the newline need to be decremented. - if (p_item->type == ITEM_NEWLINE) { - current_frame->lines.remove_at(p_line); - if (p_line < (int)current_frame->lines.size() && current_frame->lines[p_line].from) { - for (List<Item *>::Element *E = current_frame->lines[p_line].from->E; E; E = E->next()) { - if (E->get()->line > p_line) { - E->get()->line--; - } - } - } - } - } else { - // First, remove all child items for the provided item. - while (p_item->subitems.size()) { - _remove_item(p_item->subitems.front()->get(), p_line); - } - // Then remove the provided item itself. - p_item->parent->subitems.erase(p_item); - } - items.free(p_item->rid); - memdelete(p_item); -} - Size2 RichTextLabel::_get_image_size(const Ref<Texture2D> &p_image, int p_width, int p_height, const Rect2 &p_region) { Size2 ret; if (p_width > 0) { @@ -3424,48 +3397,97 @@ void RichTextLabel::add_newline() { queue_redraw(); } +void RichTextLabel::_remove_frame(HashSet<Item *> &r_erase_list, ItemFrame *p_frame, int p_line, bool p_erase, int p_char_offset, int p_line_offset) { + Line &l = p_frame->lines[p_line]; + Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + if (!p_erase) { + l.char_offset -= p_char_offset; + } + + for (Item *it = l.from; it && it != it_to;) { + Item *next_it = _get_next_item(it); + it->line -= p_line_offset; + if (!p_erase) { + while (r_erase_list.has(it->parent)) { + it->E->erase(); + it->parent = it->parent->parent; + it->E = it->parent->subitems.push_back(it); + } + } + if (it->type == ITEM_TABLE) { + ItemTable *table = static_cast<ItemTable *>(it); + for (List<Item *>::Element *sub_it = table->subitems.front(); sub_it; sub_it = sub_it->next()) { + ERR_CONTINUE(sub_it->get()->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(sub_it->get()); + for (int i = 0; i < (int)frame->lines.size(); i++) { + _remove_frame(r_erase_list, frame, i, p_erase, p_char_offset, 0); + } + if (p_erase) { + r_erase_list.insert(frame); + } else { + frame->char_ofs -= p_char_offset; + } + } + } + if (p_erase) { + r_erase_list.insert(it); + } else { + it->char_ofs -= p_char_offset; + } + it = next_it; + } +} + bool RichTextLabel::remove_paragraph(const int p_paragraph) { _stop_thread(); MutexLock data_lock(data_mutex); - if (p_paragraph >= (int)current_frame->lines.size() || p_paragraph < 0) { + if (p_paragraph >= (int)main->lines.size() || p_paragraph < 0) { return false; } - // Remove all subitems with the same line as that provided. - Vector<List<Item *>::Element *> subitem_to_remove; - if (current_frame->lines[p_paragraph].from) { - for (List<Item *>::Element *E = current_frame->lines[p_paragraph].from->E; E; E = E->next()) { - if (E->get()->line == p_paragraph) { - subitem_to_remove.push_back(E); + if (main->lines.size() == 1) { + // Clear all. + main->_clear_children(); + current = main; + current_frame = main; + main->lines.clear(); + main->lines.resize(1); + + current_char_ofs = 0; + } else { + HashSet<Item *> erase_list; + Line &l = main->lines[p_paragraph]; + int off = l.char_count; + for (int i = p_paragraph; i < (int)main->lines.size(); i++) { + if (i == p_paragraph) { + _remove_frame(erase_list, main, i, true, off, 0); } else { - break; + _remove_frame(erase_list, main, i, false, off, 1); } } - } - - bool had_newline = false; - // Reverse for loop to remove items from the end first. - for (int i = subitem_to_remove.size() - 1; i >= 0; i--) { - List<Item *>::Element *subitem = subitem_to_remove[i]; - had_newline = had_newline || subitem->get()->type == ITEM_NEWLINE; - if (subitem->get() == current) { - pop(); + for (HashSet<Item *>::Iterator E = erase_list.begin(); E; ++E) { + Item *it = *E; + if (current_frame == it) { + current_frame = main; + } + if (current == it) { + current = main; + } + if (!erase_list.has(it->parent)) { + it->E->erase(); + } + items.free(it->rid); + it->subitems.clear(); + memdelete(it); } - _remove_item(subitem->get(), p_paragraph); + main->lines.remove_at(p_paragraph); + current_char_ofs -= off; } - if (!had_newline) { - current_frame->lines.remove_at(p_paragraph); - } - - if (current_frame->lines.is_empty()) { - current_frame->lines.resize(1); - } - - if (p_paragraph == 0 && current->subitems.size() > 0) { - main->lines[0].from = main; - } + selection.click_frame = nullptr; + selection.click_item = nullptr; + deselect(); main->first_invalid_line.store(MIN(main->first_invalid_line.load(), p_paragraph)); main->first_resized_line.store(MIN(main->first_resized_line.load(), p_paragraph)); @@ -5197,7 +5219,7 @@ void RichTextLabel::scroll_to_paragraph(int p_paragraph) { } int RichTextLabel::get_paragraph_count() const { - return current_frame->lines.size(); + return main->lines.size(); } int RichTextLabel::get_visible_paragraph_count() const { diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index f8dd9e663c..a993d922d2 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -485,7 +485,7 @@ private: _FORCE_INLINE_ float _update_scroll_exceeds(float p_total_height, float p_ctrl_height, float p_width, int p_idx, float p_old_scroll, float p_text_rect_height); void _add_item(Item *p_item, bool p_enter = false, bool p_ensure_newline = false); - void _remove_item(Item *p_item, const int p_line); + void _remove_frame(HashSet<Item *> &r_erase_list, ItemFrame *p_frame, int p_line, bool p_erase, int p_char_offset, int p_line_offset); void _texture_changed(RID p_item); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 1310cac2c7..b35c4e9308 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -46,9 +46,6 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> m = p_event; - if (!m.is_valid() || drag.active) { - emit_signal(SNAME("scrolling")); - } Ref<InputEventMouseButton> b = p_event; @@ -57,13 +54,13 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (b->get_button_index() == MouseButton::WHEEL_DOWN && b->is_pressed()) { double change = get_page() != 0.0 ? get_page() / 4.0 : (get_max() - get_min()) / 16.0; - set_value(get_value() + MAX(change, get_step())); + scroll(MAX(change, get_step())); accept_event(); } if (b->get_button_index() == MouseButton::WHEEL_UP && b->is_pressed()) { double change = get_page() != 0.0 ? get_page() / 4.0 : (get_max() - get_min()) / 16.0; - set_value(get_value() - MAX(change, get_step())); + scroll(-MAX(change, get_step())); accept_event(); } @@ -84,14 +81,14 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (ofs < decr_size) { decr_active = true; - set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + scroll(-(custom_step >= 0 ? custom_step : get_step())); queue_redraw(); return; } if (ofs > total - incr_size) { incr_active = true; - set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + scroll(custom_step >= 0 ? custom_step : get_step()); queue_redraw(); return; } @@ -110,7 +107,7 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { scrolling = true; set_physics_process_internal(true); } else { - set_value(target_scroll); + scroll_to(target_scroll); } return; } @@ -134,7 +131,7 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { scrolling = true; set_physics_process_internal(true); } else { - set_value(target_scroll); + scroll_to(target_scroll); } } @@ -158,7 +155,13 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { double diff = (ofs - drag.pos_at_click) / get_area_size(); + double prev_scroll = get_value(); + set_as_ratio(drag.value_at_click + diff); + + if (!Math::is_equal_approx(prev_scroll, get_value())) { + emit_signal(SNAME("scrolling")); + } } else { double ofs = orientation == VERTICAL ? m->get_position().y : m->get_position().x; Ref<Texture2D> decr = theme_cache.decrement_icon; @@ -192,32 +195,32 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (orientation != HORIZONTAL) { return; } - set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + scroll(-(custom_step >= 0 ? custom_step : get_step())); } else if (p_event->is_action("ui_right", true)) { if (orientation != HORIZONTAL) { return; } - set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + scroll(custom_step >= 0 ? custom_step : get_step()); } else if (p_event->is_action("ui_up", true)) { if (orientation != VERTICAL) { return; } - set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + scroll(-(custom_step >= 0 ? custom_step : get_step())); } else if (p_event->is_action("ui_down", true)) { if (orientation != VERTICAL) { return; } - set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + scroll(custom_step >= 0 ? custom_step : get_step()); } else if (p_event->is_action("ui_home", true)) { - set_value(get_min()); + scroll_to(get_min()); } else if (p_event->is_action("ui_end", true)) { - set_value(get_max()); + scroll_to(get_max()); } } } @@ -329,11 +332,11 @@ void ScrollBar::_notification(int p_what) { double vel = ((target / dist) * 500) * get_physics_process_delta_time(); if (Math::abs(vel) >= dist) { - set_value(target_scroll); + scroll_to(target_scroll); scrolling = false; set_physics_process_internal(false); } else { - set_value(get_value() + vel); + scroll(vel); } } else { scrolling = false; @@ -358,7 +361,7 @@ void ScrollBar::_notification(int p_what) { turnoff = true; } - set_value(pos.x); + scroll_to(pos.x); float sgn_x = drag_node_speed.x < 0 ? -1 : 1; float val_x = Math::abs(drag_node_speed.x); @@ -381,7 +384,7 @@ void ScrollBar::_notification(int p_what) { turnoff = true; } - set_value(pos.y); + scroll_to(pos.y); float sgn_y = drag_node_speed.y < 0 ? -1 : 1; float val_y = Math::abs(drag_node_speed.y); @@ -497,6 +500,18 @@ Size2 ScrollBar::get_minimum_size() const { return minsize; } +void ScrollBar::scroll(double p_amount) { + scroll_to(get_value() + p_amount); +} + +void ScrollBar::scroll_to(double p_position) { + double prev_scroll = get_value(); + set_value(p_position); + if (!Math::is_equal_approx(prev_scroll, get_value())) { + emit_signal(SNAME("scrolling")); + } +} + void ScrollBar::set_custom_step(float p_custom_step) { custom_step = p_custom_step; } @@ -561,11 +576,11 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) { Vector2 diff = drag_node_from + drag_node_accum; if (orientation == HORIZONTAL) { - set_value(diff.x); + scroll_to(diff.x); } if (orientation == VERTICAL) { - set_value(diff.y); + scroll_to(diff.y); } time_since_motion = 0; diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index deadbb53d6..ad88d826a2 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -111,6 +111,9 @@ protected: static void _bind_methods(); public: + void scroll(double p_amount); + void scroll_to(double p_position); + void set_custom_step(float p_custom_step); float get_custom_step() const; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 1447d2f002..4f49f60d70 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -114,19 +114,19 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == MouseButton::WHEEL_UP) { // By default, the vertical orientation takes precedence. This is an exception. if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { - h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); + h_scroll->scroll(-h_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } else if (v_scroll_enabled) { - v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); + v_scroll->scroll(-v_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } } if (mb->get_button_index() == MouseButton::WHEEL_DOWN) { if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { - h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); + h_scroll->scroll(h_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } else if (v_scroll_enabled) { - v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); + v_scroll->scroll(v_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } } @@ -135,19 +135,19 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == MouseButton::WHEEL_LEFT) { // By default, the horizontal orientation takes precedence. This is an exception. if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { - v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); + v_scroll->scroll(-v_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } else if (h_scroll_enabled) { - h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); + h_scroll->scroll(-h_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } } if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) { if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { - v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); + v_scroll->scroll(v_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } else if (h_scroll_enabled) { - h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); + h_scroll->scroll(h_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } } @@ -213,12 +213,12 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { } Vector2 diff = drag_from + drag_accum; if (h_scroll_enabled) { - h_scroll->set_value(diff.x); + h_scroll->scroll_to(diff.x); } else { drag_accum.x = 0; } if (v_scroll_enabled) { - v_scroll->set_value(diff.y); + v_scroll->scroll_to(diff.y); } else { drag_accum.y = 0; } @@ -235,10 +235,10 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventPanGesture> pan_gesture = p_gui_input; if (pan_gesture.is_valid()) { if (h_scroll_enabled) { - h_scroll->set_value(prev_h_scroll + h_scroll->get_page() * pan_gesture->get_delta().x / 8); + h_scroll->scroll(h_scroll->get_page() * pan_gesture->get_delta().x / 8); } if (v_scroll_enabled) { - v_scroll->set_value(prev_v_scroll + v_scroll->get_page() * pan_gesture->get_delta().y / 8); + v_scroll->scroll(v_scroll->get_page() * pan_gesture->get_delta().y / 8); } if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { @@ -391,10 +391,10 @@ void ScrollContainer::_notification(int p_what) { } if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) { - h_scroll->set_value(pos.x); + h_scroll->scroll_to(pos.x); } if (vertical_scroll_mode != SCROLL_MODE_DISABLED) { - v_scroll->set_value(pos.y); + v_scroll->scroll_to(pos.y); } float sgn_x = drag_speed.x < 0 ? -1 : 1; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index d20fef8164..0e130d60af 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -323,6 +323,19 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { } } +String TabBar::get_tooltip(const Point2 &p_pos) const { + int tab_idx = get_tab_idx_at_point(p_pos); + if (tab_idx < 0) { + return Control::get_tooltip(p_pos); + } + + if (tabs[tab_idx].tooltip.is_empty() && tabs[tab_idx].truncated) { + return tabs[tab_idx].text; + } + + return tabs[tab_idx].tooltip; +} + void TabBar::_shape(int p_tab) { tabs.write[p_tab].text_buf->clear(); tabs.write[p_tab].text_buf->set_width(-1); @@ -757,6 +770,16 @@ String TabBar::get_tab_title(int p_tab) const { return tabs[p_tab].text; } +void TabBar::set_tab_tooltip(int p_tab, const String &p_tooltip) { + ERR_FAIL_INDEX(p_tab, tabs.size()); + tabs.write[p_tab].tooltip = p_tooltip; +} + +String TabBar::get_tab_tooltip(int p_tab) const { + ERR_FAIL_INDEX_V(p_tab, tabs.size(), ""); + return tabs[p_tab].tooltip; +} + void TabBar::set_tab_text_direction(int p_tab, Control::TextDirection p_text_direction) { ERR_FAIL_INDEX(p_tab, tabs.size()); ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); @@ -998,7 +1021,8 @@ void TabBar::_update_cache(bool p_update_hover) { tabs.write[i].size_text = Math::ceil(tabs[i].text_buf->get_size().x); tabs.write[i].size_cache = get_tab_width(i); - if (max_width > 0 && tabs[i].size_cache > max_width) { + tabs.write[i].truncated = max_width > 0 && tabs[i].size_cache > max_width; + if (tabs[i].truncated) { int size_textless = tabs[i].size_cache - tabs[i].size_text; int mw = MAX(size_textless, max_width); @@ -1720,58 +1744,6 @@ bool TabBar::get_deselect_enabled() const { return deselect_enabled; } -bool TabBar::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { - int tab_index = components[0].trim_prefix("tab_").to_int(); - const String &property = components[1]; - if (property == "title") { - set_tab_title(tab_index, p_value); - return true; - } else if (property == "icon") { - set_tab_icon(tab_index, p_value); - return true; - } else if (property == "disabled") { - set_tab_disabled(tab_index, p_value); - return true; - } - } - return false; -} - -bool TabBar::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { - int tab_index = components[0].trim_prefix("tab_").to_int(); - const String &property = components[1]; - if (property == "title") { - r_ret = get_tab_title(tab_index); - return true; - } else if (property == "icon") { - r_ret = get_tab_icon(tab_index); - return true; - } else if (property == "disabled") { - r_ret = is_tab_disabled(tab_index); - return true; - } - } - return false; -} - -void TabBar::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < tabs.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("tab_%d/title", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("tab_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(get_tab_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("tab_%d/disabled", i)); - pi.usage &= ~(!is_tab_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tab_count", "count"), &TabBar::set_tab_count); ClassDB::bind_method(D_METHOD("get_tab_count"), &TabBar::get_tab_count); @@ -1782,6 +1754,8 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("select_next_available"), &TabBar::select_next_available); ClassDB::bind_method(D_METHOD("set_tab_title", "tab_idx", "title"), &TabBar::set_tab_title); ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &TabBar::get_tab_title); + ClassDB::bind_method(D_METHOD("set_tab_tooltip", "tab_idx", "tooltip"), &TabBar::set_tab_tooltip); + ClassDB::bind_method(D_METHOD("get_tab_tooltip", "tab_idx"), &TabBar::get_tab_tooltip); ClassDB::bind_method(D_METHOD("set_tab_text_direction", "tab_idx", "direction"), &TabBar::set_tab_text_direction); ClassDB::bind_method(D_METHOD("get_tab_text_direction", "tab_idx"), &TabBar::get_tab_text_direction); ClassDB::bind_method(D_METHOD("set_tab_language", "tab_idx", "language"), &TabBar::set_tab_language); @@ -1890,10 +1864,20 @@ void TabBar::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabBar, close_icon, "close"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, button_pressed_style, "button_pressed"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, button_hl_style, "button_highlight"); + + Tab defaults(true); + + base_property_helper.set_prefix("tab_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "title"), defaults.text, &TabBar::set_tab_title, &TabBar::get_tab_title); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "tooltip"), defaults.tooltip, &TabBar::set_tab_tooltip, &TabBar::get_tab_tooltip); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &TabBar::set_tab_icon, &TabBar::get_tab_icon); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &TabBar::set_tab_disabled, &TabBar::is_tab_disabled); } TabBar::TabBar() { set_size(Size2(get_size().width, get_minimum_size().height)); set_focus_mode(FOCUS_ALL); connect("mouse_exited", callable_mp(this, &TabBar::_on_mouse_exited)); + + property_helper.setup_for_instance(base_property_helper, this); } diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index 65a1d5bd4f..52f1da5ec8 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -32,6 +32,7 @@ #define TAB_BAR_H #include "scene/gui/control.h" +#include "scene/property_list_helper.h" #include "scene/resources/text_line.h" class TabBar : public Control { @@ -55,6 +56,7 @@ public: private: struct Tab { String text; + String tooltip; String language; Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED; @@ -65,6 +67,8 @@ private: bool disabled = false; bool hidden = false; + bool truncated = false; + Variant metadata; int ofs_cache = 0; int size_cache = 0; @@ -77,8 +81,13 @@ private: Tab() { text_buf.instantiate(); } + + Tab(bool p_dummy) {} }; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + int offset = 0; int max_drawn_tab = 0; int highlight_arrow = -1; @@ -162,10 +171,13 @@ private: protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual String get_tooltip(const Point2 &p_pos) const override; - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); } + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, tabs.size()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _notification(int p_what); static void _bind_methods(); @@ -184,6 +196,9 @@ public: void set_tab_title(int p_tab, const String &p_title); String get_tab_title(int p_tab) const; + void set_tab_tooltip(int p_tab, const String &p_tooltip); + String get_tab_tooltip(int p_tab) const; + void set_tab_text_direction(int p_tab, TextDirection p_text_direction); TextDirection get_tab_text_direction(int p_tab) const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 105f4484b2..05f44891f6 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -30,9 +30,6 @@ #include "tab_container.h" -#include "scene/gui/box_container.h" -#include "scene/gui/label.h" -#include "scene/gui/texture_rect.h" #include "scene/theme/theme_db.h" int TabContainer::_get_tab_height() const { @@ -403,6 +400,7 @@ void TabContainer::move_tab_from_tab_container(TabContainer *p_from, int p_from_ // Get the tab properties before they get erased by the child removal. String tab_title = p_from->get_tab_title(p_from_index); + String tab_tooltip = p_from->get_tab_tooltip(p_from_index); Ref<Texture2D> tab_icon = p_from->get_tab_icon(p_from_index); Ref<Texture2D> tab_button_icon = p_from->get_tab_button_icon(p_from_index); bool tab_disabled = p_from->is_tab_disabled(p_from_index); @@ -420,6 +418,7 @@ void TabContainer::move_tab_from_tab_container(TabContainer *p_from, int p_from_ move_child(moving_tabc, get_tab_control(p_to_index)->get_index(false)); set_tab_title(p_to_index, tab_title); + set_tab_tooltip(p_to_index, tab_tooltip); set_tab_icon(p_to_index, tab_icon); set_tab_button_icon(p_to_index, tab_button_icon); set_tab_disabled(p_to_index, tab_disabled); @@ -763,16 +762,22 @@ void TabContainer::set_tab_title(int p_tab, const String &p_title) { child->set_meta("_tab_name", p_title); } - _update_margins(); - if (!get_clip_tabs()) { - update_minimum_size(); - } + _repaint(); + queue_redraw(); } String TabContainer::get_tab_title(int p_tab) const { return tab_bar->get_tab_title(p_tab); } +void TabContainer::set_tab_tooltip(int p_tab, const String &p_tooltip) { + tab_bar->set_tab_tooltip(p_tab, p_tooltip); +} + +String TabContainer::get_tab_tooltip(int p_tab) const { + return tab_bar->get_tab_tooltip(p_tab); +} + void TabContainer::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { if (tab_bar->get_tab_icon(p_tab) == p_icon) { return; @@ -782,6 +787,7 @@ void TabContainer::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { _update_margins(); _repaint(); + queue_redraw(); } Ref<Texture2D> TabContainer::get_tab_icon(int p_tab) const { @@ -980,6 +986,8 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("is_all_tabs_in_front"), &TabContainer::is_all_tabs_in_front); ClassDB::bind_method(D_METHOD("set_tab_title", "tab_idx", "title"), &TabContainer::set_tab_title); ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &TabContainer::get_tab_title); + ClassDB::bind_method(D_METHOD("set_tab_tooltip", "tab_idx", "tooltip"), &TabContainer::set_tab_tooltip); + ClassDB::bind_method(D_METHOD("get_tab_tooltip", "tab_idx"), &TabContainer::get_tab_tooltip); ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabContainer::set_tab_icon); ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 8645a6d14e..c11d9824e7 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -155,6 +155,9 @@ public: void set_tab_title(int p_tab, const String &p_title); String get_tab_title(int p_tab) const; + void set_tab_tooltip(int p_tab, const String &p_tooltip); + String get_tab_tooltip(int p_tab) const; + void set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon); Ref<Texture2D> get_tab_icon(int p_tab) const; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 6bb745ac57..0bb77a92f2 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1187,7 +1187,7 @@ void TextEdit::_notification(int p_what) { } if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word - if (is_ascii_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') { + if (is_ascii_alphabet_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') { int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); int lookup_symbol_word_len = lookup_symbol_word.length(); while (lookup_symbol_word_col != -1) { diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 1daf86fe0f..982473deee 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -35,8 +35,6 @@ #include "core/math/math_funcs.h" #include "core/os/keyboard.h" #include "core/os/os.h" -#include "core/string/print_string.h" -#include "core/string/translation.h" #include "scene/gui/box_container.h" #include "scene/gui/text_edit.h" #include "scene/main/window.h" @@ -2459,7 +2457,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (rtl) { button_ofs.x = get_size().width - button_ofs.x - button_texture->get_width(); } - p_item->cells.write[i].buttons.write[j].rect = Rect2i(button_ofs, button_size); button_texture->draw(ci, button_ofs, p_item->cells[i].buttons[j].disabled ? Color(1, 1, 1, 0.5) : p_item->cells[i].buttons[j].color); item_width_with_buttons -= button_size.width + theme_cache.button_margin; } @@ -3995,12 +3992,14 @@ bool Tree::edit_selected(bool p_force_edit) { return false; } + float popup_scale = popup_editor->is_embedded() ? 1.0 : popup_editor->get_parent_visible_window()->get_content_scale_factor(); Rect2 rect; if (select_mode == SELECT_ROW) { rect = s->get_meta("__focus_col_" + itos(selected_col)); } else { rect = s->get_meta("__focus_rect"); } + rect.position *= popup_scale; popup_edited_item = s; popup_edited_item_col = col; @@ -4043,7 +4042,7 @@ bool Tree::edit_selected(bool p_force_edit) { popup_rect.size = rect.size; // Account for icon. - Size2 icon_size = _get_cell_icon_size(c); + Size2 icon_size = _get_cell_icon_size(c) * popup_scale; popup_rect.position.x += icon_size.x; popup_rect.size.x -= icon_size.x; @@ -4070,7 +4069,10 @@ bool Tree::edit_selected(bool p_force_edit) { } popup_editor->set_position(popup_rect.position); - popup_editor->set_size(popup_rect.size); + popup_editor->set_size(popup_rect.size * popup_scale); + if (!popup_editor->is_embedded()) { + popup_editor->set_content_scale_factor(popup_scale); + } popup_editor->popup(); popup_editor->child_controls_changed(); @@ -4086,7 +4088,10 @@ bool Tree::edit_selected(bool p_force_edit) { text_editor->show(); popup_editor->set_position(get_screen_position() + rect.position); - popup_editor->set_size(rect.size); + popup_editor->set_size(rect.size * popup_scale); + if (!popup_editor->is_embedded()) { + popup_editor->set_content_scale_factor(popup_scale); + } popup_editor->popup(); popup_editor->child_controls_changed(); @@ -4339,7 +4344,7 @@ void Tree::_notification(int p_what) { } default: { - text_pos.x += sb->get_offset().x + (tbrect.size.width - columns[i].text_buf->get_size().x) / 2; + text_pos.x += (tbrect.size.width - columns[i].text_buf->get_size().x) / 2; break; } } @@ -4721,7 +4726,8 @@ int Tree::get_column_minimum_width(int p_column) const { // Check if the visible title of the column is wider. if (show_column_titles) { - min_width = MAX(theme_cache.font->get_string_size(columns[p_column].xl_title, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT), min_width); + const float padding = theme_cache.title_button->get_margin(SIDE_LEFT) + theme_cache.title_button->get_margin(SIDE_RIGHT); + min_width = MAX(theme_cache.font->get_string_size(columns[p_column].xl_title, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + padding, min_width); } if (!columns[p_column].clip_content) { @@ -5260,6 +5266,86 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_ return nullptr; } +// When on a button, r_index is valid. +// When on an item, both r_item and r_column are valid. +// Otherwise, all output arguments are invalid. +void Tree::_find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index) const { + r_item = nullptr; + r_column = -1; + r_index = -1; + + if (!root) { + return; + } + + Point2 pos = p_pos - theme_cache.panel_style->get_offset(); + pos.y -= _get_title_button_height(); + if (pos.y < 0) { + return; + } + + if (cache.rtl) { + pos.x = get_size().width - pos.x; + } + pos += theme_cache.offset; // Scrolling. + + int col, h, section; + TreeItem *it = _find_item_at_pos(root, pos, col, h, section); + if (!it) { + return; + } + + r_item = it; + r_column = col; + + const TreeItem::Cell &c = it->cells[col]; + if (c.buttons.is_empty()) { + return; + } + + int x_limit = get_size().width - theme_cache.panel_style->get_minimum_size().width + theme_cache.offset.x; + if (v_scroll->is_visible_in_tree()) { + x_limit -= v_scroll->get_minimum_size().width; + } + + for (int i = 0; i < col; i++) { + const int col_w = get_column_width(i) + theme_cache.h_separation; + pos.x -= col_w; + x_limit -= col_w; + } + + int x_check; + if (cache.rtl) { + x_check = get_column_width(col); + } else { + // Right edge of the buttons area, relative to the start of the column. + int buttons_area_min = 0; + if (col == 0) { + // Content of column 0 should take indentation into account. + for (TreeItem *current = it; current && (current != root || !hide_root); current = current->parent) { + buttons_area_min += theme_cache.item_margin; + } + } + for (int i = c.buttons.size() - 1; i >= 0; i--) { + Ref<Texture2D> b = c.buttons[i].texture; + buttons_area_min += b->get_size().width + theme_cache.button_pressed->get_minimum_size().width + theme_cache.button_margin; + } + + x_check = MAX(buttons_area_min, MIN(get_column_width(col), x_limit)); + } + + for (int i = c.buttons.size() - 1; i >= 0; i--) { + Ref<Texture2D> b = c.buttons[i].texture; + Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); + if (pos.x > x_check - size.width) { + x_limit -= theme_cache.item_margin; + r_index = i; + return; + } + x_check -= size.width + theme_cache.button_margin; + } +} + int Tree::get_column_at_position(const Point2 &p_pos) const { if (root) { Point2 pos = p_pos; @@ -5351,84 +5437,37 @@ TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const { } int Tree::get_button_id_at_position(const Point2 &p_pos) const { - if (root) { - Point2 pos = p_pos; - pos -= theme_cache.panel_style->get_offset(); - pos.y -= _get_title_button_height(); - if (pos.y < 0) { - return -1; - } + TreeItem *it; + int col, index; + _find_button_at_pos(p_pos, it, col, index); - if (h_scroll->is_visible_in_tree()) { - pos.x += h_scroll->get_value(); - } - if (v_scroll->is_visible_in_tree()) { - pos.y += v_scroll->get_value(); - } - - int col, h, section; - TreeItem *it = _find_item_at_pos(root, pos, col, h, section); - - if (it) { - const TreeItem::Cell &c = it->cells[col]; - int col_width = get_column_width(col); - - for (int i = 0; i < col; i++) { - pos.x -= get_column_width(i); - } - - for (int j = c.buttons.size() - 1; j >= 0; j--) { - Ref<Texture2D> b = c.buttons[j].texture; - Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); - if (pos.x > col_width - size.width) { - return c.buttons[j].id; - } - col_width -= size.width; - } - } + if (index == -1) { + return -1; } - - return -1; + return it->cells[col].buttons[index].id; } String Tree::get_tooltip(const Point2 &p_pos) const { - if (root) { - Point2 pos = p_pos; - pos -= theme_cache.panel_style->get_offset(); - pos.y -= _get_title_button_height(); - if (pos.y < 0) { - return Control::get_tooltip(p_pos); - } + Point2 pos = p_pos - theme_cache.panel_style->get_offset(); + pos.y -= _get_title_button_height(); + if (pos.y < 0) { + return Control::get_tooltip(p_pos); + } - Point2 button_pos = pos; - if (h_scroll->is_visible_in_tree()) { - pos.x += h_scroll->get_value(); - } - if (v_scroll->is_visible_in_tree()) { - pos.y += v_scroll->get_value(); - } + TreeItem *it; + int col, index; + _find_button_at_pos(p_pos, it, col, index); - int col, h, section; - TreeItem *it = _find_item_at_pos(root, pos, col, h, section); + if (index != -1) { + return it->cells[col].buttons[index].tooltip; + } - if (it) { - const TreeItem::Cell &c = it->cells[col]; - for (int j = c.buttons.size() - 1; j >= 0; j--) { - if (c.buttons[j].rect.has_point(button_pos)) { - String tooltip = c.buttons[j].tooltip; - if (!tooltip.is_empty()) { - return tooltip; - } - } - } - String ret; - if (it->get_tooltip_text(col) == "") { - ret = it->get_text(col); - } else { - ret = it->get_tooltip_text(col); - } - return ret; + if (it) { + const String item_tooltip = it->get_tooltip_text(col); + if (item_tooltip.is_empty()) { + return it->get_text(col); } + return item_tooltip; } return Control::get_tooltip(p_pos); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 21696d8216..311055a2f8 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -108,7 +108,6 @@ private: Ref<Texture2D> texture; Color color = Color(1, 1, 1, 1); String tooltip; - Rect2i rect; }; Vector<Button> buttons; @@ -646,6 +645,8 @@ private: TreeItem *_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &h, int §ion) const; + void _find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index) const; + /* float drag_speed; float drag_accum; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 17d7fec230..2d30ea345d 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1461,6 +1461,8 @@ void Viewport::_gui_show_tooltip() { panel->set_flag(Window::FLAG_NO_FOCUS, true); panel->set_flag(Window::FLAG_POPUP, false); panel->set_flag(Window::FLAG_MOUSE_PASSTHROUGH, true); + // A non-embedded tooltip window will only be transparent if per_pixel_transparency is allowed in the main Viewport. + panel->set_flag(Window::FLAG_TRANSPARENT, true); panel->set_wrap_controls(true); panel->add_child(base_tooltip); panel->gui_parent = this; @@ -1469,17 +1471,25 @@ void Viewport::_gui_show_tooltip() { tooltip_owner->add_child(gui.tooltip_popup); + Window *window = Object::cast_to<Window>(gui.tooltip_popup->get_embedder()); + if (!window) { // Not embedded. + window = gui.tooltip_popup->get_parent_visible_window(); + } + float win_scale = window->content_scale_factor; Point2 tooltip_offset = GLOBAL_GET("display/mouse_cursor/tooltip_position_offset"); + if (!gui.tooltip_popup->is_embedded()) { + tooltip_offset *= win_scale; + } Rect2 r(gui.tooltip_pos + tooltip_offset, gui.tooltip_popup->get_contents_minimum_size()); - r.size = r.size.min(panel->get_max_size()); - - Window *window = gui.tooltip_popup->get_parent_visible_window(); Rect2i vr; if (gui.tooltip_popup->is_embedded()) { vr = gui.tooltip_popup->get_embedder()->get_visible_rect(); } else { + panel->content_scale_factor = win_scale; + r.size *= win_scale; vr = window->get_usable_parent_rect(); } + r.size = r.size.min(panel->get_max_size()); if (r.size.x + r.position.x > vr.size.x + vr.position.x) { // Place it in the opposite direction. If it fails, just hug the border. diff --git a/scene/property_list_helper.cpp b/scene/property_list_helper.cpp index d9a80011b0..b666e4c52d 100644 --- a/scene/property_list_helper.cpp +++ b/scene/property_list_helper.cpp @@ -31,7 +31,7 @@ #include "property_list_helper.h" const PropertyListHelper::Property *PropertyListHelper::_get_property(const String &p_property, int *r_index) const { - const Vector<String> components = p_property.split("/", true, 2); + const Vector<String> components = p_property.rsplit("/", true, 1); if (components.size() < 2 || !components[0].begins_with(prefix)) { return nullptr; } @@ -48,36 +48,73 @@ const PropertyListHelper::Property *PropertyListHelper::_get_property(const Stri } void PropertyListHelper::_call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const { + DEV_ASSERT(p_setter); Variant args[] = { p_index, p_value }; const Variant *argptrs[] = { &args[0], &args[1] }; Callable::CallError ce; p_setter->call(object, argptrs, 2, ce); } -Variant PropertyListHelper::_call_getter(const MethodBind *p_getter, int p_index) const { +Variant PropertyListHelper::_call_getter(const Property *p_property, int p_index) const { + if (!p_property->getter) { + return object->get(prefix + itos(p_index) + "/" + p_property->info.name); + } + Callable::CallError ce; Variant args[] = { p_index }; const Variant *argptrs[] = { &args[0] }; - return p_getter->call(object, argptrs, 1, ce); + return p_property->getter->call(object, argptrs, 1, ce); } void PropertyListHelper::set_prefix(const String &p_prefix) { prefix = p_prefix; } +void PropertyListHelper::register_property(const PropertyInfo &p_info, const Variant &p_default) { + Property property; + property.info = p_info; + property.default_value = p_default; + + property_list[p_info.name] = property; +} + +bool PropertyListHelper::is_initialized() const { + return !property_list.is_empty(); +} + void PropertyListHelper::setup_for_instance(const PropertyListHelper &p_base, Object *p_object) { prefix = p_base.prefix; property_list = p_base.property_list; object = p_object; } +bool PropertyListHelper::is_property_valid(const String &p_property, int *r_index) const { + const Vector<String> components = p_property.rsplit("/", true, 1); + if (components.size() < 2 || !components[0].begins_with(prefix)) { + return false; + } + + { + const String index_string = components[0].trim_prefix(prefix); + if (!index_string.is_valid_int()) { + return false; + } + + if (r_index) { + *r_index = index_string.to_int(); + } + } + + return property_list.has(components[1]); +} + void PropertyListHelper::get_property_list(List<PropertyInfo> *p_list, int p_count) const { for (int i = 0; i < p_count; i++) { for (const KeyValue<String, Property> &E : property_list) { const Property &property = E.value; PropertyInfo info = property.info; - if (_call_getter(property.getter, i) == property.default_value) { + if (_call_getter(&property, i) == property.default_value) { info.usage &= (~PROPERTY_USAGE_STORAGE); } @@ -92,7 +129,7 @@ bool PropertyListHelper::property_get_value(const String &p_property, Variant &r const Property *property = _get_property(p_property, &index); if (property) { - r_ret = _call_getter(property->getter, index); + r_ret = _call_getter(property, index); return true; } return false; @@ -110,8 +147,7 @@ bool PropertyListHelper::property_set_value(const String &p_property, const Vari } bool PropertyListHelper::property_can_revert(const String &p_property) const { - int index; - return _get_property(p_property, &index) != nullptr; + return is_property_valid(p_property); } bool PropertyListHelper::property_get_revert(const String &p_property, Variant &r_value) const { @@ -129,8 +165,10 @@ PropertyListHelper::~PropertyListHelper() { // No object = it's the main helper. Do a cleanup. if (!object) { for (const KeyValue<String, Property> &E : property_list) { - memdelete(E.value.setter); - memdelete(E.value.getter); + if (E.value.setter) { + memdelete(E.value.setter); + memdelete(E.value.getter); + } } } } diff --git a/scene/property_list_helper.h b/scene/property_list_helper.h index 6c1ad21a05..eac6b03d47 100644 --- a/scene/property_list_helper.h +++ b/scene/property_list_helper.h @@ -48,10 +48,12 @@ class PropertyListHelper { const Property *_get_property(const String &p_property, int *r_index) const; void _call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const; - Variant _call_getter(const MethodBind *p_getter, int p_index) const; + Variant _call_getter(const Property *p_property, int p_index) const; public: void set_prefix(const String &p_prefix); + // Register property without setter/getter. Only use when you don't need PropertyListHelper for _set/_get logic. + void register_property(const PropertyInfo &p_info, const Variant &p_default); template <typename S, typename G> void register_property(const PropertyInfo &p_info, const Variant &p_default, S p_setter, G p_getter) { @@ -64,7 +66,9 @@ public: property_list[p_info.name] = property; } + bool is_initialized() const; void setup_for_instance(const PropertyListHelper &p_base, Object *p_object); + bool is_property_valid(const String &p_property, int *r_index = nullptr) const; void get_property_list(List<PropertyInfo> *p_list, int p_count) const; bool property_get_value(const String &p_property, Variant &r_ret) const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 1c8833494d..6fac096c93 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -545,7 +545,7 @@ void register_scene_types() { GDREGISTER_CLASS(Camera3D); GDREGISTER_CLASS(AudioListener3D); GDREGISTER_CLASS(XRCamera3D); - GDREGISTER_ABSTRACT_CLASS(XRNode3D); + GDREGISTER_CLASS(XRNode3D); GDREGISTER_CLASS(XRController3D); GDREGISTER_CLASS(XRAnchor3D); GDREGISTER_CLASS(XROrigin3D); @@ -657,6 +657,9 @@ void register_scene_types() { GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeConstant); GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeVectorBase); GDREGISTER_CLASS(VisualShaderNodeFrame); +#ifndef DISABLE_DEPRECATED + GDREGISTER_CLASS(VisualShaderNodeComment); // Deprecated, just for compatibility. +#endif GDREGISTER_CLASS(VisualShaderNodeFloatConstant); GDREGISTER_CLASS(VisualShaderNodeIntConstant); GDREGISTER_CLASS(VisualShaderNodeUIntConstant); diff --git a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp index d613b498a1..aee743ccf2 100644 --- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp +++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp @@ -97,6 +97,24 @@ TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_obstruction_ return typed_array_obstruction_outlines; } +void NavigationMeshSourceGeometryData2D::append_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines) { + RWLockWrite write_lock(geometry_rwlock); + int traversable_outlines_size = traversable_outlines.size(); + traversable_outlines.resize(traversable_outlines_size + p_traversable_outlines.size()); + for (int i = traversable_outlines_size; i < p_traversable_outlines.size(); i++) { + traversable_outlines.write[i] = p_traversable_outlines[i]; + } +} + +void NavigationMeshSourceGeometryData2D::append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines) { + RWLockWrite write_lock(geometry_rwlock); + int obstruction_outlines_size = obstruction_outlines.size(); + obstruction_outlines.resize(obstruction_outlines_size + p_obstruction_outlines.size()); + for (int i = obstruction_outlines_size; i < p_obstruction_outlines.size(); i++) { + obstruction_outlines.write[i] = p_obstruction_outlines[i]; + } +} + void NavigationMeshSourceGeometryData2D::add_traversable_outline(const PackedVector2Array &p_shape_outline) { if (p_shape_outline.size() > 1) { Vector<Vector2> traversable_outline; @@ -240,6 +258,9 @@ void NavigationMeshSourceGeometryData2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::set_obstruction_outlines); ClassDB::bind_method(D_METHOD("get_obstruction_outlines"), &NavigationMeshSourceGeometryData2D::get_obstruction_outlines); + ClassDB::bind_method(D_METHOD("append_traversable_outlines", "traversable_outlines"), &NavigationMeshSourceGeometryData2D::append_traversable_outlines); + ClassDB::bind_method(D_METHOD("append_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::append_obstruction_outlines); + ClassDB::bind_method(D_METHOD("add_traversable_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_traversable_outline); ClassDB::bind_method(D_METHOD("add_obstruction_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_obstruction_outline); diff --git a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h index 11fc5d3850..aaa02ab40e 100644 --- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h +++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h @@ -82,6 +82,9 @@ public: void set_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines); TypedArray<Vector<Vector2>> get_obstruction_outlines() const; + void append_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines); + void append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines); + void add_traversable_outline(const PackedVector2Array &p_shape_outline); void add_obstruction_outline(const PackedVector2Array &p_shape_outline); diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d.cpp index 4e7563fdc3..5450f544c3 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d.cpp @@ -233,6 +233,11 @@ void SkeletonModification2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process,physics_process"), "set_execution_mode", "get_execution_mode"); } +void SkeletonModification2D::reset_state() { + stack = nullptr; + is_setup = false; +} + SkeletonModification2D::SkeletonModification2D() { stack = nullptr; is_setup = false; diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d.h b/scene/resources/2d/skeleton/skeleton_modification_2d.h index 413b860a99..6a6f1bb39b 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d.h +++ b/scene/resources/2d/skeleton/skeleton_modification_2d.h @@ -57,6 +57,8 @@ protected: bool _print_execution_error(bool p_condition, String p_message); + virtual void reset_state() override; + GDVIRTUAL1(_execute, double) GDVIRTUAL1(_setup_modification, Ref<SkeletonModificationStack2D>) GDVIRTUAL0(_draw_editor_gizmo) diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp index 1ad8d0eccc..051c4eabc0 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp @@ -266,7 +266,9 @@ void SkeletonModification2DCCDIK::_draw_editor_gizmo() { void SkeletonModification2DCCDIK::update_target_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + } return; } @@ -287,7 +289,9 @@ void SkeletonModification2DCCDIK::update_target_cache() { void SkeletonModification2DCCDIK::update_tip_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!"); + } return; } @@ -309,7 +313,9 @@ void SkeletonModification2DCCDIK::update_tip_cache() { void SkeletonModification2DCCDIK::ccdik_joint_update_bone2d_cache(int p_joint_idx) { ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update CCDIK Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update CCDIK Bone2D cache: modification is not properly setup!"); + } return; } @@ -390,7 +396,6 @@ void SkeletonModification2DCCDIK::set_ccdik_joint_bone_index(int p_joint_idx, in ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } } else { - WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification..."); ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp index dd1c4a91d5..16a6166878 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp @@ -289,13 +289,21 @@ void SkeletonModification2DFABRIK::_setup_modification(SkeletonModificationStack if (stack != nullptr) { is_setup = true; + + if (stack->skeleton) { + for (int i = 0; i < fabrik_data_chain.size(); i++) { + fabrik_joint_update_bone2d_cache(i); + } + } update_target_cache(); } } void SkeletonModification2DFABRIK::update_target_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + } return; } @@ -317,7 +325,9 @@ void SkeletonModification2DFABRIK::update_target_cache() { void SkeletonModification2DFABRIK::fabrik_joint_update_bone2d_cache(int p_joint_idx) { ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!"); + } return; } @@ -389,7 +399,6 @@ void SkeletonModification2DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx, fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } } else { - WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification..."); fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp index 2ace9577e4..b7200b49c4 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp @@ -254,6 +254,8 @@ void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack Bone2D *bone2d_node = stack->skeleton->get_bone(bone_idx); jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_position(); } + + jiggle_joint_update_bone2d_cache(i); } } @@ -263,7 +265,9 @@ void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack void SkeletonModification2DJiggle::update_target_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + } return; } @@ -285,7 +289,9 @@ void SkeletonModification2DJiggle::update_target_cache() { void SkeletonModification2DJiggle::jiggle_joint_update_bone2d_cache(int p_joint_idx) { ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!"); + } return; } @@ -425,7 +431,6 @@ void SkeletonModification2DJiggle::set_jiggle_joint_bone_index(int p_joint_idx, jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } } else { - WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification..."); jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp index 8f6f6bc4ae..cd4ca8e090 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp @@ -200,7 +200,9 @@ void SkeletonModification2DLookAt::_draw_editor_gizmo() { void SkeletonModification2DLookAt::update_bone2d_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!"); + } return; } @@ -256,7 +258,6 @@ void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) { bone_idx = p_bone_idx; } } else { - WARN_PRINT("Cannot verify the bone index for this modification..."); bone_idx = p_bone_idx; } @@ -265,7 +266,9 @@ void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) { void SkeletonModification2DLookAt::update_target_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + } return; } diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp index 61e5aed150..aa8d7d0b3b 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp @@ -153,7 +153,7 @@ void SkeletonModification2DPhysicalBones::_setup_modification(SkeletonModificati void SkeletonModification2DPhysicalBones::_physical_bone_update_cache(int p_joint_idx) { ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Cannot update PhysicalBone2D cache: joint index out of range!"); if (!is_setup || !stack) { - if (!stack) { + if (is_setup) { ERR_PRINT_ONCE("Cannot update PhysicalBone2D cache: modification is not properly setup!"); } return; diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp index c3366d5c36..41e4ea828e 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp @@ -250,7 +250,9 @@ void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() { void SkeletonModification2DTwoBoneIK::update_target_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + } return; } @@ -271,7 +273,9 @@ void SkeletonModification2DTwoBoneIK::update_target_cache() { void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!"); + } return; } @@ -299,7 +303,9 @@ void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() { void SkeletonModification2DTwoBoneIK::update_joint_two_bone2d_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!"); + } return; } @@ -400,7 +406,6 @@ void SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx(int p_bone_idx) { joint_one_bone_idx = p_bone_idx; } } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one..."); joint_one_bone_idx = p_bone_idx; } @@ -425,7 +430,6 @@ void SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx(int p_bone_idx) { joint_two_bone_idx = p_bone_idx; } } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two..."); joint_two_bone_idx = p_bone_idx; } diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp index 04b00b6bdb..57cc4ad602 100644 --- a/scene/resources/2d/tile_set.cpp +++ b/scene/resources/2d/tile_set.cpp @@ -5591,6 +5591,11 @@ Ref<ImageTexture> TileSetAtlasSource::_create_padded_image_texture(const Ref<Tex ret.instantiate(); return ret; } + if (src_image->is_compressed()) { + src_image = src_image->duplicate(); + Error err = src_image->decompress(); + ERR_FAIL_COND_V_MSG(err != OK, Ref<ImageTexture>(), "Unable to decompress image."); + } Size2 size = get_atlas_grid_size() * (texture_region_size + Vector2i(2, 2)); Ref<Image> image = Image::create_empty(size.x, size.y, false, src_image->get_format()); diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp index 952e99608d..375efcb227 100644 --- a/scene/resources/3d/importer_mesh.cpp +++ b/scene/resources/3d/importer_mesh.cpp @@ -1139,7 +1139,7 @@ Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, s.material = get_surface_material(i); s.name = get_surface_name(i); - SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format); + SurfaceTool::create_vertex_array_from_arrays(arrays, s.vertices, &s.format); PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX]; int vc = rvertices.size(); diff --git a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp index 39a17946fa..3f3f8b44fd 100644 --- a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp +++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp @@ -35,9 +35,24 @@ void NavigationMeshSourceGeometryData3D::set_vertices(const Vector<float> &p_ver } void NavigationMeshSourceGeometryData3D::set_indices(const Vector<int> &p_indices) { + ERR_FAIL_COND(vertices.size() < p_indices.size()); indices = p_indices; } +void NavigationMeshSourceGeometryData3D::append_arrays(const Vector<float> &p_vertices, const Vector<int> &p_indices) { + RWLockWrite write_lock(geometry_rwlock); + + const int64_t number_of_vertices_before_merge = vertices.size(); + const int64_t number_of_indices_before_merge = indices.size(); + + vertices.append_array(p_vertices); + indices.append_array(p_indices); + + for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) { + indices.set(i, indices[i] + number_of_vertices_before_merge / 3); + } +} + void NavigationMeshSourceGeometryData3D::clear() { vertices.clear(); indices.clear(); @@ -158,6 +173,15 @@ void NavigationMeshSourceGeometryData3D::_add_faces(const PackedVector3Array &p_ void NavigationMeshSourceGeometryData3D::add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform) { ERR_FAIL_COND(!p_mesh.is_valid()); + +#ifdef DEBUG_ENABLED + if (!Engine::get_singleton()->is_editor_hint()) { + WARN_PRINT_ONCE("Source geometry parsing for navigation mesh baking had to parse RenderingServer meshes at runtime.\n\ + This poses a significant performance issues as visual meshes store geometry data on the GPU and transferring this data back to the CPU blocks the rendering.\n\ + For runtime (re)baking navigation meshes use and parse collision shapes as source geometry or create geometry data procedurally in scripts."); + } +#endif + _add_mesh(p_mesh, root_node_transform * p_xform); } @@ -174,14 +198,7 @@ void NavigationMeshSourceGeometryData3D::add_faces(const PackedVector3Array &p_f void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry) { ERR_FAIL_NULL(p_other_geometry); - // No need to worry about `root_node_transform` here as the vertices are already xformed. - const int64_t number_of_vertices_before_merge = vertices.size(); - const int64_t number_of_indices_before_merge = indices.size(); - vertices.append_array(p_other_geometry->vertices); - indices.append_array(p_other_geometry->indices); - for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) { - indices.set(i, indices[i] + number_of_vertices_before_merge / 3); - } + append_arrays(p_other_geometry->vertices, p_other_geometry->indices); if (p_other_geometry->_projected_obstructions.size() > 0) { RWLockWrite write_lock(geometry_rwlock); @@ -306,6 +323,8 @@ void NavigationMeshSourceGeometryData3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_indices", "indices"), &NavigationMeshSourceGeometryData3D::set_indices); ClassDB::bind_method(D_METHOD("get_indices"), &NavigationMeshSourceGeometryData3D::get_indices); + ClassDB::bind_method(D_METHOD("append_arrays", "vertices", "indices"), &NavigationMeshSourceGeometryData3D::append_arrays); + ClassDB::bind_method(D_METHOD("clear"), &NavigationMeshSourceGeometryData3D::clear); ClassDB::bind_method(D_METHOD("has_data"), &NavigationMeshSourceGeometryData3D::has_data); diff --git a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h index 79e2f3740d..6c1ca760ea 100644 --- a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h +++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h @@ -80,6 +80,8 @@ public: void set_indices(const Vector<int> &p_indices); const Vector<int> &get_indices() const { return indices; } + void append_arrays(const Vector<float> &p_vertices, const Vector<int> &p_indices); + bool has_data() { return vertices.size() && indices.size(); }; void clear(); void clear_projected_obstructions(); diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index f766d1d2c7..73f3009fd1 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -2086,7 +2086,7 @@ Error ArrayMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, flo Array arrays = surface_get_arrays(i); s.material = surface_get_material(i); - SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format); + SurfaceTool::create_vertex_array_from_arrays(arrays, s.vertices, &s.format); PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX]; int vc = rvertices.size(); diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index 30b90841e3..685625ab72 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -741,26 +741,33 @@ void ParticleProcessMaterial::_update_shader() { code += "vec3 get_random_direction_from_spread(inout uint alt_seed, float spread_angle){\n"; code += " float pi = 3.14159;\n"; code += " float degree_to_rad = pi / 180.0;\n"; - code += " vec3 velocity = vec3(0.);\n"; code += " float spread_rad = spread_angle * degree_to_rad;\n"; - code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; - code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n"; - code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n"; - code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n"; - code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n"; - code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n"; - code += " vec3 direction_nrm = length(direction) > 0.0 ? normalize(direction) : vec3(0.0, 0.0, 1.0);\n"; - code += " // rotate spread to direction\n"; - code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n"; - code += " if (length(binormal) < 0.0001) {\n"; - code += " // direction is parallel to Y. Choose Z as the binormal.\n"; - code += " binormal = vec3(0.0, 0.0, 1.0);\n"; - code += " }\n"; - 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 += " return spread_direction;\n"; - + if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { + // Spread calculation for 2D. + 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 spread_direction = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n"; + code += " return spread_direction;\n"; + } else { + // Spread calculation for 3D. + code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; + code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n"; + code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n"; + code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n"; + code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n"; + code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n"; + code += " vec3 direction_nrm = length(direction) > 0.0 ? normalize(direction) : vec3(0.0, 0.0, 1.0);\n"; + code += " // rotate spread to direction\n"; + code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n"; + code += " if (length(binormal) < 0.0001) {\n"; + code += " // direction is parallel to Y. Choose Z as the binormal.\n"; + code += " binormal = vec3(0.0, 0.0, 1.0);\n"; + code += " }\n"; + 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 += " return normalize(spread_direction);\n"; + } code += "}\n"; code += "vec3 process_radial_displacement(DynamicsParameters param, float lifetime, inout uint alt_seed, mat4 transform, mat4 emission_transform, float delta){\n"; diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 98a12f1400..f70a00a9f6 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -40,6 +40,7 @@ // Version 2: changed names for Basis, AABB, Vectors, etc. // Version 3: new string ID for ext/subresources, breaks forward compat. // Version 4: PackedByteArray is now stored as base64 encoded. +#define FORMAT_VERSION_COMPAT 3 #define FORMAT_VERSION 4 #define BINARY_FORMAT_VERSION 4 @@ -845,7 +846,7 @@ void ResourceLoaderText::set_translation_remapped(bool p_remapped) { } ResourceLoaderText::ResourceLoaderText() : - stream(false) {} + stream(false), format_version(FORMAT_VERSION) {} void ResourceLoaderText::get_dependencies(Ref<FileAccess> p_f, List<String> *p_dependencies, bool p_add_types) { open(p_f); @@ -954,13 +955,13 @@ Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String } if (is_scene) { - fw->store_line("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + uid_text + "]\n"); + fw->store_line("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(format_version) + uid_text + "]\n"); } else { String script_res_text; if (!script_class.is_empty()) { script_res_text = "script_class=\"" + script_class + "\" "; } - fw->store_line("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + uid_text + "]\n"); + fw->store_line("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(format_version) + uid_text + "]\n"); } } @@ -1063,13 +1064,15 @@ void ResourceLoaderText::open(Ref<FileAccess> p_f, bool p_skip_first_tag) { } if (tag.fields.has("format")) { - int fmt = tag.fields["format"]; - if (fmt > FORMAT_VERSION) { + format_version = tag.fields["format"]; + if (format_version > FORMAT_VERSION) { error_text = "Saved with newer format version"; _printerr(); error = ERR_FILE_UNRECOGNIZED; return; } + } else { + format_version = FORMAT_VERSION; } if (tag.name == "gd_scene") { @@ -1970,6 +1973,12 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, _find_resources(v); } } break; + case Variant::PACKED_BYTE_ARRAY: { + // Balance between compatibility and performance. + if (use_compat && p_variant.operator PackedByteArray().size() > 64) { + use_compat = false; + } + } break; default: { } } @@ -2005,6 +2014,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso } // Save resources. + use_compat = true; // _find_resources() changes this. _find_resources(p_resource, true); if (packed_scene.is_valid()) { @@ -2037,7 +2047,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso if (load_steps > 1) { title += "load_steps=" + itos(load_steps) + " "; } - title += "format=" + itos(FORMAT_VERSION) + ""; + title += "format=" + itos(use_compat ? FORMAT_VERSION_COMPAT : FORMAT_VERSION) + ""; ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(local_path, true); @@ -2223,7 +2233,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso } String vars; - VariantWriter::write_to_string(value, vars, _write_resources, this); + VariantWriter::write_to_string(value, vars, _write_resources, this, use_compat); f->store_string(name.property_name_encode() + " = " + vars + "\n"); } } @@ -2287,14 +2297,14 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso if (!instance_placeholder.is_empty()) { String vars; f->store_string(" instance_placeholder="); - VariantWriter::write_to_string(instance_placeholder, vars, _write_resources, this); + VariantWriter::write_to_string(instance_placeholder, vars, _write_resources, this, use_compat); f->store_string(vars); } if (instance.is_valid()) { String vars; f->store_string(" instance="); - VariantWriter::write_to_string(instance, vars, _write_resources, this); + VariantWriter::write_to_string(instance, vars, _write_resources, this, use_compat); f->store_string(vars); } @@ -2302,7 +2312,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso for (int j = 0; j < state->get_node_property_count(i); j++) { String vars; - VariantWriter::write_to_string(state->get_node_property_value(i, j), vars, _write_resources, this); + VariantWriter::write_to_string(state->get_node_property_value(i, j), vars, _write_resources, this, use_compat); f->store_string(String(state->get_node_property_name(i, j)).property_name_encode() + " = " + vars + "\n"); } @@ -2336,7 +2346,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso f->store_string(connstr); if (binds.size()) { String vars; - VariantWriter::write_to_string(binds, vars, _write_resources, this); + VariantWriter::write_to_string(binds, vars, _write_resources, this, use_compat); f->store_string(" binds= " + vars); } @@ -2368,14 +2378,14 @@ Error ResourceLoaderText::set_uid(Ref<FileAccess> p_f, ResourceUID::ID p_uid) { fw = FileAccess::open(local_path + ".uidren", FileAccess::WRITE); if (is_scene) { - fw->store_string("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]"); + fw->store_string("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(format_version) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]"); } else { String script_res_text; if (!script_class.is_empty()) { script_res_text = "script_class=\"" + script_class + "\" "; } - fw->store_string("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]"); + fw->store_string("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(format_version) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]"); } uint8_t c = f->get_8(); diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index c05b7a24e1..41363fd975 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -54,6 +54,7 @@ class ResourceLoaderText { }; bool is_scene = false; + int format_version; String res_type; bool ignore_resource_parsing = false; @@ -178,6 +179,7 @@ class ResourceFormatSaverTextInstance { List<Ref<Resource>> saved_resources; HashMap<Ref<Resource>, String> external_resources; HashMap<Ref<Resource>, String> internal_resources; + bool use_compat = true; struct ResourceSort { Ref<Resource> resource; diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 06d53e4e2f..9f2fad410c 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -802,7 +802,7 @@ void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, Local const uint32_t SurfaceTool::custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 }; const uint32_t SurfaceTool::custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; -void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint64_t *r_format) { +void SurfaceTool::create_vertex_array_from_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint64_t *r_format) { ret.clear(); Vector<Vector3> varr = p_arrays[RS::ARRAY_VERTEX]; @@ -932,7 +932,7 @@ void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays } void SurfaceTool::_create_list_from_arrays(Array arr, LocalVector<Vertex> *r_vertex, LocalVector<int> *r_index, uint64_t &lformat) { - create_vertex_array_from_triangle_arrays(arr, *r_vertex, &lformat); + create_vertex_array_from_arrays(arr, *r_vertex, &lformat); ERR_FAIL_COND(r_vertex->size() == 0); //indices @@ -949,9 +949,9 @@ void SurfaceTool::_create_list_from_arrays(Array arr, LocalVector<Vertex> *r_ver } } -void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { +void SurfaceTool::create_from_arrays(const Array &p_arrays, Mesh::PrimitiveType p_primitive_type) { clear(); - primitive = Mesh::PRIMITIVE_TRIANGLES; + primitive = p_primitive_type; _create_list_from_arrays(p_arrays, &vertex_array, &index_array, format); for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { @@ -961,6 +961,10 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { } } +void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { + create_from_arrays(p_arrays, Mesh::PRIMITIVE_TRIANGLES); +} + void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from() must be a valid object of type Mesh"); @@ -1377,6 +1381,7 @@ void SurfaceTool::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &SurfaceTool::clear); ClassDB::bind_method(D_METHOD("create_from", "existing", "surface"), &SurfaceTool::create_from); + ClassDB::bind_method(D_METHOD("create_from_arrays", "arrays", "primitive_type"), &SurfaceTool::create_from_arrays, DEFVAL(Mesh::PRIMITIVE_TRIANGLES)); ClassDB::bind_method(D_METHOD("create_from_blend_shape", "existing", "surface", "blend_shape"), &SurfaceTool::create_from_blend_shape); ClassDB::bind_method(D_METHOD("append_from", "existing", "surface", "transform"), &SurfaceTool::append_from); ClassDB::bind_method(D_METHOD("commit", "existing", "flags"), &SurfaceTool::commit, DEFVAL(Variant()), DEFVAL(0)); diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h index 9dfb298b9e..a072df5bee 100644 --- a/scene/resources/surface_tool.h +++ b/scene/resources/surface_tool.h @@ -219,7 +219,8 @@ public: LocalVector<Vertex> &get_vertex_array() { return vertex_array; } void create_from_triangle_arrays(const Array &p_arrays); - static void create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<Vertex> &ret, uint64_t *r_format = nullptr); + void create_from_arrays(const Array &p_arrays, Mesh::PrimitiveType p_primitive_type = Mesh::PRIMITIVE_TRIANGLES); + static void create_vertex_array_from_arrays(const Array &p_arrays, LocalVector<Vertex> &ret, uint64_t *r_format = nullptr); Array commit_to_arrays(); void create_from(const Ref<Mesh> &p_existing, int p_surface); void create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name); diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp index 441c8859cf..c735395829 100644 --- a/scene/resources/syntax_highlighter.cpp +++ b/scene/resources/syntax_highlighter.cpp @@ -313,7 +313,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { } } - if (!in_word && (is_ascii_char(str[j]) || is_underscore(str[j])) && !is_number) { + if (!in_word && (is_ascii_alphabet_char(str[j]) || is_underscore(str[j])) && !is_number) { in_word = true; } diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 9ac899ad78..6f1aa5c850 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -1463,7 +1463,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN return String(); } - while (port_name.length() && !is_ascii_char(port_name[0])) { + while (port_name.length() && !is_ascii_alphabet_char(port_name[0])) { port_name = port_name.substr(1, port_name.length() - 1); } @@ -1508,7 +1508,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN String VisualShader::validate_parameter_name(const String &p_name, const Ref<VisualShaderNodeParameter> &p_parameter) const { String param_name = p_name; //validate name first - while (param_name.length() && !is_ascii_char(param_name[0])) { + while (param_name.length() && !is_ascii_alphabet_char(param_name[0])) { param_name = param_name.substr(1, param_name.length() - 1); } if (!param_name.is_empty()) { @@ -4206,7 +4206,7 @@ VisualShaderNodeResizableBase::VisualShaderNodeResizableBase() { set_allow_v_resize(true); } -////////////// Comment +////////////// Frame String VisualShaderNodeFrame::get_caption() const { return title; @@ -4323,6 +4323,25 @@ void VisualShaderNodeFrame::_bind_methods() { VisualShaderNodeFrame::VisualShaderNodeFrame() { } +////////////// Comment (Deprecated) + +#ifndef DISABLE_DEPRECATED +void VisualShaderNodeComment::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_description", "description"), &VisualShaderNodeComment::set_description); + ClassDB::bind_method(D_METHOD("get_description"), &VisualShaderNodeComment::get_description); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_description", "get_description"); +} + +void VisualShaderNodeComment::set_description(const String &p_description) { + description = p_description; +} + +String VisualShaderNodeComment::get_description() const { + return description; +} +#endif + ////////////// GroupBase void VisualShaderNodeGroupBase::set_inputs(const String &p_inputs) { diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 3ef6dcd4f9..d7270f3ac6 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -767,6 +767,28 @@ public: VisualShaderNodeFrame(); }; +#ifndef DISABLE_DEPRECATED +// Deprecated, for compatibility only. +class VisualShaderNodeComment : public VisualShaderNodeFrame { + GDCLASS(VisualShaderNodeComment, VisualShaderNodeFrame); + + String description; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const override { return "Comment(Deprecated)"; } + + virtual Category get_category() const override { return CATEGORY_NONE; } + + void set_description(const String &p_description); + String get_description() const; + + VisualShaderNodeComment() {} +}; +#endif + class VisualShaderNodeGroupBase : public VisualShaderNodeResizableBase { GDCLASS(VisualShaderNodeGroupBase, VisualShaderNodeResizableBase); diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index 3e3a7d2381..b5333d91c6 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -676,63 +676,6 @@ bool AudioStreamRandomizer::is_monophonic() const { return false; } -bool AudioStreamRandomizer::_get(const StringName &p_name, Variant &r_ret) const { - if (AudioStream::_get(p_name, r_ret)) { - return true; - } - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("stream_") && components[0].trim_prefix("stream_").is_valid_int()) { - int index = components[0].trim_prefix("stream_").to_int(); - if (index < 0 || index >= (int)audio_stream_pool.size()) { - return false; - } - - if (components[1] == "stream") { - r_ret = get_stream(index); - return true; - } else if (components[1] == "weight") { - r_ret = get_stream_probability_weight(index); - return true; - } else { - return false; - } - } - return false; -} - -bool AudioStreamRandomizer::_set(const StringName &p_name, const Variant &p_value) { - if (AudioStream::_set(p_name, p_value)) { - return true; - } - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("stream_") && components[0].trim_prefix("stream_").is_valid_int()) { - int index = components[0].trim_prefix("stream_").to_int(); - if (index < 0 || index >= (int)audio_stream_pool.size()) { - return false; - } - - if (components[1] == "stream") { - set_stream(index, p_value); - return true; - } else if (components[1] == "weight") { - set_stream_probability_weight(index, p_value); - return true; - } else { - return false; - } - } - return false; -} - -void AudioStreamRandomizer::_get_property_list(List<PropertyInfo> *p_list) const { - AudioStream::_get_property_list(p_list); // Define the trivial scalar properties. - p_list->push_back(PropertyInfo(Variant::NIL, "Streams", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < audio_stream_pool.size(); i++) { - p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("stream_%d/stream", i), PROPERTY_HINT_RESOURCE_TYPE, "AudioStream")); - p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("stream_%d/weight", i), PROPERTY_HINT_RANGE, "0,100,0.001,or_greater")); - } -} - void AudioStreamRandomizer::_bind_methods() { ClassDB::bind_method(D_METHOD("add_stream", "index", "stream", "weight"), &AudioStreamRandomizer::add_stream, DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("move_stream", "index_from", "index_to"), &AudioStreamRandomizer::move_stream); @@ -764,9 +707,17 @@ void AudioStreamRandomizer::_bind_methods() { BIND_ENUM_CONSTANT(PLAYBACK_RANDOM_NO_REPEATS); BIND_ENUM_CONSTANT(PLAYBACK_RANDOM); BIND_ENUM_CONSTANT(PLAYBACK_SEQUENTIAL); + + PoolEntry defaults; + + base_property_helper.set_prefix("stream_"); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), defaults.stream, &AudioStreamRandomizer::set_stream, &AudioStreamRandomizer::get_stream); + base_property_helper.register_property(PropertyInfo(Variant::FLOAT, "weight", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), defaults.weight, &AudioStreamRandomizer::set_stream_probability_weight, &AudioStreamRandomizer::get_stream_probability_weight); } -AudioStreamRandomizer::AudioStreamRandomizer() {} +AudioStreamRandomizer::AudioStreamRandomizer() { + property_helper.setup_for_instance(base_property_helper, this); +} void AudioStreamPlaybackRandomizer::start(double p_from_pos) { playing = playback; diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index f8123fbe15..01a4a09942 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -33,6 +33,7 @@ #include "core/io/image.h" #include "core/io/resource.h" +#include "scene/property_list_helper.h" #include "servers/audio/audio_filter_sw.h" #include "servers/audio_server.h" @@ -236,9 +237,12 @@ private: struct PoolEntry { Ref<AudioStream> stream; - float weight; + float weight = 1.0; }; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + HashSet<AudioStreamPlaybackRandomizer *> playbacks; Vector<PoolEntry> audio_stream_pool; float random_pitch_scale = 1.0f; @@ -254,9 +258,11 @@ private: protected: static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); } + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, audio_stream_pool.size()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } public: void add_stream(int p_index, Ref<AudioStream> p_stream, float p_weight = 1.0); diff --git a/servers/navigation_server_2d.cpp b/servers/navigation_server_2d.cpp index 625ae8abde..c5ce82265b 100644 --- a/servers/navigation_server_2d.cpp +++ b/servers/navigation_server_2d.cpp @@ -165,6 +165,9 @@ void NavigationServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data_async", "navigation_polygon", "source_geometry_data", "callback"), &NavigationServer2D::bake_from_source_geometry_data_async, DEFVAL(Callable())); ClassDB::bind_method(D_METHOD("is_baking_navigation_polygon", "navigation_polygon"), &NavigationServer2D::is_baking_navigation_polygon); + ClassDB::bind_method(D_METHOD("source_geometry_parser_create"), &NavigationServer2D::source_geometry_parser_create); + ClassDB::bind_method(D_METHOD("source_geometry_parser_set_callback", "parser", "callback"), &NavigationServer2D::source_geometry_parser_set_callback); + ClassDB::bind_method(D_METHOD("simplify_path", "path", "epsilon"), &NavigationServer2D::simplify_path); ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer2D::free); diff --git a/servers/navigation_server_2d.h b/servers/navigation_server_2d.h index 39d4c19064..a8d9678a6f 100644 --- a/servers/navigation_server_2d.h +++ b/servers/navigation_server_2d.h @@ -306,6 +306,9 @@ public: virtual void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) = 0; virtual bool is_baking_navigation_polygon(Ref<NavigationPolygon> p_navigation_polygon) const = 0; + virtual RID source_geometry_parser_create() = 0; + virtual void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) = 0; + virtual Vector<Vector2> simplify_path(const Vector<Vector2> &p_path, real_t p_epsilon) = 0; NavigationServer2D(); diff --git a/servers/navigation_server_2d_dummy.h b/servers/navigation_server_2d_dummy.h index 5d4cfbf91b..465cfcca98 100644 --- a/servers/navigation_server_2d_dummy.h +++ b/servers/navigation_server_2d_dummy.h @@ -170,6 +170,9 @@ public: void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override {} bool is_baking_navigation_polygon(Ref<NavigationPolygon> p_navigation_polygon) const override { return false; } + RID source_geometry_parser_create() override { return RID(); } + void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) override {} + Vector<Vector2> simplify_path(const Vector<Vector2> &p_path, real_t p_epsilon) override { return Vector<Vector2>(); } void set_debug_enabled(bool p_enabled) {} diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp index fda26aacc1..b21c6b60f0 100644 --- a/servers/navigation_server_3d.cpp +++ b/servers/navigation_server_3d.cpp @@ -188,6 +188,9 @@ void NavigationServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_baking_navigation_mesh", "navigation_mesh"), &NavigationServer3D::is_baking_navigation_mesh); #endif // _3D_DISABLED + ClassDB::bind_method(D_METHOD("source_geometry_parser_create"), &NavigationServer3D::source_geometry_parser_create); + ClassDB::bind_method(D_METHOD("source_geometry_parser_set_callback", "parser", "callback"), &NavigationServer3D::source_geometry_parser_set_callback); + ClassDB::bind_method(D_METHOD("simplify_path", "path", "epsilon"), &NavigationServer3D::simplify_path); ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer3D::free); diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h index 5a93c662b2..17c0771732 100644 --- a/servers/navigation_server_3d.h +++ b/servers/navigation_server_3d.h @@ -351,6 +351,9 @@ public: virtual bool is_baking_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) const = 0; #endif // _3D_DISABLED + virtual RID source_geometry_parser_create() = 0; + virtual void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) = 0; + virtual Vector<Vector3> simplify_path(const Vector<Vector3> &p_path, real_t p_epsilon) = 0; NavigationServer3D(); diff --git a/servers/navigation_server_3d_dummy.h b/servers/navigation_server_3d_dummy.h index 7079aa66be..5c9e97d226 100644 --- a/servers/navigation_server_3d_dummy.h +++ b/servers/navigation_server_3d_dummy.h @@ -182,6 +182,9 @@ public: bool is_baking_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) const override { return false; } #endif // _3D_DISABLED + RID source_geometry_parser_create() override { return RID(); } + void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) override {} + Vector<Vector3> simplify_path(const Vector<Vector3> &p_path, real_t p_epsilon) override { return Vector<Vector3>(); } void free(RID p_object) override {} diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index c03c0b7a40..1a75614a4c 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -94,6 +94,7 @@ #include "physics_server_3d_wrap_mt.h" #include "servers/extensions/physics_server_3d_extension.h" #include "xr/xr_body_tracker.h" +#include "xr/xr_controller_tracker.h" #include "xr/xr_face_tracker.h" #include "xr/xr_hand_tracker.h" #include "xr/xr_interface.h" @@ -325,12 +326,14 @@ void register_server_types() { GDREGISTER_ABSTRACT_CLASS(XRInterface); GDREGISTER_CLASS(XRBodyTracker); + GDREGISTER_CLASS(XRControllerTracker); GDREGISTER_CLASS(XRFaceTracker); GDREGISTER_CLASS(XRHandTracker); GDREGISTER_CLASS(XRInterfaceExtension); // can't register this as virtual because we need a creation function for our extensions. GDREGISTER_CLASS(XRPose); GDREGISTER_CLASS(XRPositionalTracker); GDREGISTER_CLASS(XRServer); + GDREGISTER_ABSTRACT_CLASS(XRTracker); #endif // _3D_DISABLED GDREGISTER_ABSTRACT_CLASS(NavigationServer3D); diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index 46c84fd230..34f9069649 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -1970,6 +1970,8 @@ void RendererCanvasCull::canvas_light_occluder_set_polygon(RID p_occluder, RID p void RendererCanvasCull::canvas_light_occluder_set_as_sdf_collision(RID p_occluder, bool p_enable) { RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder); ERR_FAIL_NULL(occluder); + + occluder->sdf_collision = p_enable; } void RendererCanvasCull::canvas_light_occluder_set_transform(RID p_occluder, const Transform2D &p_xform) { diff --git a/servers/rendering/renderer_rd/effects/copy_effects.cpp b/servers/rendering/renderer_rd/effects/copy_effects.cpp index 1568867663..a363b03dd8 100644 --- a/servers/rendering/renderer_rd/effects/copy_effects.cpp +++ b/servers/rendering/renderer_rd/effects/copy_effects.cpp @@ -533,7 +533,7 @@ void CopyEffects::copy_to_atlas_fb(RID p_source_rd_texture, RID p_dest_framebuff RD::get_singleton()->draw_list_draw(draw_list, true); } -void CopyEffects::copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y, bool p_force_luminance, bool p_alpha_to_zero, bool p_srgb, RID p_secondary, bool p_multiview, bool p_alpha_to_one, bool p_linear, bool p_normal) { +void CopyEffects::copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y, bool p_force_luminance, bool p_alpha_to_zero, bool p_srgb, RID p_secondary, bool p_multiview, bool p_alpha_to_one, bool p_linear, bool p_normal, const Rect2 &p_src_rect) { UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton(); ERR_FAIL_NULL(uniform_set_cache); MaterialStorage *material_storage = MaterialStorage::get_singleton(); @@ -568,6 +568,14 @@ void CopyEffects::copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffe copy_to_fb.push_constant.flags |= COPY_TO_FB_FLAG_NORMAL; } + if (p_src_rect != Rect2()) { + copy_to_fb.push_constant.section[0] = p_src_rect.position.x; + copy_to_fb.push_constant.section[1] = p_src_rect.position.y; + copy_to_fb.push_constant.section[2] = p_src_rect.size.x; + copy_to_fb.push_constant.section[3] = p_src_rect.size.y; + copy_to_fb.push_constant.flags |= COPY_TO_FB_FLAG_USE_SRC_SECTION; + } + // setup our uniforms RID default_sampler = material_storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED); diff --git a/servers/rendering/renderer_rd/effects/copy_effects.h b/servers/rendering/renderer_rd/effects/copy_effects.h index d18971a676..014f78e2b9 100644 --- a/servers/rendering/renderer_rd/effects/copy_effects.h +++ b/servers/rendering/renderer_rd/effects/copy_effects.h @@ -191,6 +191,7 @@ private: COPY_TO_FB_FLAG_ALPHA_TO_ONE = (1 << 5), COPY_TO_FB_FLAG_LINEAR = (1 << 6), COPY_TO_FB_FLAG_NORMAL = (1 << 7), + COPY_TO_FB_FLAG_USE_SRC_SECTION = (1 << 8), }; struct CopyToFbPushConstant { @@ -329,7 +330,7 @@ public: void copy_cubemap_to_panorama(RID p_source_cube, RID p_dest_panorama, const Size2i &p_panorama_size, float p_lod, bool p_is_array); void copy_depth_to_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y = false); void copy_depth_to_rect_and_linearize(RID p_source_rd_texture, RID p_dest_texture, const Rect2i &p_rect, bool p_flip_y, float p_z_near, float p_z_far); - void copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y = false, bool p_force_luminance = false, bool p_alpha_to_zero = false, bool p_srgb = false, RID p_secondary = RID(), bool p_multiview = false, bool alpha_to_one = false, bool p_linear = false, bool p_normal = false); + void copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y = false, bool p_force_luminance = false, bool p_alpha_to_zero = false, bool p_srgb = false, RID p_secondary = RID(), bool p_multiview = false, bool alpha_to_one = false, bool p_linear = false, bool p_normal = false, const Rect2 &p_src_rect = Rect2()); void copy_to_atlas_fb(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2 &p_uv_rect, RD::DrawListID p_draw_list, bool p_flip_y = false, bool p_panorama = false); void copy_to_drawlist(RD::DrawListID p_draw_list, RD::FramebufferFormatID p_fb_format, RID p_source_rd_texture, bool p_linear = false); void copy_raster(RID p_source_texture, RID p_dest_framebuffer); diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp index 27c07f23fa..b5d31f5414 100644 --- a/servers/rendering/renderer_rd/environment/sky.cpp +++ b/servers/rendering/renderer_rd/environment/sky.cpp @@ -1472,7 +1472,7 @@ void SkyRD::update_res_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p Vector<Color> clear_colors; clear_colors.push_back(Color(0.0, 0.0, 0.0)); - RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 0.0); _render_sky(draw_list, p_time, framebuffer, pipeline, material->uniform_set, texture_uniform_set, projection, sky_transform, sky_scene_state.cam_transform.origin, p_luminance_multiplier); RD::get_singleton()->draw_list_end(); } @@ -1491,7 +1491,7 @@ void SkyRD::update_res_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p Vector<Color> clear_colors; clear_colors.push_back(Color(0.0, 0.0, 0.0)); - RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 0.0); _render_sky(draw_list, p_time, framebuffer, pipeline, material->uniform_set, texture_uniform_set, projection, sky_transform, sky_scene_state.cam_transform.origin, p_luminance_multiplier); RD::get_singleton()->draw_list_end(); } 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 0e69ad99b8..c7ab7ea462 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1413,7 +1413,7 @@ void RenderForwardClustered::_pre_opaque_render(RenderDataRD *p_render_data, boo if (p_render_data->directional_shadows.size()) { //open the pass for directional shadows light_storage->update_directional_shadow_atlas(); - RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE); + RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, Vector<Color>(), 0.0); RD::get_singleton()->draw_list_end(); } } @@ -1930,7 +1930,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co } if (needs_pre_resolve) { //pre clear the depth framebuffer, as AMD (and maybe others?) use compute for it, and barrier other compute shaders. - RD::get_singleton()->draw_list_begin(depth_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, depth_pass_clear); + RD::get_singleton()->draw_list_begin(depth_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, depth_pass_clear, 0.0); RD::get_singleton()->draw_list_end(); //start compute processes here, so they run at the same time as depth pre-pass _post_prepass_render(p_render_data, using_sdfgi || using_voxelgi); 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 48c9cda253..5715d94d95 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -614,7 +614,7 @@ void RenderForwardMobile::_pre_opaque_render(RenderDataRD *p_render_data) { if (p_render_data->directional_shadows.size()) { //open the pass for directional shadows light_storage->update_directional_shadow_atlas(); - RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE); + RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, Vector<Color>(), 0.0); RD::get_singleton()->draw_list_end(); } } diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index 6f56711151..fa8cf9c028 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -1931,7 +1931,7 @@ void RendererCanvasRenderRD::render_sdf(RID p_render_target, LightOccluderInstan while (instance) { OccluderPolygon *co = occluder_polygon_owner.get_or_null(instance->occluder); - if (!co || co->sdf_index_array.is_null()) { + if (!co || co->sdf_index_array.is_null() || !instance->sdf_collision) { instance = instance->next; continue; } diff --git a/servers/rendering/renderer_rd/shaders/effects/copy_to_fb.glsl b/servers/rendering/renderer_rd/shaders/effects/copy_to_fb.glsl index 7192e596eb..26ee06aa03 100644 --- a/servers/rendering/renderer_rd/shaders/effects/copy_to_fb.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/copy_to_fb.glsl @@ -21,6 +21,7 @@ #define FLAG_ALPHA_TO_ONE (1 << 5) #define FLAG_LINEAR (1 << 6) #define FLAG_NORMAL (1 << 7) +#define FLAG_USE_SRC_SECTION (1 << 8) #ifdef USE_MULTIVIEW layout(location = 0) out vec3 uv_interp; @@ -54,6 +55,10 @@ void main() { if (bool(params.flags & FLAG_FLIP_Y)) { uv_interp.y = 1.0 - uv_interp.y; } + + if (bool(params.flags & FLAG_USE_SRC_SECTION)) { + uv_interp.xy = params.section.xy + uv_interp.xy * params.section.zw; + } } #[fragment] diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp index da046bf6b1..af30a32866 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp @@ -3820,7 +3820,10 @@ void TextureStorage::render_target_copy_to_back_buffer(RID p_render_target, cons if (RendererSceneRenderRD::get_singleton()->_render_buffers_can_be_storage()) { copy_effects->copy_to_rect(rt->color, rt->backbuffer_mipmap0, region, false, false, false, !rt->use_hdr, true); } else { - copy_effects->copy_to_fb_rect(rt->color, rt->backbuffer_fb, region, false, false, false, false, RID(), false, true); + Rect2 src_rect = Rect2(region); + src_rect.position /= Size2(rt->size); + src_rect.size /= Size2(rt->size); + copy_effects->copy_to_fb_rect(rt->color, rt->backbuffer_fb, region, false, false, false, false, RID(), false, true, false, false, src_rect); } if (!p_gen_mipmaps) { diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index 2b6644e893..962531c866 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -1350,6 +1350,9 @@ Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_laye thread_local LocalVector<RDD::BufferTextureCopyRegion> command_buffer_texture_copy_regions_vector; command_buffer_texture_copy_regions_vector.clear(); + uint32_t block_w = 0, block_h = 0; + get_compressed_image_format_block_dimensions(tex->format, block_w, block_h); + uint32_t w = tex->width; uint32_t h = tex->height; uint32_t d = tex->depth; @@ -1365,8 +1368,8 @@ Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_laye copy_region.texture_region_size.z = d; command_buffer_texture_copy_regions_vector.push_back(copy_region); - w = MAX(1u, w >> 1); - h = MAX(1u, h >> 1); + w = MAX(block_w, w >> 1); + h = MAX(block_h, h >> 1); d = MAX(1u, d >> 1); } @@ -1395,8 +1398,6 @@ Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_laye for (uint32_t i = 0; i < tex->mipmaps; i++) { uint32_t width = 0, height = 0, depth = 0; uint32_t tight_mip_size = get_image_format_required_size(tex->format, w, h, d, 1, &width, &height, &depth); - uint32_t block_w = 0, block_h = 0; - get_compressed_image_format_block_dimensions(tex->format, block_w, block_h); uint32_t tight_row_pitch = tight_mip_size / ((height / block_h) * depth); // Copy row-by-row to erase padding due to alignments. @@ -1408,8 +1409,8 @@ Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_laye wp += tight_row_pitch; } - w = MAX(1u, w >> 1); - h = MAX(1u, h >> 1); + w = MAX(block_w, w >> 1); + h = MAX(block_h, h >> 1); d = MAX(1u, d >> 1); read_ptr += mip_layouts[i].size; write_ptr += tight_mip_size; @@ -6400,11 +6401,11 @@ Vector<int64_t> RenderingDevice::_draw_list_switch_to_next_pass_split(uint32_t p #endif void RenderingDevice::_draw_list_set_push_constant(DrawListID p_list, const Vector<uint8_t> &p_data, uint32_t p_data_size) { - ERR_FAIL_COND((uint32_t)p_data.size() > p_data_size); + ERR_FAIL_COND(p_data_size > (uint32_t)p_data.size()); draw_list_set_push_constant(p_list, p_data.ptr(), p_data_size); } void RenderingDevice::_compute_list_set_push_constant(ComputeListID p_list, const Vector<uint8_t> &p_data, uint32_t p_data_size) { - ERR_FAIL_COND((uint32_t)p_data.size() > p_data_size); + ERR_FAIL_COND(p_data_size > (uint32_t)p_data.size()); compute_list_set_push_constant(p_list, p_data.ptr(), p_data_size); } diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index c8277024cf..9a898a2fca 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -1103,7 +1103,7 @@ private: public: DrawListID draw_list_begin_for_screen(DisplayServer::WindowID p_screen = 0, const Color &p_clear_color = Color()); - DrawListID draw_list_begin(RID p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values = Vector<Color>(), float p_clear_depth = 0.0, uint32_t p_clear_stencil = 0, const Rect2 &p_region = Rect2()); + DrawListID draw_list_begin(RID p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values = Vector<Color>(), float p_clear_depth = 1.0, uint32_t p_clear_stencil = 0, const Rect2 &p_region = Rect2()); void draw_list_set_blend_constants(DrawListID p_list, const Color &p_color); void draw_list_bind_render_pipeline(DrawListID p_list, RID p_render_pipeline); diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp index 53e1d119fd..8f9797805f 100644 --- a/servers/text/text_server_extension.cpp +++ b/servers/text/text_server_extension.cpp @@ -285,7 +285,7 @@ void TextServerExtension::_bind_methods() { GDVIRTUAL_BIND(_shaped_text_get_line_breaks_adv, "shaped", "width", "start", "once", "break_flags"); GDVIRTUAL_BIND(_shaped_text_get_line_breaks, "shaped", "width", "start", "break_flags"); - GDVIRTUAL_BIND(_shaped_text_get_word_breaks, "shaped", "grapheme_flags"); + GDVIRTUAL_BIND(_shaped_text_get_word_breaks, "shaped", "grapheme_flags", "skip_grapheme_flags"); GDVIRTUAL_BIND(_shaped_text_get_trim_pos, "shaped"); GDVIRTUAL_BIND(_shaped_text_get_ellipsis_pos, "shaped"); @@ -1256,12 +1256,12 @@ PackedInt32Array TextServerExtension::shaped_text_get_line_breaks(const RID &p_s return TextServer::shaped_text_get_line_breaks(p_shaped, p_width, p_start, p_break_flags); } -PackedInt32Array TextServerExtension::shaped_text_get_word_breaks(const RID &p_shaped, BitField<TextServer::GraphemeFlag> p_grapheme_flags) const { +PackedInt32Array TextServerExtension::shaped_text_get_word_breaks(const RID &p_shaped, BitField<TextServer::GraphemeFlag> p_grapheme_flags, BitField<TextServer::GraphemeFlag> p_skip_grapheme_flags) const { PackedInt32Array ret; - if (GDVIRTUAL_CALL(_shaped_text_get_word_breaks, p_shaped, p_grapheme_flags, ret)) { + if (GDVIRTUAL_CALL(_shaped_text_get_word_breaks, p_shaped, p_grapheme_flags, p_skip_grapheme_flags, ret)) { return ret; } - return TextServer::shaped_text_get_word_breaks(p_shaped, p_grapheme_flags); + return TextServer::shaped_text_get_word_breaks(p_shaped, p_grapheme_flags, p_skip_grapheme_flags); } int64_t TextServerExtension::shaped_text_get_trim_pos(const RID &p_shaped) const { diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h index 53d30abee4..f447f0f5bd 100644 --- a/servers/text/text_server_extension.h +++ b/servers/text/text_server_extension.h @@ -472,10 +472,10 @@ public: virtual PackedInt32Array shaped_text_get_line_breaks_adv(const RID &p_shaped, const PackedFloat32Array &p_width, int64_t p_start = 0, bool p_once = true, BitField<TextServer::LineBreakFlag> p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const override; virtual PackedInt32Array shaped_text_get_line_breaks(const RID &p_shaped, double p_width, int64_t p_start = 0, BitField<TextServer::LineBreakFlag> p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const override; - virtual PackedInt32Array shaped_text_get_word_breaks(const RID &p_shaped, BitField<TextServer::GraphemeFlag> p_grapheme_flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_PUNCTUATION) const override; + virtual PackedInt32Array shaped_text_get_word_breaks(const RID &p_shaped, BitField<TextServer::GraphemeFlag> p_grapheme_flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_PUNCTUATION, BitField<TextServer::GraphemeFlag> p_skip_grapheme_flags = GRAPHEME_IS_VIRTUAL) const override; GDVIRTUAL5RC(PackedInt32Array, _shaped_text_get_line_breaks_adv, RID, const PackedFloat32Array &, int64_t, bool, BitField<TextServer::LineBreakFlag>); GDVIRTUAL4RC(PackedInt32Array, _shaped_text_get_line_breaks, RID, double, int64_t, BitField<TextServer::LineBreakFlag>); - GDVIRTUAL2RC(PackedInt32Array, _shaped_text_get_word_breaks, RID, BitField<TextServer::GraphemeFlag>); + GDVIRTUAL3RC(PackedInt32Array, _shaped_text_get_word_breaks, RID, BitField<TextServer::GraphemeFlag>, BitField<TextServer::GraphemeFlag>); virtual int64_t shaped_text_get_trim_pos(const RID &p_shaped) const override; virtual int64_t shaped_text_get_ellipsis_pos(const RID &p_shaped) const override; diff --git a/servers/text_server.compat.inc b/servers/text_server.compat.inc new file mode 100644 index 0000000000..0ff35721a3 --- /dev/null +++ b/servers/text_server.compat.inc @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* text_server.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 DISABLE_DEPRECATED + +PackedInt32Array TextServer::_shaped_text_get_word_breaks_bind_compat_90732(const RID &p_shaped, BitField<TextServer::GraphemeFlag> p_grapheme_flags) const { + return shaped_text_get_word_breaks(p_shaped, p_grapheme_flags, 0); +} + +void TextServer::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("shaped_text_get_word_breaks", "shaped", "grapheme_flags"), &TextServer::_shaped_text_get_word_breaks_bind_compat_90732, DEFVAL(GRAPHEME_IS_SPACE | GRAPHEME_IS_PUNCTUATION)); +} + +#endif diff --git a/servers/text_server.cpp b/servers/text_server.cpp index fac9e32d01..562f2df411 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -29,6 +29,8 @@ /**************************************************************************/ #include "servers/text_server.h" +#include "text_server.compat.inc" + #include "core/variant/typed_array.h" #include "servers/rendering_server.h" @@ -435,7 +437,7 @@ void TextServer::_bind_methods() { ClassDB::bind_method(D_METHOD("shaped_text_get_range", "shaped"), &TextServer::shaped_text_get_range); ClassDB::bind_method(D_METHOD("shaped_text_get_line_breaks_adv", "shaped", "width", "start", "once", "break_flags"), &TextServer::shaped_text_get_line_breaks_adv, DEFVAL(0), DEFVAL(true), DEFVAL(BREAK_MANDATORY | BREAK_WORD_BOUND)); ClassDB::bind_method(D_METHOD("shaped_text_get_line_breaks", "shaped", "width", "start", "break_flags"), &TextServer::shaped_text_get_line_breaks, DEFVAL(0), DEFVAL(BREAK_MANDATORY | BREAK_WORD_BOUND)); - ClassDB::bind_method(D_METHOD("shaped_text_get_word_breaks", "shaped", "grapheme_flags"), &TextServer::shaped_text_get_word_breaks, DEFVAL(GRAPHEME_IS_SPACE | GRAPHEME_IS_PUNCTUATION)); + ClassDB::bind_method(D_METHOD("shaped_text_get_word_breaks", "shaped", "grapheme_flags", "skip_grapheme_flags"), &TextServer::shaped_text_get_word_breaks, DEFVAL(GRAPHEME_IS_SPACE | GRAPHEME_IS_PUNCTUATION), DEFVAL(GRAPHEME_IS_VIRTUAL)); ClassDB::bind_method(D_METHOD("shaped_text_get_trim_pos", "shaped"), &TextServer::shaped_text_get_trim_pos); ClassDB::bind_method(D_METHOD("shaped_text_get_ellipsis_pos", "shaped"), &TextServer::shaped_text_get_ellipsis_pos); @@ -1094,7 +1096,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do return lines; } -PackedInt32Array TextServer::shaped_text_get_word_breaks(const RID &p_shaped, BitField<TextServer::GraphemeFlag> p_grapheme_flags) const { +PackedInt32Array TextServer::shaped_text_get_word_breaks(const RID &p_shaped, BitField<TextServer::GraphemeFlag> p_grapheme_flags, BitField<TextServer::GraphemeFlag> p_skip_grapheme_flags) const { PackedInt32Array words; const_cast<TextServer *>(this)->shaped_text_update_justification_ops(p_shaped); @@ -1107,10 +1109,11 @@ PackedInt32Array TextServer::shaped_text_get_word_breaks(const RID &p_shaped, Bi for (int i = 0; i < l_size; i++) { if (l_gl[i].count > 0) { - if ((l_gl[i].flags & p_grapheme_flags) != 0) { - if (word_start != l_gl[i].start) { + if ((l_gl[i].flags & p_grapheme_flags) != 0 && (l_gl[i].flags & p_skip_grapheme_flags) == 0) { + int next = (i == 0) ? l_gl[i].start : l_gl[i - 1].end; + if (word_start < next) { words.push_back(word_start); - words.push_back(l_gl[i].start); + words.push_back(next); } word_start = l_gl[i].end; } diff --git a/servers/text_server.h b/servers/text_server.h index 396d7ca8e5..775dbb5508 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -226,6 +226,11 @@ protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + PackedInt32Array _shaped_text_get_word_breaks_bind_compat_90732(const RID &p_shaped, BitField<TextServer::GraphemeFlag> p_grapheme_flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_PUNCTUATION) const; + static void _bind_compatibility_methods(); +#endif + public: virtual bool has_feature(Feature p_feature) const = 0; virtual String get_name() const = 0; @@ -483,7 +488,7 @@ public: virtual PackedInt32Array shaped_text_get_line_breaks_adv(const RID &p_shaped, const PackedFloat32Array &p_width, int64_t p_start = 0, bool p_once = true, BitField<TextServer::LineBreakFlag> p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const; virtual PackedInt32Array shaped_text_get_line_breaks(const RID &p_shaped, double p_width, int64_t p_start = 0, BitField<TextServer::LineBreakFlag> p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const; - virtual PackedInt32Array shaped_text_get_word_breaks(const RID &p_shaped, BitField<TextServer::GraphemeFlag> p_grapheme_flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_PUNCTUATION) const; + virtual PackedInt32Array shaped_text_get_word_breaks(const RID &p_shaped, BitField<TextServer::GraphemeFlag> p_grapheme_flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_PUNCTUATION, BitField<TextServer::GraphemeFlag> p_skip_grapheme_flags = GRAPHEME_IS_VIRTUAL) const; virtual int64_t shaped_text_get_trim_pos(const RID &p_shaped) const = 0; virtual int64_t shaped_text_get_ellipsis_pos(const RID &p_shaped) const = 0; diff --git a/servers/xr/xr_body_tracker.cpp b/servers/xr/xr_body_tracker.cpp index cd58c14348..9c82b80911 100644 --- a/servers/xr/xr_body_tracker.cpp +++ b/servers/xr/xr_body_tracker.cpp @@ -134,6 +134,14 @@ void XRBodyTracker::_bind_methods() { BIND_BITFIELD_FLAG(JOINT_FLAG_POSITION_TRACKED); } +void XRBodyTracker::set_tracker_type(XRServer::TrackerType p_type) { + ERR_FAIL_COND_MSG(p_type != XRServer::TRACKER_BODY, "XRBodyTracker must be of type TRACKER_BODY."); +} + +void XRBodyTracker::set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) { + ERR_FAIL_COND_MSG(p_hand != XRPositionalTracker::TRACKER_HAND_UNKNOWN, "XRBodyTracker cannot specify hand."); +} + void XRBodyTracker::set_has_tracking_data(bool p_has_tracking_data) { has_tracking_data = p_has_tracking_data; } @@ -169,3 +177,7 @@ Transform3D XRBodyTracker::get_joint_transform(Joint p_joint) const { ERR_FAIL_INDEX_V(p_joint, JOINT_MAX, Transform3D()); return joint_transforms[p_joint]; } + +XRBodyTracker::XRBodyTracker() { + type = XRServer::TRACKER_BODY; +} diff --git a/servers/xr/xr_body_tracker.h b/servers/xr/xr_body_tracker.h index 659aa39df1..544de8ca8b 100644 --- a/servers/xr/xr_body_tracker.h +++ b/servers/xr/xr_body_tracker.h @@ -31,10 +31,10 @@ #ifndef XR_BODY_TRACKER_H #define XR_BODY_TRACKER_H -#include "core/object/ref_counted.h" +#include "servers/xr/xr_positional_tracker.h" -class XRBodyTracker : public RefCounted { - GDCLASS(XRBodyTracker, RefCounted); +class XRBodyTracker : public XRPositionalTracker { + GDCLASS(XRBodyTracker, XRPositionalTracker); _THREAD_SAFE_CLASS_ public: @@ -140,6 +140,9 @@ public: JOINT_FLAG_POSITION_TRACKED = 8, }; + void set_tracker_type(XRServer::TrackerType p_type) override; + void set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) override; + void set_has_tracking_data(bool p_has_tracking_data); bool get_has_tracking_data() const; @@ -152,6 +155,8 @@ public: void set_joint_transform(Joint p_joint, const Transform3D &p_transform); Transform3D get_joint_transform(Joint p_joint) const; + XRBodyTracker(); + protected: static void _bind_methods(); diff --git a/servers/xr/xr_controller_tracker.cpp b/servers/xr/xr_controller_tracker.cpp new file mode 100644 index 0000000000..df85e86b7e --- /dev/null +++ b/servers/xr/xr_controller_tracker.cpp @@ -0,0 +1,39 @@ +/**************************************************************************/ +/* xr_controller_tracker.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_controller_tracker.h" + +#include "core/input/input.h" + +void XRControllerTracker::_bind_methods(){}; + +XRControllerTracker::XRControllerTracker() { + type = XRServer::TRACKER_CONTROLLER; +} diff --git a/servers/xr/xr_controller_tracker.h b/servers/xr/xr_controller_tracker.h new file mode 100644 index 0000000000..a443cc1fd8 --- /dev/null +++ b/servers/xr/xr_controller_tracker.h @@ -0,0 +1,52 @@ +/**************************************************************************/ +/* xr_controller_tracker.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 XR_CONTROLLER_TRACKER_H +#define XR_CONTROLLER_TRACKER_H + +#include "core/os/thread_safe.h" +#include "servers/xr/xr_positional_tracker.h" + +/** + The controller tracker object as an object that represents the position and orientation of a controller. +*/ + +class XRControllerTracker : public XRPositionalTracker { + GDCLASS(XRControllerTracker, XRPositionalTracker); + _THREAD_SAFE_CLASS_ + +protected: + static void _bind_methods(); + +public: + XRControllerTracker(); +}; + +#endif // XR_CONTROLLER_TRACKER_H diff --git a/servers/xr/xr_face_tracker.cpp b/servers/xr/xr_face_tracker.cpp index a38ccfd527..7015cd0805 100644 --- a/servers/xr/xr_face_tracker.cpp +++ b/servers/xr/xr_face_tracker.cpp @@ -187,6 +187,10 @@ void XRFaceTracker::_bind_methods() { ADD_PROPERTY_DEFAULT("blend_shapes", PackedFloat32Array()); // To prevent ludicrously large default values. } +void XRFaceTracker::set_tracker_type(XRServer::TrackerType p_type) { + ERR_FAIL_COND_MSG(p_type != XRServer::TRACKER_FACE, "XRFaceTracker must be of type TRACKER_FACE."); +} + float XRFaceTracker::get_blend_shape(BlendShapeEntry p_blend_shape) const { // Fail if the blend shape index is out of range. ERR_FAIL_INDEX_V(p_blend_shape, FT_MAX, 0.0f); @@ -220,3 +224,7 @@ void XRFaceTracker::set_blend_shapes(const PackedFloat32Array &p_blend_shapes) { // Copy the blend shape values into the blend shape array. memcpy(blend_shape_values, p_blend_shapes.ptr(), sizeof(blend_shape_values)); } + +XRFaceTracker::XRFaceTracker() { + type = XRServer::TRACKER_FACE; +} diff --git a/servers/xr/xr_face_tracker.h b/servers/xr/xr_face_tracker.h index b9f553cba6..a753a7abdc 100644 --- a/servers/xr/xr_face_tracker.h +++ b/servers/xr/xr_face_tracker.h @@ -31,7 +31,7 @@ #ifndef XR_FACE_TRACKER_H #define XR_FACE_TRACKER_H -#include "core/object/ref_counted.h" +#include "servers/xr/xr_tracker.h" /** The XRFaceTracker class provides face blend shape weights. @@ -41,8 +41,8 @@ and Meta Movement standards. */ -class XRFaceTracker : public RefCounted { - GDCLASS(XRFaceTracker, RefCounted); +class XRFaceTracker : public XRTracker { + GDCLASS(XRFaceTracker, XRTracker); _THREAD_SAFE_CLASS_ public: @@ -195,12 +195,16 @@ public: FT_MAX // Maximum blend shape. }; + void set_tracker_type(XRServer::TrackerType p_type) override; + float get_blend_shape(BlendShapeEntry p_blend_shape) const; void set_blend_shape(BlendShapeEntry p_blend_shape, float p_value); PackedFloat32Array get_blend_shapes() const; void set_blend_shapes(const PackedFloat32Array &p_blend_shapes); + XRFaceTracker(); + protected: static void _bind_methods(); diff --git a/servers/xr/xr_hand_tracker.cpp b/servers/xr/xr_hand_tracker.cpp index 8cc2d5f7d2..cb0fbfb35f 100644 --- a/servers/xr/xr_hand_tracker.cpp +++ b/servers/xr/xr_hand_tracker.cpp @@ -30,6 +30,8 @@ #include "xr_hand_tracker.h" +#include "xr_body_tracker.h" + void XRHandTracker::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hand", "hand"), &XRHandTracker::set_hand); ClassDB::bind_method(D_METHOD("get_hand"), &XRHandTracker::get_hand); @@ -55,7 +57,6 @@ void XRHandTracker::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hand_joint_angular_velocity", "joint", "angular_velocity"), &XRHandTracker::set_hand_joint_angular_velocity); ClassDB::bind_method(D_METHOD("get_hand_joint_angular_velocity", "joint"), &XRHandTracker::get_hand_joint_angular_velocity); - ADD_PROPERTY(PropertyInfo(Variant::INT, "hand", PROPERTY_HINT_ENUM, "Left,Right"), "set_hand", "get_hand"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "has_tracking_data", PROPERTY_HINT_NONE), "set_has_tracking_data", "get_has_tracking_data"); ADD_PROPERTY(PropertyInfo(Variant::INT, "hand_tracking_source", PROPERTY_HINT_ENUM, "Unknown,Unobstructed,Controller"), "set_hand_tracking_source", "get_hand_tracking_source"); @@ -104,8 +105,49 @@ void XRHandTracker::_bind_methods() { BIND_BITFIELD_FLAG(HAND_JOINT_FLAG_ANGULAR_VELOCITY_VALID); } +void XRHandTracker::set_tracker_type(XRServer::TrackerType p_type) { + ERR_FAIL_COND_MSG(p_type != XRServer::TRACKER_HAND, "XRHandTracker must be of type TRACKER_HAND."); +} + +void XRHandTracker::set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) { + ERR_FAIL_INDEX(p_hand, TRACKER_HAND_MAX); + + switch (p_hand) { + case TRACKER_HAND_LEFT: + tracker_hand = TRACKER_HAND_LEFT; + hand = HAND_LEFT; + break; + + case TRACKER_HAND_RIGHT: + tracker_hand = TRACKER_HAND_RIGHT; + hand = HAND_RIGHT; + break; + + case TRACKER_HAND_UNKNOWN: + default: + ERR_FAIL_MSG("XRHandTracker must specify hand"); + break; + } +} + void XRHandTracker::set_hand(XRHandTracker::Hand p_hand) { - hand = p_hand; + ERR_FAIL_INDEX(p_hand, HAND_MAX); + + switch (p_hand) { + case HAND_LEFT: + tracker_hand = TRACKER_HAND_LEFT; + hand = HAND_LEFT; + break; + + case HAND_RIGHT: + tracker_hand = TRACKER_HAND_RIGHT; + hand = HAND_RIGHT; + break; + + default: + ERR_FAIL_MSG("XRHandTracker must specify hand"); + break; + } } XRHandTracker::Hand XRHandTracker::get_hand() const { @@ -177,3 +219,7 @@ Vector3 XRHandTracker::get_hand_joint_angular_velocity(XRHandTracker::HandJoint ERR_FAIL_INDEX_V(p_joint, HAND_JOINT_MAX, Vector3()); return hand_joint_angular_velocities[p_joint]; } + +XRHandTracker::XRHandTracker() { + type = XRServer::TRACKER_HAND; +} diff --git a/servers/xr/xr_hand_tracker.h b/servers/xr/xr_hand_tracker.h index 648f02d1f8..8ef3c229c3 100644 --- a/servers/xr/xr_hand_tracker.h +++ b/servers/xr/xr_hand_tracker.h @@ -31,10 +31,10 @@ #ifndef XR_HAND_TRACKER_H #define XR_HAND_TRACKER_H -#include "core/object/ref_counted.h" +#include "servers/xr/xr_positional_tracker.h" -class XRHandTracker : public RefCounted { - GDCLASS(XRHandTracker, RefCounted); +class XRHandTracker : public XRPositionalTracker { + GDCLASS(XRHandTracker, XRPositionalTracker); _THREAD_SAFE_CLASS_ public: @@ -90,6 +90,9 @@ public: HAND_JOINT_FLAG_ANGULAR_VELOCITY_VALID = 32, }; + void set_tracker_type(XRServer::TrackerType p_type) override; + void set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) override; + void set_hand(Hand p_hand); Hand get_hand() const; @@ -114,6 +117,8 @@ public: void set_hand_joint_angular_velocity(HandJoint p_joint, const Vector3 &p_velocity); Vector3 get_hand_joint_angular_velocity(HandJoint p_joint) const; + XRHandTracker(); + protected: static void _bind_methods(); diff --git a/servers/xr/xr_positional_tracker.cpp b/servers/xr/xr_positional_tracker.cpp index 6c15e4c1b0..b479237730 100644 --- a/servers/xr/xr_positional_tracker.cpp +++ b/servers/xr/xr_positional_tracker.cpp @@ -31,23 +31,13 @@ #include "xr_positional_tracker.h" #include "core/input/input.h" +#include "xr_controller_tracker.h" void XRPositionalTracker::_bind_methods() { BIND_ENUM_CONSTANT(TRACKER_HAND_UNKNOWN); BIND_ENUM_CONSTANT(TRACKER_HAND_LEFT); BIND_ENUM_CONSTANT(TRACKER_HAND_RIGHT); - - ClassDB::bind_method(D_METHOD("get_tracker_type"), &XRPositionalTracker::get_tracker_type); - ClassDB::bind_method(D_METHOD("set_tracker_type", "type"), &XRPositionalTracker::set_tracker_type); - ADD_PROPERTY(PropertyInfo(Variant::INT, "type"), "set_tracker_type", "get_tracker_type"); - - ClassDB::bind_method(D_METHOD("get_tracker_name"), &XRPositionalTracker::get_tracker_name); - ClassDB::bind_method(D_METHOD("set_tracker_name", "name"), &XRPositionalTracker::set_tracker_name); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "name"), "set_tracker_name", "get_tracker_name"); - - ClassDB::bind_method(D_METHOD("get_tracker_desc"), &XRPositionalTracker::get_tracker_desc); - ClassDB::bind_method(D_METHOD("set_tracker_desc", "description"), &XRPositionalTracker::set_tracker_desc); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_tracker_desc", "get_tracker_desc"); + BIND_ENUM_CONSTANT(TRACKER_HAND_MAX); ClassDB::bind_method(D_METHOD("get_tracker_profile"), &XRPositionalTracker::get_tracker_profile); ClassDB::bind_method(D_METHOD("set_tracker_profile", "profile"), &XRPositionalTracker::set_tracker_profile); @@ -73,34 +63,6 @@ void XRPositionalTracker::_bind_methods() { ADD_SIGNAL(MethodInfo("profile_changed", PropertyInfo(Variant::STRING, "role"))); }; -void XRPositionalTracker::set_tracker_type(XRServer::TrackerType p_type) { - if (type != p_type) { - type = p_type; - hand = XRPositionalTracker::TRACKER_HAND_UNKNOWN; - }; -}; - -XRServer::TrackerType XRPositionalTracker::get_tracker_type() const { - return type; -}; - -void XRPositionalTracker::set_tracker_name(const StringName &p_name) { - // Note: this should not be changed after the tracker is registered with the XRServer! - name = p_name; -}; - -StringName XRPositionalTracker::get_tracker_name() const { - return name; -}; - -void XRPositionalTracker::set_tracker_desc(const String &p_desc) { - description = p_desc; -} - -String XRPositionalTracker::get_tracker_desc() const { - return description; -} - void XRPositionalTracker::set_tracker_profile(const String &p_profile) { if (profile != p_profile) { profile = p_profile; @@ -114,19 +76,12 @@ String XRPositionalTracker::get_tracker_profile() const { } XRPositionalTracker::TrackerHand XRPositionalTracker::get_tracker_hand() const { - return hand; + return tracker_hand; }; void XRPositionalTracker::set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) { - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL(xr_server); - - if (hand != p_hand) { - // we can only set this if we've previously set this to be a controller!! - ERR_FAIL_COND((type != XRServer::TRACKER_CONTROLLER) && (p_hand != XRPositionalTracker::TRACKER_HAND_UNKNOWN)); - - hand = p_hand; - }; + ERR_FAIL_INDEX(p_hand, TRACKER_HAND_MAX); + tracker_hand = p_hand; }; bool XRPositionalTracker::has_pose(const StringName &p_action_name) const { @@ -177,6 +132,11 @@ void XRPositionalTracker::set_pose(const StringName &p_action_name, const Transf } Variant XRPositionalTracker::get_input(const StringName &p_action_name) const { + // Complain if this method is called on a XRPositionalTracker instance. + if (!dynamic_cast<const XRControllerTracker *>(this)) { + WARN_DEPRECATED_MSG(R"*(The "get_input()" method is deprecated, use "XRControllerTracker" instead.)*"); + } + if (inputs.has(p_action_name)) { return inputs[p_action_name]; } else { @@ -185,10 +145,13 @@ Variant XRPositionalTracker::get_input(const StringName &p_action_name) const { } void XRPositionalTracker::set_input(const StringName &p_action_name, const Variant &p_value) { - bool changed = false; + // Complain if this method is called on a XRPositionalTracker instance. + if (!dynamic_cast<XRControllerTracker *>(this)) { + WARN_DEPRECATED_MSG(R"*(The "set_input()" method is deprecated, use "XRControllerTracker" instead.)*"); + } // XR inputs - + bool changed; if (inputs.has(p_action_name)) { changed = inputs[p_action_name] != p_value; } else { @@ -227,9 +190,3 @@ void XRPositionalTracker::set_input(const StringName &p_action_name, const Varia } } } - -XRPositionalTracker::XRPositionalTracker() { - type = XRServer::TRACKER_UNKNOWN; - name = "Unknown"; - hand = TRACKER_HAND_UNKNOWN; -}; diff --git a/servers/xr/xr_positional_tracker.h b/servers/xr/xr_positional_tracker.h index d8939b4582..9e4e4d1cc3 100644 --- a/servers/xr/xr_positional_tracker.h +++ b/servers/xr/xr_positional_tracker.h @@ -34,6 +34,7 @@ #include "core/os/thread_safe.h" #include "scene/resources/mesh.h" #include "servers/xr/xr_pose.h" +#include "servers/xr/xr_tracker.h" #include "servers/xr_server.h" /** @@ -42,41 +43,33 @@ This is where potentially additional AR/VR interfaces may be active as there are AR/VR SDKs that solely deal with positional tracking. */ -class XRPositionalTracker : public RefCounted { - GDCLASS(XRPositionalTracker, RefCounted); +class XRPositionalTracker : public XRTracker { + GDCLASS(XRPositionalTracker, XRTracker); _THREAD_SAFE_CLASS_ public: enum TrackerHand { TRACKER_HAND_UNKNOWN, /* unknown or not applicable */ TRACKER_HAND_LEFT, /* controller is the left hand controller */ - TRACKER_HAND_RIGHT /* controller is the right hand controller */ + TRACKER_HAND_RIGHT, /* controller is the right hand controller */ + TRACKER_HAND_MAX }; -private: - XRServer::TrackerType type; // type of tracker - StringName name; // (unique) name of the tracker - String description; // description of the tracker +protected: String profile; // this is interface dependent, for OpenXR this will be the interaction profile bound for to the tracker - TrackerHand hand; // if known, the hand this tracker is held in + TrackerHand tracker_hand = TRACKER_HAND_UNKNOWN; // if known, the hand this tracker is held in HashMap<StringName, Ref<XRPose>> poses; HashMap<StringName, Variant> inputs; -protected: static void _bind_methods(); public: - void set_tracker_type(XRServer::TrackerType p_type); - XRServer::TrackerType get_tracker_type() const; - void set_tracker_name(const StringName &p_name); - StringName get_tracker_name() const; - void set_tracker_desc(const String &p_desc); - String get_tracker_desc() const; void set_tracker_profile(const String &p_profile); String get_tracker_profile() const; + XRPositionalTracker::TrackerHand get_tracker_hand() const; - void set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand); + virtual void set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand); bool has_pose(const StringName &p_action_name) const; Ref<XRPose> get_pose(const StringName &p_action_name) const; @@ -85,9 +78,6 @@ public: Variant get_input(const StringName &p_action_name) const; void set_input(const StringName &p_action_name, const Variant &p_value); - - XRPositionalTracker(); - ~XRPositionalTracker() {} }; VARIANT_ENUM_CAST(XRPositionalTracker::TrackerHand); diff --git a/servers/xr/xr_tracker.cpp b/servers/xr/xr_tracker.cpp new file mode 100644 index 0000000000..0b917a5dc3 --- /dev/null +++ b/servers/xr/xr_tracker.cpp @@ -0,0 +1,70 @@ +/**************************************************************************/ +/* xr_tracker.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_tracker.h" + +void XRTracker::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_tracker_type"), &XRTracker::get_tracker_type); + ClassDB::bind_method(D_METHOD("set_tracker_type", "type"), &XRTracker::set_tracker_type); + ADD_PROPERTY(PropertyInfo(Variant::INT, "type"), "set_tracker_type", "get_tracker_type"); + + ClassDB::bind_method(D_METHOD("get_tracker_name"), &XRTracker::get_tracker_name); + ClassDB::bind_method(D_METHOD("set_tracker_name", "name"), &XRTracker::set_tracker_name); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "name"), "set_tracker_name", "get_tracker_name"); + + ClassDB::bind_method(D_METHOD("get_tracker_desc"), &XRTracker::get_tracker_desc); + ClassDB::bind_method(D_METHOD("set_tracker_desc", "description"), &XRTracker::set_tracker_desc); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_tracker_desc", "get_tracker_desc"); +}; + +void XRTracker::set_tracker_type(XRServer::TrackerType p_type) { + type = p_type; +}; + +XRServer::TrackerType XRTracker::get_tracker_type() const { + return type; +}; + +void XRTracker::set_tracker_name(const StringName &p_name) { + // Note: this should not be changed after the tracker is registered with the XRServer! + name = p_name; +}; + +StringName XRTracker::get_tracker_name() const { + return name; +}; + +void XRTracker::set_tracker_desc(const String &p_desc) { + description = p_desc; +} + +String XRTracker::get_tracker_desc() const { + return description; +} diff --git a/servers/xr/xr_tracker.h b/servers/xr/xr_tracker.h new file mode 100644 index 0000000000..3348e164d8 --- /dev/null +++ b/servers/xr/xr_tracker.h @@ -0,0 +1,61 @@ +/**************************************************************************/ +/* xr_tracker.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 XR_TRACKER_H +#define XR_TRACKER_H + +#include "core/os/thread_safe.h" +#include "servers/xr_server.h" + +/** + The XR tracker object is a common base for all different types of XR trackers. +*/ + +class XRTracker : public RefCounted { + GDCLASS(XRTracker, RefCounted); + _THREAD_SAFE_CLASS_ + +protected: + XRServer::TrackerType type = XRServer::TRACKER_UNKNOWN; // type of tracker + StringName name = "Unknown"; // (unique) name of the tracker + String description; // description of the tracker + + static void _bind_methods(); + +public: + virtual void set_tracker_type(XRServer::TrackerType p_type); + XRServer::TrackerType get_tracker_type() const; + void set_tracker_name(const StringName &p_name); + StringName get_tracker_name() const; + void set_tracker_desc(const String &p_desc); + String get_tracker_desc() const; +}; + +#endif // XR_TRACKER_H diff --git a/servers/xr_server.compat.inc b/servers/xr_server.compat.inc new file mode 100644 index 0000000000..967666fa6f --- /dev/null +++ b/servers/xr_server.compat.inc @@ -0,0 +1,51 @@ +/**************************************************************************/ +/* xr_server.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 DISABLE_DEPRECATED + +void XRServer::_add_tracker_bind_compat_90645(const Ref<XRPositionalTracker> &p_tracker) { + add_tracker(p_tracker); +} + +void XRServer::_remove_tracker_bind_compat_90645(const Ref<XRPositionalTracker> &p_tracker) { + remove_tracker(p_tracker); +} + +Ref<XRPositionalTracker> XRServer::_get_tracker_bind_compat_90645(const StringName &p_name) const { + return get_tracker(p_name); +} + +void XRServer::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("add_tracker", "tracker"), &XRServer::_add_tracker_bind_compat_90645); + ClassDB::bind_compatibility_method(D_METHOD("remove_tracker", "tracker"), &XRServer::_remove_tracker_bind_compat_90645); + ClassDB::bind_compatibility_method(D_METHOD("get_tracker", "name"), &XRServer::_get_tracker_bind_compat_90645); +} + +#endif // DISABLE_DEPRECATED diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp index af14ba4a00..f1105a650d 100644 --- a/servers/xr_server.cpp +++ b/servers/xr_server.cpp @@ -35,6 +35,7 @@ #include "xr/xr_hand_tracker.h" #include "xr/xr_interface.h" #include "xr/xr_positional_tracker.h" +#include "xr_server.compat.inc" XRServer::XRMode XRServer::xr_mode = XRMODE_DEFAULT; @@ -77,21 +78,6 @@ void XRServer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_trackers", "tracker_types"), &XRServer::get_trackers); ClassDB::bind_method(D_METHOD("get_tracker", "tracker_name"), &XRServer::get_tracker); - ClassDB::bind_method(D_METHOD("add_hand_tracker", "tracker_name", "hand_tracker"), &XRServer::add_hand_tracker); - ClassDB::bind_method(D_METHOD("remove_hand_tracker", "tracker_name"), &XRServer::remove_hand_tracker); - ClassDB::bind_method(D_METHOD("get_hand_trackers"), &XRServer::get_hand_trackers); - ClassDB::bind_method(D_METHOD("get_hand_tracker", "tracker_name"), &XRServer::get_hand_tracker); - - ClassDB::bind_method(D_METHOD("add_face_tracker", "tracker_name", "face_tracker"), &XRServer::add_face_tracker); - ClassDB::bind_method(D_METHOD("remove_face_tracker", "tracker_name"), &XRServer::remove_face_tracker); - ClassDB::bind_method(D_METHOD("get_face_trackers"), &XRServer::get_face_trackers); - ClassDB::bind_method(D_METHOD("get_face_tracker", "tracker_name"), &XRServer::get_face_tracker); - - ClassDB::bind_method(D_METHOD("add_body_tracker", "tracker_name", "body_tracker"), &XRServer::add_body_tracker); - ClassDB::bind_method(D_METHOD("remove_body_tracker", "tracker_name"), &XRServer::remove_body_tracker); - ClassDB::bind_method(D_METHOD("get_body_trackers"), &XRServer::get_body_trackers); - ClassDB::bind_method(D_METHOD("get_body_tracker", "tracker_name"), &XRServer::get_body_tracker); - ClassDB::bind_method(D_METHOD("get_primary_interface"), &XRServer::get_primary_interface); ClassDB::bind_method(D_METHOD("set_primary_interface", "interface"), &XRServer::set_primary_interface); @@ -101,6 +87,9 @@ void XRServer::_bind_methods() { BIND_ENUM_CONSTANT(TRACKER_CONTROLLER); BIND_ENUM_CONSTANT(TRACKER_BASESTATION); BIND_ENUM_CONSTANT(TRACKER_ANCHOR); + BIND_ENUM_CONSTANT(TRACKER_HAND); + BIND_ENUM_CONSTANT(TRACKER_BODY); + BIND_ENUM_CONSTANT(TRACKER_FACE); BIND_ENUM_CONSTANT(TRACKER_ANY_KNOWN); BIND_ENUM_CONSTANT(TRACKER_UNKNOWN); BIND_ENUM_CONSTANT(TRACKER_ANY); @@ -115,18 +104,6 @@ void XRServer::_bind_methods() { ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"))); ADD_SIGNAL(MethodInfo("tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"))); ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"))); - - ADD_SIGNAL(MethodInfo("hand_tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "hand_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRHandTracker"))); - ADD_SIGNAL(MethodInfo("hand_tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "hand_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRHandTracker"))); - ADD_SIGNAL(MethodInfo("hand_tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"))); - - ADD_SIGNAL(MethodInfo("face_tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "face_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRFaceTracker"))); - ADD_SIGNAL(MethodInfo("face_tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "face_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRFaceTracker"))); - ADD_SIGNAL(MethodInfo("face_tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"))); - - ADD_SIGNAL(MethodInfo("body_tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "body_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRBodyTracker"))); - ADD_SIGNAL(MethodInfo("body_tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "body_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRBodyTracker"))); - ADD_SIGNAL(MethodInfo("body_tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"))); }; double XRServer::get_world_scale() const { @@ -281,7 +258,7 @@ void XRServer::set_primary_interface(const Ref<XRInterface> &p_primary_interface } }; -void XRServer::add_tracker(Ref<XRPositionalTracker> p_tracker) { +void XRServer::add_tracker(const Ref<XRTracker> &p_tracker) { ERR_FAIL_COND(p_tracker.is_null()); StringName tracker_name = p_tracker->get_tracker_name(); @@ -297,7 +274,7 @@ void XRServer::add_tracker(Ref<XRPositionalTracker> p_tracker) { } }; -void XRServer::remove_tracker(Ref<XRPositionalTracker> p_tracker) { +void XRServer::remove_tracker(const Ref<XRTracker> &p_tracker) { ERR_FAIL_COND(p_tracker.is_null()); StringName tracker_name = p_tracker->get_tracker_name(); @@ -323,12 +300,12 @@ Dictionary XRServer::get_trackers(int p_tracker_types) { return res; } -Ref<XRPositionalTracker> XRServer::get_tracker(const StringName &p_name) const { +Ref<XRTracker> XRServer::get_tracker(const StringName &p_name) const { if (trackers.has(p_name)) { return trackers[p_name]; } else { // tracker hasn't been registered yet, which is fine, no need to spam the error log... - return Ref<XRPositionalTracker>(); + return Ref<XRTracker>(); } }; @@ -382,120 +359,6 @@ PackedStringArray XRServer::get_suggested_pose_names(const StringName &p_tracker return arr; } -void XRServer::add_hand_tracker(const StringName &p_tracker_name, Ref<XRHandTracker> p_hand_tracker) { - ERR_FAIL_COND(p_hand_tracker.is_null()); - - if (!hand_trackers.has(p_tracker_name)) { - // We don't have a tracker with this name, we're going to add it. - hand_trackers[p_tracker_name] = p_hand_tracker; - emit_signal(SNAME("hand_tracker_added"), p_tracker_name, p_hand_tracker); - } else if (hand_trackers[p_tracker_name] != p_hand_tracker) { - // We already have a tracker with this name, we're going to replace it. - hand_trackers[p_tracker_name] = p_hand_tracker; - emit_signal(SNAME("hand_tracker_updated"), p_tracker_name, p_hand_tracker); - } -} - -void XRServer::remove_hand_tracker(const StringName &p_tracker_name) { - // Skip if no hand tracker is found. - if (!hand_trackers.has(p_tracker_name)) { - return; - } - - // Send the removed signal, then remove the hand tracker. - emit_signal(SNAME("hand_tracker_removed"), p_tracker_name); - hand_trackers.erase(p_tracker_name); -} - -Dictionary XRServer::get_hand_trackers() const { - return hand_trackers; -} - -Ref<XRHandTracker> XRServer::get_hand_tracker(const StringName &p_tracker_name) const { - // Skip if no tracker is found. - if (!hand_trackers.has(p_tracker_name)) { - return Ref<XRHandTracker>(); - } - - return hand_trackers[p_tracker_name]; -} - -void XRServer::add_face_tracker(const StringName &p_tracker_name, Ref<XRFaceTracker> p_face_tracker) { - ERR_FAIL_COND(p_face_tracker.is_null()); - - if (!face_trackers.has(p_tracker_name)) { - // We don't have a tracker with this name, we're going to add it. - face_trackers[p_tracker_name] = p_face_tracker; - emit_signal(SNAME("face_tracker_added"), p_tracker_name, p_face_tracker); - } else if (face_trackers[p_tracker_name] != p_face_tracker) { - // We already have a tracker with this name, we're going to replace it. - face_trackers[p_tracker_name] = p_face_tracker; - emit_signal(SNAME("face_tracker_updated"), p_tracker_name, p_face_tracker); - } -} - -void XRServer::remove_face_tracker(const StringName &p_tracker_name) { - // Skip if no face tracker is found. - if (!face_trackers.has(p_tracker_name)) { - return; - } - - // Send the removed signal, then remove the face tracker. - emit_signal(SNAME("face_tracker_removed"), p_tracker_name); - face_trackers.erase(p_tracker_name); -} - -Dictionary XRServer::get_face_trackers() const { - return face_trackers; -} - -Ref<XRFaceTracker> XRServer::get_face_tracker(const StringName &p_tracker_name) const { - // Skip if no tracker is found. - if (!face_trackers.has(p_tracker_name)) { - return Ref<XRFaceTracker>(); - } - - return face_trackers[p_tracker_name]; -} - -void XRServer::add_body_tracker(const StringName &p_tracker_name, Ref<XRBodyTracker> p_body_tracker) { - ERR_FAIL_COND(p_body_tracker.is_null()); - - if (!body_trackers.has(p_tracker_name)) { - // We don't have a tracker with this name, we're going to add it. - body_trackers[p_tracker_name] = p_body_tracker; - emit_signal(SNAME("body_tracker_added"), p_tracker_name, p_body_tracker); - } else if (body_trackers[p_tracker_name] != p_body_tracker) { - // We already have a tracker with this name, we're going to replace it. - body_trackers[p_tracker_name] = p_body_tracker; - emit_signal(SNAME("body_tracker_updated"), p_tracker_name, p_body_tracker); - } -} - -void XRServer::remove_body_tracker(const StringName &p_tracker_name) { - // Skip if no face tracker is found. - if (!body_trackers.has(p_tracker_name)) { - return; - } - - // Send the removed signal, then remove the face tracker. - emit_signal(SNAME("body_tracker_removed"), p_tracker_name); - body_trackers.erase(p_tracker_name); -} - -Dictionary XRServer::get_body_trackers() const { - return body_trackers; -} - -Ref<XRBodyTracker> XRServer::get_body_tracker(const StringName &p_tracker_name) const { - // Skip if no tracker is found. - if (!body_trackers.has(p_tracker_name)) { - return Ref<XRBodyTracker>(); - } - - return body_trackers[p_tracker_name]; -} - void XRServer::_process() { // called from our main game loop before we handle physics and game logic // note that we can have multiple interfaces active if we have interfaces that purely handle tracking @@ -545,14 +408,8 @@ XRServer::XRServer() { XRServer::~XRServer() { primary_interface.unref(); - while (interfaces.size() > 0) { - interfaces.remove_at(0); - } - - // TODO pretty sure there is a clear function or something... - while (trackers.size() > 0) { - trackers.erase(trackers.get_key_at_index(0)); - } + interfaces.clear(); + trackers.clear(); singleton = nullptr; }; diff --git a/servers/xr_server.h b/servers/xr_server.h index 6aaa34b21d..717728171a 100644 --- a/servers/xr_server.h +++ b/servers/xr_server.h @@ -38,10 +38,8 @@ #include "core/variant/variant.h" class XRInterface; +class XRTracker; class XRPositionalTracker; -class XRHandTracker; -class XRFaceTracker; -class XRBodyTracker; /** The XR server is a singleton object that gives access to the various @@ -71,6 +69,9 @@ public: TRACKER_CONTROLLER = 0x02, /* tracks a controller */ TRACKER_BASESTATION = 0x04, /* tracks location of a base station */ TRACKER_ANCHOR = 0x08, /* tracks an anchor point, used in AR to track a real live location */ + TRACKER_HAND = 0x10, /* tracks a hand */ + TRACKER_BODY = 0x20, /* tracks a body */ + TRACKER_FACE = 0x40, /* tracks a face */ TRACKER_UNKNOWN = 0x80, /* unknown tracker */ TRACKER_ANY_KNOWN = 0x7f, /* all except unknown */ @@ -88,9 +89,6 @@ private: Vector<Ref<XRInterface>> interfaces; Dictionary trackers; - Dictionary hand_trackers; - Dictionary face_trackers; - Dictionary body_trackers; Ref<XRInterface> primary_interface; /* we'll identify one interface as primary, this will be used by our viewports */ @@ -103,6 +101,13 @@ protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + static void _bind_compatibility_methods(); + void _add_tracker_bind_compat_90645(const Ref<XRPositionalTracker> &p_tracker); + void _remove_tracker_bind_compat_90645(const Ref<XRPositionalTracker> &p_tracker); + Ref<XRPositionalTracker> _get_tracker_bind_compat_90645(const StringName &p_name) const; +#endif + public: static XRMode get_xr_mode(); static void set_xr_mode(XRMode p_mode); @@ -174,13 +179,13 @@ public: void set_primary_interface(const Ref<XRInterface> &p_primary_interface); /* - Our trackers are objects that expose the orientation and position of physical devices such as controller, anchor points, etc. + Our trackers are objects that expose tracked information about physical objects such as controller, anchor points, faces, hands etc. They are created and managed by our active AR/VR interfaces. */ - void add_tracker(Ref<XRPositionalTracker> p_tracker); - void remove_tracker(Ref<XRPositionalTracker> p_tracker); + void add_tracker(const Ref<XRTracker> &p_tracker); + void remove_tracker(const Ref<XRTracker> &p_tracker); Dictionary get_trackers(int p_tracker_types); - Ref<XRPositionalTracker> get_tracker(const StringName &p_name) const; + Ref<XRTracker> get_tracker(const StringName &p_name) const; /* We don't know which trackers and actions will existing during runtime but we can request suggested names from our interfaces to help our IDE UI. @@ -189,30 +194,6 @@ public: PackedStringArray get_suggested_pose_names(const StringName &p_tracker_name) const; // Q: Should we add get_suggested_input_names and get_suggested_haptic_names even though we don't use them for the IDE? - /* - Hand trackers are objects that expose the tracked joints of a hand. - */ - void add_hand_tracker(const StringName &p_tracker_name, Ref<XRHandTracker> p_hand_tracker); - void remove_hand_tracker(const StringName &p_tracker_name); - Dictionary get_hand_trackers() const; - Ref<XRHandTracker> get_hand_tracker(const StringName &p_tracker_name) const; - - /* - Face trackers are objects that expose the tracked blend shapes of a face. - */ - void add_face_tracker(const StringName &p_tracker_name, Ref<XRFaceTracker> p_face_tracker); - void remove_face_tracker(const StringName &p_tracker_name); - Dictionary get_face_trackers() const; - Ref<XRFaceTracker> get_face_tracker(const StringName &p_tracker_name) const; - - /* - Body trackers are objects that expose the tracked joints of a body. - */ - void add_body_tracker(const StringName &p_tracker_name, Ref<XRBodyTracker> p_face_tracker); - void remove_body_tracker(const StringName &p_tracker_name); - Dictionary get_body_trackers() const; - Ref<XRBodyTracker> get_body_tracker(const StringName &p_tracker_name) const; - // Process is called before we handle our physics process and game process. This is where our interfaces will update controller data and such. void _process(); diff --git a/tests/core/io/test_resource.h b/tests/core/io/test_resource.h index 9ddb51220b..a83e7f88ba 100644 --- a/tests/core/io/test_resource.h +++ b/tests/core/io/test_resource.h @@ -38,6 +38,8 @@ #include "thirdparty/doctest/doctest.h" +#include "tests/test_macros.h" + namespace TestResource { TEST_CASE("[Resource] Duplication") { @@ -124,9 +126,12 @@ TEST_CASE("[Resource] Breaking circular references on save") { const String save_path_binary = OS::get_singleton()->get_cache_path().path_join("resource.res"); const String save_path_text = OS::get_singleton()->get_cache_path().path_join("resource.tres"); ResourceSaver::save(resource_a, save_path_binary); + // Suppress expected errors caused by the resources above being uncached. + ERR_PRINT_OFF; ResourceSaver::save(resource_a, save_path_text); const Ref<Resource> &loaded_resource_a_binary = ResourceLoader::load(save_path_binary); + ERR_PRINT_ON; CHECK_MESSAGE( loaded_resource_a_binary->get_name() == "A", "The loaded resource name should be equal to the expected value."); diff --git a/tests/core/math/test_geometry_2d.h b/tests/core/math/test_geometry_2d.h index 50b2575700..a4bb6dfca0 100644 --- a/tests/core/math/test_geometry_2d.h +++ b/tests/core/math/test_geometry_2d.h @@ -711,12 +711,12 @@ TEST_CASE("[Geometry2D] Clip polyline with polygon") { r = Geometry2D::clip_polyline_with_polygon(l, p); REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting clipped lines."); REQUIRE_MESSAGE(r[0].size() == 3, "The resulting clipped line should have 3 vertices."); - CHECK(r[0][0].is_equal_approx(Vector2(160, 320))); + CHECK(r[0][0].is_equal_approx(Vector2(121.412682, 225.038757))); CHECK(r[0][1].is_equal_approx(Vector2(122, 250))); - CHECK(r[0][2].is_equal_approx(Vector2(121.412682, 225.038757))); + CHECK(r[0][2].is_equal_approx(Vector2(160, 320))); REQUIRE_MESSAGE(r[1].size() == 2, "The resulting clipped line should have 2 vertices."); - CHECK(r[1][0].is_equal_approx(Vector2(53.07737, 116.143021))); - CHECK(r[1][1].is_equal_approx(Vector2(55, 70))); + CHECK(r[1][0].is_equal_approx(Vector2(55, 70))); + CHECK(r[1][1].is_equal_approx(Vector2(53.07737, 116.143021))); } } diff --git a/tests/core/math/test_transform_3d.h b/tests/core/math/test_transform_3d.h index 551b20fe74..fba0fcb280 100644 --- a/tests/core/math/test_transform_3d.h +++ b/tests/core/math/test_transform_3d.h @@ -107,6 +107,35 @@ TEST_CASE("[Transform3D] Finite number checks") { "Transform3D with two components infinite should not be finite."); } +TEST_CASE("[Transform3D] Rotate around global origin") { + // Start with the default orientation, but not centered on the origin. + // Rotating should rotate both our basis and the origin. + Transform3D transform = Transform3D(); + transform.origin = Vector3(0, 0, 1); + + Transform3D expected = Transform3D(); + expected.origin = Vector3(0, 0, -1); + expected.basis[0] = Vector3(-1, 0, 0); + expected.basis[2] = Vector3(0, 0, -1); + + const Transform3D rotated_transform = transform.rotated(Vector3(0, 1, 0), Math_PI); + CHECK_MESSAGE(rotated_transform.is_equal_approx(expected), "The rotated transform should have a new orientation and basis."); +} + +TEST_CASE("[Transform3D] Rotate in-place (local rotation)") { + // Start with the default orientation. + // Local rotation should not change the origin, only the basis. + Transform3D transform = Transform3D(); + transform.origin = Vector3(1, 2, 3); + + Transform3D expected = Transform3D(); + expected.origin = Vector3(1, 2, 3); + expected.basis[0] = Vector3(-1, 0, 0); + expected.basis[2] = Vector3(0, 0, -1); + + const Transform3D rotated_transform = Transform3D(transform.rotated_local(Vector3(0, 1, 0), Math_PI)); + CHECK_MESSAGE(rotated_transform.is_equal_approx(expected), "The rotated transform should have a new orientation but still be based on the same origin."); +} } // namespace TestTransform3D #endif // TEST_TRANSFORM_3D_H diff --git a/thirdparty/misc/clipper.cpp b/thirdparty/misc/clipper.cpp deleted file mode 100644 index c67045d113..0000000000 --- a/thirdparty/misc/clipper.cpp +++ /dev/null @@ -1,4661 +0,0 @@ -/******************************************************************************* -* * -* Author : Angus Johnson * -* Version : 6.4.2 * -* Date : 27 February 2017 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2017 * -* * -* License: * -* Use, modification & distribution is subject to Boost Software License Ver 1. * -* http://www.boost.org/LICENSE_1_0.txt * -* * -* Attributions: * -* The code in this library is an extension of Bala Vatti's clipping algorithm: * -* "A generic solution to polygon clipping" * -* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * -* http://portal.acm.org/citation.cfm?id=129906 * -* * -* Computer graphics and geometric modeling: implementation and algorithms * -* By Max K. Agoston * -* Springer; 1 edition (January 4, 2005) * -* http://books.google.com/books?q=vatti+clipping+agoston * -* * -* See also: * -* "Polygon Offsetting by Computing Winding Numbers" * -* Paper no. DETC2005-85513 pp. 565-575 * -* ASME 2005 International Design Engineering Technical Conferences * -* and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24-28, 2005 , Long Beach, California, USA * -* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * -* * -*******************************************************************************/ - -/******************************************************************************* -* * -* This is a translation of the Delphi Clipper library and the naming style * -* used has retained a Delphi flavour. * -* * -*******************************************************************************/ - -#include "clipper.hpp" -#include <cmath> -#include <vector> -#include <algorithm> -#include <stdexcept> -#include <cstring> -#include <cstdlib> -#include <ostream> -#include <functional> - -//Explicitly disables exceptions handling for target platform -//#define CLIPPER_NOEXCEPTION - -#define CLIPPER_THROW(exception) std::abort() -#define CLIPPER_TRY if(true) -#define CLIPPER_CATCH(exception) if(false) - -#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) - #ifndef CLIPPER_NOEXCEPTION - #undef CLIPPER_THROW - #define CLIPPER_THROW(exception) throw exception - #undef CLIPPER_TRY - #define CLIPPER_TRY try - #undef CLIPPER_CATCH - #define CLIPPER_CATCH(exception) catch(exception) - #endif -#endif - -//Optionally allows to override exception macros -#if defined(CLIPPER_THROW_USER) - #undef CLIPPER_THROW - #define CLIPPER_THROW CLIPPER_THROW_USER -#endif -#if defined(CLIPPER_TRY_USER) - #undef CLIPPER_TRY - #define CLIPPER_TRY CLIPPER_TRY_USER -#endif -#if defined(CLIPPER_CATCH_USER) - #undef CLIPPER_CATCH - #define CLIPPER_CATCH CLIPPER_CATCH_USER -#endif - -namespace ClipperLib { - -static double const pi = 3.141592653589793238; -static double const two_pi = pi *2; -static double const def_arc_tolerance = 0.25; - -enum Direction { dRightToLeft, dLeftToRight }; - -static int const Unassigned = -1; //edge not currently 'owning' a solution -static int const Skip = -2; //edge that would otherwise close a path - -#define HORIZONTAL (-1.0E+40) -#define TOLERANCE (1.0e-20) -#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) - -struct TEdge { - IntPoint Bot; - IntPoint Curr; //current (updated for every new scanbeam) - IntPoint Top; - double Dx; - PolyType PolyTyp; - EdgeSide Side; //side only refers to current side of solution poly - int WindDelta; //1 or -1 depending on winding direction - int WindCnt; - int WindCnt2; //winding count of the opposite polytype - int OutIdx; - TEdge *Next; - TEdge *Prev; - TEdge *NextInLML; - TEdge *NextInAEL; - TEdge *PrevInAEL; - TEdge *NextInSEL; - TEdge *PrevInSEL; -}; - -struct IntersectNode { - TEdge *Edge1; - TEdge *Edge2; - IntPoint Pt; -}; - -struct LocalMinimum { - cInt Y; - TEdge *LeftBound; - TEdge *RightBound; -}; - -struct OutPt; - -//OutRec: contains a path in the clipping solution. Edges in the AEL will -//carry a pointer to an OutRec when they are part of the clipping solution. -struct OutRec { - int Idx; - bool IsHole; - bool IsOpen; - OutRec *FirstLeft; //see comments in clipper.pas - PolyNode *PolyNd; - OutPt *Pts; - OutPt *BottomPt; -}; - -struct OutPt { - int Idx; - IntPoint Pt; - OutPt *Next; - OutPt *Prev; -}; - -struct Join { - OutPt *OutPt1; - OutPt *OutPt2; - IntPoint OffPt; -}; - -struct LocMinSorter -{ - inline bool operator()(const LocalMinimum& locMin1, const LocalMinimum& locMin2) - { - return locMin2.Y < locMin1.Y; - } -}; - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - -inline cInt Round(double val) -{ - if ((val < 0)) return static_cast<cInt>(val - 0.5); - else return static_cast<cInt>(val + 0.5); -} -//------------------------------------------------------------------------------ - -inline cInt Abs(cInt val) -{ - return val < 0 ? -val : val; -} - -//------------------------------------------------------------------------------ -// PolyTree methods ... -//------------------------------------------------------------------------------ - -void PolyTree::Clear() -{ - for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) - delete AllNodes[i]; - AllNodes.resize(0); - Childs.resize(0); -} -//------------------------------------------------------------------------------ - -PolyNode* PolyTree::GetFirst() const -{ - if (!Childs.empty()) - return Childs[0]; - else - return 0; -} -//------------------------------------------------------------------------------ - -int PolyTree::Total() const -{ - int result = (int)AllNodes.size(); - //with negative offsets, ignore the hidden outer polygon ... - if (result > 0 && Childs[0] != AllNodes[0]) result--; - return result; -} - -//------------------------------------------------------------------------------ -// PolyNode methods ... -//------------------------------------------------------------------------------ - -PolyNode::PolyNode(): Parent(0), Index(0), m_IsOpen(false) -{ -} -//------------------------------------------------------------------------------ - -int PolyNode::ChildCount() const -{ - return (int)Childs.size(); -} -//------------------------------------------------------------------------------ - -void PolyNode::AddChild(PolyNode& child) -{ - unsigned cnt = (unsigned)Childs.size(); - Childs.push_back(&child); - child.Parent = this; - child.Index = cnt; -} -//------------------------------------------------------------------------------ - -PolyNode* PolyNode::GetNext() const -{ - if (!Childs.empty()) - return Childs[0]; - else - return GetNextSiblingUp(); -} -//------------------------------------------------------------------------------ - -PolyNode* PolyNode::GetNextSiblingUp() const -{ - if (!Parent) //protects against PolyTree.GetNextSiblingUp() - return 0; - else if (Index == Parent->Childs.size() - 1) - return Parent->GetNextSiblingUp(); - else - return Parent->Childs[Index + 1]; -} -//------------------------------------------------------------------------------ - -bool PolyNode::IsHole() const -{ - bool result = true; - PolyNode* node = Parent; - while (node) - { - result = !result; - node = node->Parent; - } - return result; -} -//------------------------------------------------------------------------------ - -bool PolyNode::IsOpen() const -{ - return m_IsOpen; -} -//------------------------------------------------------------------------------ - -#ifndef use_int32 - -//------------------------------------------------------------------------------ -// Int128 class (enables safe math on signed 64bit integers) -// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 -// Int128 val2((long64)9223372036854775807); -// Int128 val3 = val1 * val2; -// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) -//------------------------------------------------------------------------------ - -class Int128 -{ - public: - ulong64 lo; - long64 hi; - - Int128(long64 _lo = 0) - { - lo = (ulong64)_lo; - if (_lo < 0) hi = -1; else hi = 0; - } - - - Int128(const Int128 &val): lo(val.lo), hi(val.hi){} - - Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} - - Int128& operator = (const long64 &val) - { - lo = (ulong64)val; - if (val < 0) hi = -1; else hi = 0; - return *this; - } - - bool operator == (const Int128 &val) const - {return (hi == val.hi && lo == val.lo);} - - bool operator != (const Int128 &val) const - { return !(*this == val);} - - bool operator > (const Int128 &val) const - { - if (hi != val.hi) - return hi > val.hi; - else - return lo > val.lo; - } - - bool operator < (const Int128 &val) const - { - if (hi != val.hi) - return hi < val.hi; - else - return lo < val.lo; - } - - bool operator >= (const Int128 &val) const - { return !(*this < val);} - - bool operator <= (const Int128 &val) const - { return !(*this > val);} - - Int128& operator += (const Int128 &rhs) - { - hi += rhs.hi; - lo += rhs.lo; - if (lo < rhs.lo) hi++; - return *this; - } - - Int128 operator + (const Int128 &rhs) const - { - Int128 result(*this); - result+= rhs; - return result; - } - - Int128& operator -= (const Int128 &rhs) - { - *this += -rhs; - return *this; - } - - Int128 operator - (const Int128 &rhs) const - { - Int128 result(*this); - result -= rhs; - return result; - } - - Int128 operator-() const //unary negation - { - if (lo == 0) - return Int128(-hi, 0); - else - return Int128(~hi, ~lo + 1); - } - - operator double() const - { - const double shift64 = 18446744073709551616.0; //2^64 - if (hi < 0) - { - if (lo == 0) return (double)hi * shift64; - else return -(double)(~lo + ~hi * shift64); - } - else - return (double)(lo + hi * shift64); - } - -}; -//------------------------------------------------------------------------------ - -Int128 Int128Mul (long64 lhs, long64 rhs) -{ - bool negate = (lhs < 0) != (rhs < 0); - - if (lhs < 0) lhs = -lhs; - ulong64 int1Hi = ulong64(lhs) >> 32; - ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); - - if (rhs < 0) rhs = -rhs; - ulong64 int2Hi = ulong64(rhs) >> 32; - ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); - - //nb: see comments in clipper.pas - ulong64 a = int1Hi * int2Hi; - ulong64 b = int1Lo * int2Lo; - ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; - - Int128 tmp; - tmp.hi = long64(a + (c >> 32)); - tmp.lo = long64(c << 32); - tmp.lo += long64(b); - if (tmp.lo < b) tmp.hi++; - if (negate) tmp = -tmp; - return tmp; -}; -#endif - -//------------------------------------------------------------------------------ -// Miscellaneous global functions -//------------------------------------------------------------------------------ - -bool Orientation(const Path &poly) -{ - return Area(poly) >= 0; -} -//------------------------------------------------------------------------------ - -double Area(const Path &poly) -{ - int size = (int)poly.size(); - if (size < 3) return 0; - - double a = 0; - for (int i = 0, j = size -1; i < size; ++i) - { - a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); - j = i; - } - return -a * 0.5; -} -//------------------------------------------------------------------------------ - -double Area(const OutPt *op) -{ - const OutPt *startOp = op; - if (!op) return 0; - double a = 0; - do { - a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); - op = op->Next; - } while (op != startOp); - return a * 0.5; -} -//------------------------------------------------------------------------------ - -double Area(const OutRec &outRec) -{ - return Area(outRec.Pts); -} -//------------------------------------------------------------------------------ - -bool PointIsVertex(const IntPoint &Pt, OutPt *pp) -{ - OutPt *pp2 = pp; - do - { - if (pp2->Pt == Pt) return true; - pp2 = pp2->Next; - } - while (pp2 != pp); - return false; -} -//------------------------------------------------------------------------------ - -//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos -//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf -int PointInPolygon(const IntPoint &pt, const Path &path) -{ - //returns 0 if false, +1 if true, -1 if pt ON polygon boundary - int result = 0; - size_t cnt = path.size(); - if (cnt < 3) return 0; - IntPoint ip = path[0]; - for(size_t i = 1; i <= cnt; ++i) - { - IntPoint ipNext = (i == cnt ? path[0] : path[i]); - if (ipNext.Y == pt.Y) - { - if ((ipNext.X == pt.X) || (ip.Y == pt.Y && - ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1; - } - if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) - { - if (ip.X >= pt.X) - { - if (ipNext.X > pt.X) result = 1 - result; - else - { - double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; - } - } else - { - if (ipNext.X > pt.X) - { - double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; - } - } - } - ip = ipNext; - } - return result; -} -//------------------------------------------------------------------------------ - -int PointInPolygon (const IntPoint &pt, OutPt *op) -{ - //returns 0 if false, +1 if true, -1 if pt ON polygon boundary - int result = 0; - OutPt* startOp = op; - for(;;) - { - if (op->Next->Pt.Y == pt.Y) - { - if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && - ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; - } - if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) - { - if (op->Pt.X >= pt.X) - { - if (op->Next->Pt.X > pt.X) result = 1 - result; - else - { - double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; - } - } else - { - if (op->Next->Pt.X > pt.X) - { - double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; - } - } - } - op = op->Next; - if (startOp == op) break; - } - return result; -} -//------------------------------------------------------------------------------ - -bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) -{ - OutPt* op = OutPt1; - do - { - //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon - int res = PointInPolygon(op->Pt, OutPt2); - if (res >= 0) return res > 0; - op = op->Next; - } - while (op != OutPt1); - return true; -} -//---------------------------------------------------------------------- - -bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) -{ -#ifndef use_int32 - if (UseFullInt64Range) - return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) == - Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y); - else -#endif - return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) == - (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y); -} -//------------------------------------------------------------------------------ - -bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, - const IntPoint pt3, bool UseFullInt64Range) -{ -#ifndef use_int32 - if (UseFullInt64Range) - return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); - else -#endif - return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); -} -//------------------------------------------------------------------------------ - -bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, - const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) -{ -#ifndef use_int32 - if (UseFullInt64Range) - return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); - else -#endif - return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); -} -//------------------------------------------------------------------------------ - -inline bool IsHorizontal(TEdge &e) -{ - return e.Dx == HORIZONTAL; -} -//------------------------------------------------------------------------------ - -inline double GetDx(const IntPoint pt1, const IntPoint pt2) -{ - return (pt1.Y == pt2.Y) ? - HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); -} -//--------------------------------------------------------------------------- - -inline void SetDx(TEdge &e) -{ - cInt dy = (e.Top.Y - e.Bot.Y); - if (dy == 0) e.Dx = HORIZONTAL; - else e.Dx = (double)(e.Top.X - e.Bot.X) / dy; -} -//--------------------------------------------------------------------------- - -inline void SwapSides(TEdge &Edge1, TEdge &Edge2) -{ - EdgeSide Side = Edge1.Side; - Edge1.Side = Edge2.Side; - Edge2.Side = Side; -} -//------------------------------------------------------------------------------ - -inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) -{ - int OutIdx = Edge1.OutIdx; - Edge1.OutIdx = Edge2.OutIdx; - Edge2.OutIdx = OutIdx; -} -//------------------------------------------------------------------------------ - -inline cInt TopX(TEdge &edge, const cInt currentY) -{ - return ( currentY == edge.Top.Y ) ? - edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); -} -//------------------------------------------------------------------------------ - -void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) -{ -#ifdef use_xyz - ip.Z = 0; -#endif - - double b1, b2; - if (Edge1.Dx == Edge2.Dx) - { - ip.Y = Edge1.Curr.Y; - ip.X = TopX(Edge1, ip.Y); - return; - } - else if (Edge1.Dx == 0) - { - ip.X = Edge1.Bot.X; - if (IsHorizontal(Edge2)) - ip.Y = Edge2.Bot.Y; - else - { - b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); - ip.Y = Round(ip.X / Edge2.Dx + b2); - } - } - else if (Edge2.Dx == 0) - { - ip.X = Edge2.Bot.X; - if (IsHorizontal(Edge1)) - ip.Y = Edge1.Bot.Y; - else - { - b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); - ip.Y = Round(ip.X / Edge1.Dx + b1); - } - } - else - { - b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; - b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; - double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); - ip.Y = Round(q); - if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) - ip.X = Round(Edge1.Dx * q + b1); - else - ip.X = Round(Edge2.Dx * q + b2); - } - - if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) - { - if (Edge1.Top.Y > Edge2.Top.Y) - ip.Y = Edge1.Top.Y; - else - ip.Y = Edge2.Top.Y; - if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) - ip.X = TopX(Edge1, ip.Y); - else - ip.X = TopX(Edge2, ip.Y); - } - //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... - if (ip.Y > Edge1.Curr.Y) - { - ip.Y = Edge1.Curr.Y; - //use the more vertical edge to derive X ... - if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) - ip.X = TopX(Edge2, ip.Y); else - ip.X = TopX(Edge1, ip.Y); - } -} -//------------------------------------------------------------------------------ - -void ReversePolyPtLinks(OutPt *pp) -{ - if (!pp) return; - OutPt *pp1, *pp2; - pp1 = pp; - do { - pp2 = pp1->Next; - pp1->Next = pp1->Prev; - pp1->Prev = pp2; - pp1 = pp2; - } while( pp1 != pp ); -} -//------------------------------------------------------------------------------ - -void DisposeOutPts(OutPt*& pp) -{ - if (pp == 0) return; - pp->Prev->Next = 0; - while( pp ) - { - OutPt *tmpPp = pp; - pp = pp->Next; - delete tmpPp; - } -} -//------------------------------------------------------------------------------ - -inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) -{ - std::memset(e, 0, sizeof(TEdge)); - e->Next = eNext; - e->Prev = ePrev; - e->Curr = Pt; - e->OutIdx = Unassigned; -} -//------------------------------------------------------------------------------ - -void InitEdge2(TEdge& e, PolyType Pt) -{ - if (e.Curr.Y >= e.Next->Curr.Y) - { - e.Bot = e.Curr; - e.Top = e.Next->Curr; - } else - { - e.Top = e.Curr; - e.Bot = e.Next->Curr; - } - SetDx(e); - e.PolyTyp = Pt; -} -//------------------------------------------------------------------------------ - -TEdge* RemoveEdge(TEdge* e) -{ - //removes e from double_linked_list (but without removing from memory) - e->Prev->Next = e->Next; - e->Next->Prev = e->Prev; - TEdge* result = e->Next; - e->Prev = 0; //flag as removed (see ClipperBase.Clear) - return result; -} -//------------------------------------------------------------------------------ - -inline void ReverseHorizontal(TEdge &e) -{ - //swap horizontal edges' Top and Bottom x's so they follow the natural - //progression of the bounds - ie so their xbots will align with the - //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] - std::swap(e.Top.X, e.Bot.X); -#ifdef use_xyz - std::swap(e.Top.Z, e.Bot.Z); -#endif -} -//------------------------------------------------------------------------------ - -void SwapPoints(IntPoint &pt1, IntPoint &pt2) -{ - IntPoint tmp = pt1; - pt1 = pt2; - pt2 = tmp; -} -//------------------------------------------------------------------------------ - -bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, - IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) -{ - //precondition: segments are Collinear. - if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) - { - if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); - if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); - if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; - if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; - return pt1.X < pt2.X; - } else - { - if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); - if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); - if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; - if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; - return pt1.Y > pt2.Y; - } -} -//------------------------------------------------------------------------------ - -bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) -{ - OutPt *p = btmPt1->Prev; - while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev; - double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); - p = btmPt1->Next; - while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next; - double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); - - p = btmPt2->Prev; - while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev; - double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); - p = btmPt2->Next; - while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; - double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); - - if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) && - std::min(dx1p, dx1n) == std::min(dx2p, dx2n)) - return Area(btmPt1) > 0; //if otherwise identical use orientation - else - return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); -} -//------------------------------------------------------------------------------ - -OutPt* GetBottomPt(OutPt *pp) -{ - OutPt* dups = 0; - OutPt* p = pp->Next; - while (p != pp) - { - if (p->Pt.Y > pp->Pt.Y) - { - pp = p; - dups = 0; - } - else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) - { - if (p->Pt.X < pp->Pt.X) - { - dups = 0; - pp = p; - } else - { - if (p->Next != pp && p->Prev != pp) dups = p; - } - } - p = p->Next; - } - if (dups) - { - //there appears to be at least 2 vertices at BottomPt so ... - while (dups != p) - { - if (!FirstIsBottomPt(p, dups)) pp = dups; - dups = dups->Next; - while (dups->Pt != pp->Pt) dups = dups->Next; - } - } - return pp; -} -//------------------------------------------------------------------------------ - -bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, - const IntPoint pt2, const IntPoint pt3) -{ - if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) - return false; - else if (pt1.X != pt3.X) - return (pt2.X > pt1.X) == (pt2.X < pt3.X); - else - return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); -} -//------------------------------------------------------------------------------ - -bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) -{ - if (seg1a > seg1b) std::swap(seg1a, seg1b); - if (seg2a > seg2b) std::swap(seg2a, seg2b); - return (seg1a < seg2b) && (seg2a < seg1b); -} - -//------------------------------------------------------------------------------ -// ClipperBase class methods ... -//------------------------------------------------------------------------------ - -ClipperBase::ClipperBase() //constructor -{ - m_CurrentLM = m_MinimaList.begin(); //begin() == end() here - m_UseFullRange = false; -} -//------------------------------------------------------------------------------ - -ClipperBase::~ClipperBase() //destructor -{ - Clear(); -} -//------------------------------------------------------------------------------ - -void RangeTest(const IntPoint& Pt, bool& useFullRange) -{ - if (useFullRange) - { - if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) - CLIPPER_THROW(clipperException("Coordinate outside allowed range")); - } - else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) - { - useFullRange = true; - RangeTest(Pt, useFullRange); - } -} -//------------------------------------------------------------------------------ - -TEdge* FindNextLocMin(TEdge* E) -{ - for (;;) - { - while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next; - if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break; - while (IsHorizontal(*E->Prev)) E = E->Prev; - TEdge* E2 = E; - while (IsHorizontal(*E)) E = E->Next; - if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. - if (E2->Prev->Bot.X < E->Bot.X) E = E2; - break; - } - return E; -} -//------------------------------------------------------------------------------ - -TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) -{ - TEdge *Result = E; - TEdge *Horz = 0; - - if (E->OutIdx == Skip) - { - //if edges still remain in the current bound beyond the skip edge then - //create another LocMin and call ProcessBound once more - if (NextIsForward) - { - while (E->Top.Y == E->Next->Bot.Y) E = E->Next; - //don't include top horizontals when parsing a bound a second time, - //they will be contained in the opposite bound ... - while (E != Result && IsHorizontal(*E)) E = E->Prev; - } - else - { - while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; - while (E != Result && IsHorizontal(*E)) E = E->Next; - } - - if (E == Result) - { - if (NextIsForward) Result = E->Next; - else Result = E->Prev; - } - else - { - //there are more edges in the bound beyond result starting with E - if (NextIsForward) - E = Result->Next; - else - E = Result->Prev; - MinimaList::value_type locMin; - locMin.Y = E->Bot.Y; - locMin.LeftBound = 0; - locMin.RightBound = E; - E->WindDelta = 0; - Result = ProcessBound(E, NextIsForward); - m_MinimaList.push_back(locMin); - } - return Result; - } - - TEdge *EStart; - - if (IsHorizontal(*E)) - { - //We need to be careful with open paths because this may not be a - //true local minima (ie E may be following a skip edge). - //Also, consecutive horz. edges may start heading left before going right. - if (NextIsForward) - EStart = E->Prev; - else - EStart = E->Next; - if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge - { - if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) - ReverseHorizontal(*E); - } - else if (EStart->Bot.X != E->Bot.X) - ReverseHorizontal(*E); - } - - EStart = E; - if (NextIsForward) - { - while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) - Result = Result->Next; - if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) - { - //nb: at the top of a bound, horizontals are added to the bound - //only when the preceding edge attaches to the horizontal's left vertex - //unless a Skip edge is encountered when that becomes the top divide - Horz = Result; - while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; - if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; - } - while (E != Result) - { - E->NextInLML = E->Next; - if (IsHorizontal(*E) && E != EStart && - E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); - E = E->Next; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) - ReverseHorizontal(*E); - Result = Result->Next; //move to the edge just beyond current bound - } else - { - while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) - Result = Result->Prev; - if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) - { - Horz = Result; - while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; - if (Horz->Next->Top.X == Result->Prev->Top.X || - Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; - } - - while (E != Result) - { - E->NextInLML = E->Prev; - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - E = E->Prev; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - Result = Result->Prev; //move to the edge just beyond current bound - } - - return Result; -} -//------------------------------------------------------------------------------ - -bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) -{ -#ifdef use_lines - if (!Closed && PolyTyp == ptClip) - CLIPPER_THROW(clipperException("AddPath: Open paths must be subject.")); -#else - if (!Closed) - CLIPPER_THROW(clipperException("AddPath: Open paths have been disabled.")); -#endif - - int highI = (int)pg.size() -1; - if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; - while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; - if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; - - //create a new edge array ... - TEdge *edges = new TEdge [highI +1]; - - bool IsFlat = true; - //1. Basic (first) edge initialization ... - CLIPPER_TRY - { - edges[1].Curr = pg[1]; - RangeTest(pg[0], m_UseFullRange); - RangeTest(pg[highI], m_UseFullRange); - InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); - InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); - for (int i = highI - 1; i >= 1; --i) - { - RangeTest(pg[i], m_UseFullRange); - InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); - } - } - CLIPPER_CATCH(...) - { - delete [] edges; - CLIPPER_THROW(); //range test fails - } - TEdge *eStart = &edges[0]; - - //2. Remove duplicate vertices, and (when closed) collinear edges ... - TEdge *E = eStart, *eLoopStop = eStart; - for (;;) - { - //nb: allows matching start and end points when not Closed ... - if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) - { - if (E == E->Next) break; - if (E == eStart) eStart = E->Next; - E = RemoveEdge(E); - eLoopStop = E; - continue; - } - if (E->Prev == E->Next) - break; //only two vertices - else if (Closed && - SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) && - (!m_PreserveCollinear || - !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) - { - //Collinear edges are allowed for open paths but in closed paths - //the default is to merge adjacent collinear edges into a single edge. - //However, if the PreserveCollinear property is enabled, only overlapping - //collinear edges (ie spikes) will be removed from closed paths. - if (E == eStart) eStart = E->Next; - E = RemoveEdge(E); - E = E->Prev; - eLoopStop = E; - continue; - } - E = E->Next; - if ((E == eLoopStop) || (!Closed && E->Next == eStart)) break; - } - - if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) - { - delete [] edges; - return false; - } - - if (!Closed) - { - m_HasOpenPaths = true; - eStart->Prev->OutIdx = Skip; - } - - //3. Do second stage of edge initialization ... - E = eStart; - do - { - InitEdge2(*E, PolyTyp); - E = E->Next; - if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; - } - while (E != eStart); - - //4. Finally, add edge bounds to LocalMinima list ... - - //Totally flat paths must be handled differently when adding them - //to LocalMinima list to avoid endless loops etc ... - if (IsFlat) - { - if (Closed) - { - delete [] edges; - return false; - } - E->Prev->OutIdx = Skip; - MinimaList::value_type locMin; - locMin.Y = E->Bot.Y; - locMin.LeftBound = 0; - locMin.RightBound = E; - locMin.RightBound->Side = esRight; - locMin.RightBound->WindDelta = 0; - for (;;) - { - if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); - if (E->Next->OutIdx == Skip) break; - E->NextInLML = E->Next; - E = E->Next; - } - m_MinimaList.push_back(locMin); - m_edges.push_back(edges); - return true; - } - - m_edges.push_back(edges); - bool leftBoundIsForward; - TEdge* EMin = 0; - - //workaround to avoid an endless loop in the while loop below when - //open paths have matching start and end points ... - if (E->Prev->Bot == E->Prev->Top) E = E->Next; - - for (;;) - { - E = FindNextLocMin(E); - if (E == EMin) break; - else if (!EMin) EMin = E; - - //E and E.Prev now share a local minima (left aligned if horizontal). - //Compare their slopes to find which starts which bound ... - MinimaList::value_type locMin; - locMin.Y = E->Bot.Y; - if (E->Dx < E->Prev->Dx) - { - locMin.LeftBound = E->Prev; - locMin.RightBound = E; - leftBoundIsForward = false; //Q.nextInLML = Q.prev - } else - { - locMin.LeftBound = E; - locMin.RightBound = E->Prev; - leftBoundIsForward = true; //Q.nextInLML = Q.next - } - - if (!Closed) locMin.LeftBound->WindDelta = 0; - else if (locMin.LeftBound->Next == locMin.RightBound) - locMin.LeftBound->WindDelta = -1; - else locMin.LeftBound->WindDelta = 1; - locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta; - - E = ProcessBound(locMin.LeftBound, leftBoundIsForward); - if (E->OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward); - - TEdge* E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward); - if (E2->OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward); - - if (locMin.LeftBound->OutIdx == Skip) - locMin.LeftBound = 0; - else if (locMin.RightBound->OutIdx == Skip) - locMin.RightBound = 0; - m_MinimaList.push_back(locMin); - if (!leftBoundIsForward) E = E2; - } - return true; -} -//------------------------------------------------------------------------------ - -bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) -{ - bool result = false; - for (Paths::size_type i = 0; i < ppg.size(); ++i) - if (AddPath(ppg[i], PolyTyp, Closed)) result = true; - return result; -} -//------------------------------------------------------------------------------ - -void ClipperBase::Clear() -{ - DisposeLocalMinimaList(); - for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) - { - TEdge* edges = m_edges[i]; - delete [] edges; - } - m_edges.clear(); - m_UseFullRange = false; - m_HasOpenPaths = false; -} -//------------------------------------------------------------------------------ - -void ClipperBase::Reset() -{ - m_CurrentLM = m_MinimaList.begin(); - if (m_CurrentLM == m_MinimaList.end()) return; //ie nothing to process - std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter()); - - m_Scanbeam = ScanbeamList(); //clears/resets priority_queue - //reset all edges ... - for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) - { - InsertScanbeam(lm->Y); - TEdge* e = lm->LeftBound; - if (e) - { - e->Curr = e->Bot; - e->Side = esLeft; - e->OutIdx = Unassigned; - } - - e = lm->RightBound; - if (e) - { - e->Curr = e->Bot; - e->Side = esRight; - e->OutIdx = Unassigned; - } - } - m_ActiveEdges = 0; - m_CurrentLM = m_MinimaList.begin(); -} -//------------------------------------------------------------------------------ - -void ClipperBase::DisposeLocalMinimaList() -{ - m_MinimaList.clear(); - m_CurrentLM = m_MinimaList.begin(); -} -//------------------------------------------------------------------------------ - -bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) -{ - if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y) return false; - locMin = &(*m_CurrentLM); - ++m_CurrentLM; - return true; -} -//------------------------------------------------------------------------------ - -IntRect ClipperBase::GetBounds() -{ - IntRect result; - MinimaList::iterator lm = m_MinimaList.begin(); - if (lm == m_MinimaList.end()) - { - result.left = result.top = result.right = result.bottom = 0; - return result; - } - result.left = lm->LeftBound->Bot.X; - result.top = lm->LeftBound->Bot.Y; - result.right = lm->LeftBound->Bot.X; - result.bottom = lm->LeftBound->Bot.Y; - while (lm != m_MinimaList.end()) - { - //todo - needs fixing for open paths - result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); - TEdge* e = lm->LeftBound; - for (;;) { - TEdge* bottomE = e; - while (e->NextInLML) - { - if (e->Bot.X < result.left) result.left = e->Bot.X; - if (e->Bot.X > result.right) result.right = e->Bot.X; - e = e->NextInLML; - } - result.left = std::min(result.left, e->Bot.X); - result.right = std::max(result.right, e->Bot.X); - result.left = std::min(result.left, e->Top.X); - result.right = std::max(result.right, e->Top.X); - result.top = std::min(result.top, e->Top.Y); - if (bottomE == lm->LeftBound) e = lm->RightBound; - else break; - } - ++lm; - } - return result; -} -//------------------------------------------------------------------------------ - -void ClipperBase::InsertScanbeam(const cInt Y) -{ - m_Scanbeam.push(Y); -} -//------------------------------------------------------------------------------ - -bool ClipperBase::PopScanbeam(cInt &Y) -{ - if (m_Scanbeam.empty()) return false; - Y = m_Scanbeam.top(); - m_Scanbeam.pop(); - while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. - return true; -} -//------------------------------------------------------------------------------ - -void ClipperBase::DisposeAllOutRecs(){ - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - DisposeOutRec(i); - m_PolyOuts.clear(); -} -//------------------------------------------------------------------------------ - -void ClipperBase::DisposeOutRec(PolyOutList::size_type index) -{ - OutRec *outRec = m_PolyOuts[index]; - if (outRec->Pts) DisposeOutPts(outRec->Pts); - delete outRec; - m_PolyOuts[index] = 0; -} -//------------------------------------------------------------------------------ - -void ClipperBase::DeleteFromAEL(TEdge *e) -{ - TEdge* AelPrev = e->PrevInAEL; - TEdge* AelNext = e->NextInAEL; - if (!AelPrev && !AelNext && (e != m_ActiveEdges)) return; //already deleted - if (AelPrev) AelPrev->NextInAEL = AelNext; - else m_ActiveEdges = AelNext; - if (AelNext) AelNext->PrevInAEL = AelPrev; - e->NextInAEL = 0; - e->PrevInAEL = 0; -} -//------------------------------------------------------------------------------ - -OutRec* ClipperBase::CreateOutRec() -{ - OutRec* result = new OutRec; - result->IsHole = false; - result->IsOpen = false; - result->FirstLeft = 0; - result->Pts = 0; - result->BottomPt = 0; - result->PolyNd = 0; - m_PolyOuts.push_back(result); - result->Idx = (int)m_PolyOuts.size() - 1; - return result; -} -//------------------------------------------------------------------------------ - -void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) -{ - //check that one or other edge hasn't already been removed from AEL ... - if (Edge1->NextInAEL == Edge1->PrevInAEL || - Edge2->NextInAEL == Edge2->PrevInAEL) return; - - if (Edge1->NextInAEL == Edge2) - { - TEdge* Next = Edge2->NextInAEL; - if (Next) Next->PrevInAEL = Edge1; - TEdge* Prev = Edge1->PrevInAEL; - if (Prev) Prev->NextInAEL = Edge2; - Edge2->PrevInAEL = Prev; - Edge2->NextInAEL = Edge1; - Edge1->PrevInAEL = Edge2; - Edge1->NextInAEL = Next; - } - else if (Edge2->NextInAEL == Edge1) - { - TEdge* Next = Edge1->NextInAEL; - if (Next) Next->PrevInAEL = Edge2; - TEdge* Prev = Edge2->PrevInAEL; - if (Prev) Prev->NextInAEL = Edge1; - Edge1->PrevInAEL = Prev; - Edge1->NextInAEL = Edge2; - Edge2->PrevInAEL = Edge1; - Edge2->NextInAEL = Next; - } - else - { - TEdge* Next = Edge1->NextInAEL; - TEdge* Prev = Edge1->PrevInAEL; - Edge1->NextInAEL = Edge2->NextInAEL; - if (Edge1->NextInAEL) Edge1->NextInAEL->PrevInAEL = Edge1; - Edge1->PrevInAEL = Edge2->PrevInAEL; - if (Edge1->PrevInAEL) Edge1->PrevInAEL->NextInAEL = Edge1; - Edge2->NextInAEL = Next; - if (Edge2->NextInAEL) Edge2->NextInAEL->PrevInAEL = Edge2; - Edge2->PrevInAEL = Prev; - if (Edge2->PrevInAEL) Edge2->PrevInAEL->NextInAEL = Edge2; - } - - if (!Edge1->PrevInAEL) m_ActiveEdges = Edge1; - else if (!Edge2->PrevInAEL) m_ActiveEdges = Edge2; -} -//------------------------------------------------------------------------------ - -void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) -{ - if (!e->NextInLML) - CLIPPER_THROW(clipperException("UpdateEdgeIntoAEL: invalid call")); - - e->NextInLML->OutIdx = e->OutIdx; - TEdge* AelPrev = e->PrevInAEL; - TEdge* AelNext = e->NextInAEL; - if (AelPrev) AelPrev->NextInAEL = e->NextInLML; - else m_ActiveEdges = e->NextInLML; - if (AelNext) AelNext->PrevInAEL = e->NextInLML; - e->NextInLML->Side = e->Side; - e->NextInLML->WindDelta = e->WindDelta; - e->NextInLML->WindCnt = e->WindCnt; - e->NextInLML->WindCnt2 = e->WindCnt2; - e = e->NextInLML; - e->Curr = e->Bot; - e->PrevInAEL = AelPrev; - e->NextInAEL = AelNext; - if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); -} -//------------------------------------------------------------------------------ - -bool ClipperBase::LocalMinimaPending() -{ - return (m_CurrentLM != m_MinimaList.end()); -} - -//------------------------------------------------------------------------------ -// TClipper methods ... -//------------------------------------------------------------------------------ - -Clipper::Clipper(int initOptions) : ClipperBase() //constructor -{ - m_ExecuteLocked = false; - m_UseFullRange = false; - m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); - m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); - m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); - m_HasOpenPaths = false; -#ifdef use_xyz - m_ZFill = 0; -#endif -} -//------------------------------------------------------------------------------ - -#ifdef use_xyz -void Clipper::ZFillFunction(ZFillCallback zFillFunc) -{ - m_ZFill = zFillFunc; -} -//------------------------------------------------------------------------------ -#endif - -bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType) -{ - return Execute(clipType, solution, fillType, fillType); -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType) -{ - return Execute(clipType, polytree, fillType, fillType); -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, Paths &solution, - PolyFillType subjFillType, PolyFillType clipFillType) -{ - if( m_ExecuteLocked ) return false; - if (m_HasOpenPaths) - CLIPPER_THROW(clipperException("Error: PolyTree struct is needed for open path clipping.")); - m_ExecuteLocked = true; - solution.resize(0); - m_SubjFillType = subjFillType; - m_ClipFillType = clipFillType; - m_ClipType = clipType; - m_UsingPolyTree = false; - bool succeeded = ExecuteInternal(); - if (succeeded) BuildResult(solution); - DisposeAllOutRecs(); - m_ExecuteLocked = false; - return succeeded; -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, PolyTree& polytree, - PolyFillType subjFillType, PolyFillType clipFillType) -{ - if( m_ExecuteLocked ) return false; - m_ExecuteLocked = true; - m_SubjFillType = subjFillType; - m_ClipFillType = clipFillType; - m_ClipType = clipType; - m_UsingPolyTree = true; - bool succeeded = ExecuteInternal(); - if (succeeded) BuildResult2(polytree); - DisposeAllOutRecs(); - m_ExecuteLocked = false; - return succeeded; -} -//------------------------------------------------------------------------------ - -void Clipper::FixHoleLinkage(OutRec &outrec) -{ - //skip OutRecs that (a) contain outermost polygons or - //(b) already have the correct owner/child linkage ... - if (!outrec.FirstLeft || - (outrec.IsHole != outrec.FirstLeft->IsHole && - outrec.FirstLeft->Pts)) return; - - OutRec* orfl = outrec.FirstLeft; - while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) - orfl = orfl->FirstLeft; - outrec.FirstLeft = orfl; -} -//------------------------------------------------------------------------------ - -bool Clipper::ExecuteInternal() -{ - bool succeeded = true; - CLIPPER_TRY { - Reset(); - m_Maxima = MaximaList(); - m_SortedEdges = 0; - - succeeded = true; - cInt botY, topY; - if (!PopScanbeam(botY)) return false; - InsertLocalMinimaIntoAEL(botY); - while (PopScanbeam(topY) || LocalMinimaPending()) - { - ProcessHorizontals(); - ClearGhostJoins(); - if (!ProcessIntersections(topY)) - { - succeeded = false; - break; - } - ProcessEdgesAtTopOfScanbeam(topY); - botY = topY; - InsertLocalMinimaIntoAEL(botY); - } - } - CLIPPER_CATCH(...) - { - succeeded = false; - } - - if (succeeded) - { - //fix orientations ... - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec *outRec = m_PolyOuts[i]; - if (!outRec->Pts || outRec->IsOpen) continue; - if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) - ReversePolyPtLinks(outRec->Pts); - } - - if (!m_Joins.empty()) JoinCommonEdges(); - - //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec *outRec = m_PolyOuts[i]; - if (!outRec->Pts) continue; - if (outRec->IsOpen) - FixupOutPolyline(*outRec); - else - FixupOutPolygon(*outRec); - } - - if (m_StrictSimple) DoSimplePolygons(); - } - - ClearJoins(); - ClearGhostJoins(); - return succeeded; -} -//------------------------------------------------------------------------------ - -void Clipper::SetWindingCount(TEdge &edge) -{ - TEdge *e = edge.PrevInAEL; - //find the edge of the same polytype that immediately preceeds 'edge' in AEL - while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; - if (!e) - { - if (edge.WindDelta == 0) - { - PolyFillType pft = (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType); - edge.WindCnt = (pft == pftNegative ? -1 : 1); - } - else - edge.WindCnt = edge.WindDelta; - edge.WindCnt2 = 0; - e = m_ActiveEdges; //ie get ready to calc WindCnt2 - } - else if (edge.WindDelta == 0 && m_ClipType != ctUnion) - { - edge.WindCnt = 1; - edge.WindCnt2 = e->WindCnt2; - e = e->NextInAEL; //ie get ready to calc WindCnt2 - } - else if (IsEvenOddFillType(edge)) - { - //EvenOdd filling ... - if (edge.WindDelta == 0) - { - //are we inside a subj polygon ... - bool Inside = true; - TEdge *e2 = e->PrevInAEL; - while (e2) - { - if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) - Inside = !Inside; - e2 = e2->PrevInAEL; - } - edge.WindCnt = (Inside ? 0 : 1); - } - else - { - edge.WindCnt = edge.WindDelta; - } - edge.WindCnt2 = e->WindCnt2; - e = e->NextInAEL; //ie get ready to calc WindCnt2 - } - else - { - //nonZero, Positive or Negative filling ... - if (e->WindCnt * e->WindDelta < 0) - { - //prev edge is 'decreasing' WindCount (WC) toward zero - //so we're outside the previous polygon ... - if (Abs(e->WindCnt) > 1) - { - //outside prev poly but still inside another. - //when reversing direction of prev poly use the same WC - if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; - //otherwise continue to 'decrease' WC ... - else edge.WindCnt = e->WindCnt + edge.WindDelta; - } - else - //now outside all polys of same polytype so set own WC ... - edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); - } else - { - //prev edge is 'increasing' WindCount (WC) away from zero - //so we're inside the previous polygon ... - if (edge.WindDelta == 0) - edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); - //if wind direction is reversing prev then use same WC - else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; - //otherwise add to WC ... - else edge.WindCnt = e->WindCnt + edge.WindDelta; - } - edge.WindCnt2 = e->WindCnt2; - e = e->NextInAEL; //ie get ready to calc WindCnt2 - } - - //update WindCnt2 ... - if (IsEvenOddAltFillType(edge)) - { - //EvenOdd filling ... - while (e != &edge) - { - if (e->WindDelta != 0) - edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); - e = e->NextInAEL; - } - } else - { - //nonZero, Positive or Negative filling ... - while ( e != &edge ) - { - edge.WindCnt2 += e->WindDelta; - e = e->NextInAEL; - } - } -} -//------------------------------------------------------------------------------ - -bool Clipper::IsEvenOddFillType(const TEdge& edge) const -{ - if (edge.PolyTyp == ptSubject) - return m_SubjFillType == pftEvenOdd; else - return m_ClipFillType == pftEvenOdd; -} -//------------------------------------------------------------------------------ - -bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const -{ - if (edge.PolyTyp == ptSubject) - return m_ClipFillType == pftEvenOdd; else - return m_SubjFillType == pftEvenOdd; -} -//------------------------------------------------------------------------------ - -bool Clipper::IsContributing(const TEdge& edge) const -{ - PolyFillType pft, pft2; - if (edge.PolyTyp == ptSubject) - { - pft = m_SubjFillType; - pft2 = m_ClipFillType; - } else - { - pft = m_ClipFillType; - pft2 = m_SubjFillType; - } - - switch(pft) - { - case pftEvenOdd: - //return false if a subj line has been flagged as inside a subj polygon - if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; - break; - case pftNonZero: - if (Abs(edge.WindCnt) != 1) return false; - break; - case pftPositive: - if (edge.WindCnt != 1) return false; - break; - default: //pftNegative - if (edge.WindCnt != -1) return false; - } - - switch(m_ClipType) - { - case ctIntersection: - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 != 0); - case pftPositive: - return (edge.WindCnt2 > 0); - default: - return (edge.WindCnt2 < 0); - } - break; - case ctUnion: - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 == 0); - case pftPositive: - return (edge.WindCnt2 <= 0); - default: - return (edge.WindCnt2 >= 0); - } - break; - case ctDifference: - if (edge.PolyTyp == ptSubject) - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 == 0); - case pftPositive: - return (edge.WindCnt2 <= 0); - default: - return (edge.WindCnt2 >= 0); - } - else - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 != 0); - case pftPositive: - return (edge.WindCnt2 > 0); - default: - return (edge.WindCnt2 < 0); - } - break; - case ctXor: - if (edge.WindDelta == 0) //XOr always contributing unless open - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 == 0); - case pftPositive: - return (edge.WindCnt2 <= 0); - default: - return (edge.WindCnt2 >= 0); - } - else - return true; - break; - default: - return true; - } -} -//------------------------------------------------------------------------------ - -OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) -{ - OutPt* result; - TEdge *e, *prevE; - if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) - { - result = AddOutPt(e1, Pt); - e2->OutIdx = e1->OutIdx; - e1->Side = esLeft; - e2->Side = esRight; - e = e1; - if (e->PrevInAEL == e2) - prevE = e2->PrevInAEL; - else - prevE = e->PrevInAEL; - } else - { - result = AddOutPt(e2, Pt); - e1->OutIdx = e2->OutIdx; - e1->Side = esRight; - e2->Side = esLeft; - e = e2; - if (e->PrevInAEL == e1) - prevE = e1->PrevInAEL; - else - prevE = e->PrevInAEL; - } - - if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y) - { - cInt xPrev = TopX(*prevE, Pt.Y); - cInt xE = TopX(*e, Pt.Y); - if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) && - SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), e->Top, m_UseFullRange)) - { - OutPt* outPt = AddOutPt(prevE, Pt); - AddJoin(result, outPt, e->Top); - } - } - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) -{ - AddOutPt( e1, Pt ); - if (e2->WindDelta == 0) AddOutPt(e2, Pt); - if( e1->OutIdx == e2->OutIdx ) - { - e1->OutIdx = Unassigned; - e2->OutIdx = Unassigned; - } - else if (e1->OutIdx < e2->OutIdx) - AppendPolygon(e1, e2); - else - AppendPolygon(e2, e1); -} -//------------------------------------------------------------------------------ - -void Clipper::AddEdgeToSEL(TEdge *edge) -{ - //SEL pointers in PEdge are reused to build a list of horizontal edges. - //However, we don't need to worry about order with horizontal edge processing. - if( !m_SortedEdges ) - { - m_SortedEdges = edge; - edge->PrevInSEL = 0; - edge->NextInSEL = 0; - } - else - { - edge->NextInSEL = m_SortedEdges; - edge->PrevInSEL = 0; - m_SortedEdges->PrevInSEL = edge; - m_SortedEdges = edge; - } -} -//------------------------------------------------------------------------------ - -bool Clipper::PopEdgeFromSEL(TEdge *&edge) -{ - if (!m_SortedEdges) return false; - edge = m_SortedEdges; - DeleteFromSEL(m_SortedEdges); - return true; -} -//------------------------------------------------------------------------------ - -void Clipper::CopyAELToSEL() -{ - TEdge* e = m_ActiveEdges; - m_SortedEdges = e; - while ( e ) - { - e->PrevInSEL = e->PrevInAEL; - e->NextInSEL = e->NextInAEL; - e = e->NextInAEL; - } -} -//------------------------------------------------------------------------------ - -void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) -{ - Join* j = new Join; - j->OutPt1 = op1; - j->OutPt2 = op2; - j->OffPt = OffPt; - m_Joins.push_back(j); -} -//------------------------------------------------------------------------------ - -void Clipper::ClearJoins() -{ - for (JoinList::size_type i = 0; i < m_Joins.size(); i++) - delete m_Joins[i]; - m_Joins.resize(0); -} -//------------------------------------------------------------------------------ - -void Clipper::ClearGhostJoins() -{ - for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) - delete m_GhostJoins[i]; - m_GhostJoins.resize(0); -} -//------------------------------------------------------------------------------ - -void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) -{ - Join* j = new Join; - j->OutPt1 = op; - j->OutPt2 = 0; - j->OffPt = OffPt; - m_GhostJoins.push_back(j); -} -//------------------------------------------------------------------------------ - -void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) -{ - const LocalMinimum *lm; - while (PopLocalMinima(botY, lm)) - { - TEdge* lb = lm->LeftBound; - TEdge* rb = lm->RightBound; - - OutPt *Op1 = 0; - if (!lb) - { - //nb: don't insert LB into either AEL or SEL - InsertEdgeIntoAEL(rb, 0); - SetWindingCount(*rb); - if (IsContributing(*rb)) - Op1 = AddOutPt(rb, rb->Bot); - } - else if (!rb) - { - InsertEdgeIntoAEL(lb, 0); - SetWindingCount(*lb); - if (IsContributing(*lb)) - Op1 = AddOutPt(lb, lb->Bot); - InsertScanbeam(lb->Top.Y); - } - else - { - InsertEdgeIntoAEL(lb, 0); - InsertEdgeIntoAEL(rb, lb); - SetWindingCount( *lb ); - rb->WindCnt = lb->WindCnt; - rb->WindCnt2 = lb->WindCnt2; - if (IsContributing(*lb)) - Op1 = AddLocalMinPoly(lb, rb, lb->Bot); - InsertScanbeam(lb->Top.Y); - } - - if (rb) - { - if (IsHorizontal(*rb)) - { - AddEdgeToSEL(rb); - if (rb->NextInLML) - InsertScanbeam(rb->NextInLML->Top.Y); - } - else InsertScanbeam( rb->Top.Y ); - } - - if (!lb || !rb) continue; - - //if any output polygons share an edge, they'll need joining later ... - if (Op1 && IsHorizontal(*rb) && - m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) - { - for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) - { - Join* jr = m_GhostJoins[i]; - //if the horizontal Rb and a 'ghost' horizontal overlap, then convert - //the 'ghost' join to a real join ready for later ... - if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X)) - AddJoin(jr->OutPt1, Op1, jr->OffPt); - } - } - - if (lb->OutIdx >= 0 && lb->PrevInAEL && - lb->PrevInAEL->Curr.X == lb->Bot.X && - lb->PrevInAEL->OutIdx >= 0 && - SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, m_UseFullRange) && - (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) - { - OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); - AddJoin(Op1, Op2, lb->Top); - } - - if(lb->NextInAEL != rb) - { - - if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && - SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, rb->Top, m_UseFullRange) && - (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) - { - OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); - AddJoin(Op1, Op2, rb->Top); - } - - TEdge* e = lb->NextInAEL; - if (e) - { - while( e != rb ) - { - //nb: For calculating winding counts etc, IntersectEdges() assumes - //that param1 will be to the Right of param2 ABOVE the intersection ... - IntersectEdges(rb , e , lb->Curr); //order important here - e = e->NextInAEL; - } - } - } - - } -} -//------------------------------------------------------------------------------ - -void Clipper::DeleteFromSEL(TEdge *e) -{ - TEdge* SelPrev = e->PrevInSEL; - TEdge* SelNext = e->NextInSEL; - if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted - if( SelPrev ) SelPrev->NextInSEL = SelNext; - else m_SortedEdges = SelNext; - if( SelNext ) SelNext->PrevInSEL = SelPrev; - e->NextInSEL = 0; - e->PrevInSEL = 0; -} -//------------------------------------------------------------------------------ - -#ifdef use_xyz -void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) -{ - if (pt.Z != 0 || !m_ZFill) return; - else if (pt == e1.Bot) pt.Z = e1.Bot.Z; - else if (pt == e1.Top) pt.Z = e1.Top.Z; - else if (pt == e2.Bot) pt.Z = e2.Bot.Z; - else if (pt == e2.Top) pt.Z = e2.Top.Z; - else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); -} -//------------------------------------------------------------------------------ -#endif - -void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) -{ - bool e1Contributing = ( e1->OutIdx >= 0 ); - bool e2Contributing = ( e2->OutIdx >= 0 ); - -#ifdef use_xyz - SetZ(Pt, *e1, *e2); -#endif - -#ifdef use_lines - //if either edge is on an OPEN path ... - if (e1->WindDelta == 0 || e2->WindDelta == 0) - { - //ignore subject-subject open path intersections UNLESS they - //are both open paths, AND they are both 'contributing maximas' ... - if (e1->WindDelta == 0 && e2->WindDelta == 0) return; - - //if intersecting a subj line with a subj poly ... - else if (e1->PolyTyp == e2->PolyTyp && - e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) - { - if (e1->WindDelta == 0) - { - if (e2Contributing) - { - AddOutPt(e1, Pt); - if (e1Contributing) e1->OutIdx = Unassigned; - } - } - else - { - if (e1Contributing) - { - AddOutPt(e2, Pt); - if (e2Contributing) e2->OutIdx = Unassigned; - } - } - } - else if (e1->PolyTyp != e2->PolyTyp) - { - //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... - if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && - (m_ClipType != ctUnion || e2->WindCnt2 == 0)) - { - AddOutPt(e1, Pt); - if (e1Contributing) e1->OutIdx = Unassigned; - } - else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && - (m_ClipType != ctUnion || e1->WindCnt2 == 0)) - { - AddOutPt(e2, Pt); - if (e2Contributing) e2->OutIdx = Unassigned; - } - } - return; - } -#endif - - //update winding counts... - //assumes that e1 will be to the Right of e2 ABOVE the intersection - if ( e1->PolyTyp == e2->PolyTyp ) - { - if ( IsEvenOddFillType( *e1) ) - { - int oldE1WindCnt = e1->WindCnt; - e1->WindCnt = e2->WindCnt; - e2->WindCnt = oldE1WindCnt; - } else - { - if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt; - else e1->WindCnt += e2->WindDelta; - if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt; - else e2->WindCnt -= e1->WindDelta; - } - } else - { - if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta; - else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0; - if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta; - else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0; - } - - PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; - if (e1->PolyTyp == ptSubject) - { - e1FillType = m_SubjFillType; - e1FillType2 = m_ClipFillType; - } else - { - e1FillType = m_ClipFillType; - e1FillType2 = m_SubjFillType; - } - if (e2->PolyTyp == ptSubject) - { - e2FillType = m_SubjFillType; - e2FillType2 = m_ClipFillType; - } else - { - e2FillType = m_ClipFillType; - e2FillType2 = m_SubjFillType; - } - - cInt e1Wc, e2Wc; - switch (e1FillType) - { - case pftPositive: e1Wc = e1->WindCnt; break; - case pftNegative: e1Wc = -e1->WindCnt; break; - default: e1Wc = Abs(e1->WindCnt); - } - switch(e2FillType) - { - case pftPositive: e2Wc = e2->WindCnt; break; - case pftNegative: e2Wc = -e2->WindCnt; break; - default: e2Wc = Abs(e2->WindCnt); - } - - if ( e1Contributing && e2Contributing ) - { - if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || - (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) - { - AddLocalMaxPoly(e1, e2, Pt); - } - else - { - AddOutPt(e1, Pt); - AddOutPt(e2, Pt); - SwapSides( *e1 , *e2 ); - SwapPolyIndexes( *e1 , *e2 ); - } - } - else if ( e1Contributing ) - { - if (e2Wc == 0 || e2Wc == 1) - { - AddOutPt(e1, Pt); - SwapSides(*e1, *e2); - SwapPolyIndexes(*e1, *e2); - } - } - else if ( e2Contributing ) - { - if (e1Wc == 0 || e1Wc == 1) - { - AddOutPt(e2, Pt); - SwapSides(*e1, *e2); - SwapPolyIndexes(*e1, *e2); - } - } - else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) - { - //neither edge is currently contributing ... - - cInt e1Wc2, e2Wc2; - switch (e1FillType2) - { - case pftPositive: e1Wc2 = e1->WindCnt2; break; - case pftNegative : e1Wc2 = -e1->WindCnt2; break; - default: e1Wc2 = Abs(e1->WindCnt2); - } - switch (e2FillType2) - { - case pftPositive: e2Wc2 = e2->WindCnt2; break; - case pftNegative: e2Wc2 = -e2->WindCnt2; break; - default: e2Wc2 = Abs(e2->WindCnt2); - } - - if (e1->PolyTyp != e2->PolyTyp) - { - AddLocalMinPoly(e1, e2, Pt); - } - else if (e1Wc == 1 && e2Wc == 1) - switch( m_ClipType ) { - case ctIntersection: - if (e1Wc2 > 0 && e2Wc2 > 0) - AddLocalMinPoly(e1, e2, Pt); - break; - case ctUnion: - if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) - AddLocalMinPoly(e1, e2, Pt); - break; - case ctDifference: - if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || - ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) - AddLocalMinPoly(e1, e2, Pt); - break; - case ctXor: - AddLocalMinPoly(e1, e2, Pt); - } - else - SwapSides( *e1, *e2 ); - } -} -//------------------------------------------------------------------------------ - -void Clipper::SetHoleState(TEdge *e, OutRec *outrec) -{ - TEdge *e2 = e->PrevInAEL; - TEdge *eTmp = 0; - while (e2) - { - if (e2->OutIdx >= 0 && e2->WindDelta != 0) - { - if (!eTmp) eTmp = e2; - else if (eTmp->OutIdx == e2->OutIdx) eTmp = 0; - } - e2 = e2->PrevInAEL; - } - if (!eTmp) - { - outrec->FirstLeft = 0; - outrec->IsHole = false; - } - else - { - outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx]; - outrec->IsHole = !outrec->FirstLeft->IsHole; - } -} -//------------------------------------------------------------------------------ - -OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) -{ - //work out which polygon fragment has the correct hole state ... - if (!outRec1->BottomPt) - outRec1->BottomPt = GetBottomPt(outRec1->Pts); - if (!outRec2->BottomPt) - outRec2->BottomPt = GetBottomPt(outRec2->Pts); - OutPt *OutPt1 = outRec1->BottomPt; - OutPt *OutPt2 = outRec2->BottomPt; - if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; - else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; - else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; - else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; - else if (OutPt1->Next == OutPt1) return outRec2; - else if (OutPt2->Next == OutPt2) return outRec1; - else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; - else return outRec2; -} -//------------------------------------------------------------------------------ - -bool OutRec1RightOfOutRec2(OutRec* outRec1, OutRec* outRec2) -{ - do - { - outRec1 = outRec1->FirstLeft; - if (outRec1 == outRec2) return true; - } while (outRec1); - return false; -} -//------------------------------------------------------------------------------ - -OutRec* Clipper::GetOutRec(int Idx) -{ - OutRec* outrec = m_PolyOuts[Idx]; - while (outrec != m_PolyOuts[outrec->Idx]) - outrec = m_PolyOuts[outrec->Idx]; - return outrec; -} -//------------------------------------------------------------------------------ - -void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) -{ - //get the start and ends of both output polygons ... - OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; - OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; - - OutRec *holeStateRec; - if (OutRec1RightOfOutRec2(outRec1, outRec2)) - holeStateRec = outRec2; - else if (OutRec1RightOfOutRec2(outRec2, outRec1)) - holeStateRec = outRec1; - else - holeStateRec = GetLowermostRec(outRec1, outRec2); - - //get the start and ends of both output polygons and - //join e2 poly onto e1 poly and delete pointers to e2 ... - - OutPt* p1_lft = outRec1->Pts; - OutPt* p1_rt = p1_lft->Prev; - OutPt* p2_lft = outRec2->Pts; - OutPt* p2_rt = p2_lft->Prev; - - //join e2 poly onto e1 poly and delete pointers to e2 ... - if( e1->Side == esLeft ) - { - if( e2->Side == esLeft ) - { - //z y x a b c - ReversePolyPtLinks(p2_lft); - p2_lft->Next = p1_lft; - p1_lft->Prev = p2_lft; - p1_rt->Next = p2_rt; - p2_rt->Prev = p1_rt; - outRec1->Pts = p2_rt; - } else - { - //x y z a b c - p2_rt->Next = p1_lft; - p1_lft->Prev = p2_rt; - p2_lft->Prev = p1_rt; - p1_rt->Next = p2_lft; - outRec1->Pts = p2_lft; - } - } else - { - if( e2->Side == esRight ) - { - //a b c z y x - ReversePolyPtLinks(p2_lft); - p1_rt->Next = p2_rt; - p2_rt->Prev = p1_rt; - p2_lft->Next = p1_lft; - p1_lft->Prev = p2_lft; - } else - { - //a b c x y z - p1_rt->Next = p2_lft; - p2_lft->Prev = p1_rt; - p1_lft->Prev = p2_rt; - p2_rt->Next = p1_lft; - } - } - - outRec1->BottomPt = 0; - if (holeStateRec == outRec2) - { - if (outRec2->FirstLeft != outRec1) - outRec1->FirstLeft = outRec2->FirstLeft; - outRec1->IsHole = outRec2->IsHole; - } - outRec2->Pts = 0; - outRec2->BottomPt = 0; - outRec2->FirstLeft = outRec1; - - int OKIdx = e1->OutIdx; - int ObsoleteIdx = e2->OutIdx; - - e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly - e2->OutIdx = Unassigned; - - TEdge* e = m_ActiveEdges; - while( e ) - { - if( e->OutIdx == ObsoleteIdx ) - { - e->OutIdx = OKIdx; - e->Side = e1->Side; - break; - } - e = e->NextInAEL; - } - - outRec2->Idx = outRec1->Idx; -} -//------------------------------------------------------------------------------ - -OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) -{ - if( e->OutIdx < 0 ) - { - OutRec *outRec = CreateOutRec(); - outRec->IsOpen = (e->WindDelta == 0); - OutPt* newOp = new OutPt; - outRec->Pts = newOp; - newOp->Idx = outRec->Idx; - newOp->Pt = pt; - newOp->Next = newOp; - newOp->Prev = newOp; - if (!outRec->IsOpen) - SetHoleState(e, outRec); - e->OutIdx = outRec->Idx; - return newOp; - } else - { - OutRec *outRec = m_PolyOuts[e->OutIdx]; - //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' - OutPt* op = outRec->Pts; - - bool ToFront = (e->Side == esLeft); - if (ToFront && (pt == op->Pt)) return op; - else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; - - OutPt* newOp = new OutPt; - newOp->Idx = outRec->Idx; - newOp->Pt = pt; - newOp->Next = op; - newOp->Prev = op->Prev; - newOp->Prev->Next = newOp; - op->Prev = newOp; - if (ToFront) outRec->Pts = newOp; - return newOp; - } -} -//------------------------------------------------------------------------------ - -OutPt* Clipper::GetLastOutPt(TEdge *e) -{ - OutRec *outRec = m_PolyOuts[e->OutIdx]; - if (e->Side == esLeft) - return outRec->Pts; - else - return outRec->Pts->Prev; -} -//------------------------------------------------------------------------------ - -void Clipper::ProcessHorizontals() -{ - TEdge* horzEdge; - while (PopEdgeFromSEL(horzEdge)) - ProcessHorizontal(horzEdge); -} -//------------------------------------------------------------------------------ - -inline bool IsMinima(TEdge *e) -{ - return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); -} -//------------------------------------------------------------------------------ - -inline bool IsMaxima(TEdge *e, const cInt Y) -{ - return e && e->Top.Y == Y && !e->NextInLML; -} -//------------------------------------------------------------------------------ - -inline bool IsIntermediate(TEdge *e, const cInt Y) -{ - return e->Top.Y == Y && e->NextInLML; -} -//------------------------------------------------------------------------------ - -TEdge *GetMaximaPair(TEdge *e) -{ - if ((e->Next->Top == e->Top) && !e->Next->NextInLML) - return e->Next; - else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) - return e->Prev; - else return 0; -} -//------------------------------------------------------------------------------ - -TEdge *GetMaximaPairEx(TEdge *e) -{ - //as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's horizontal) - TEdge* result = GetMaximaPair(e); - if (result && (result->OutIdx == Skip || - (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) return 0; - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) -{ - if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return; - if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return; - - if( Edge1->NextInSEL == Edge2 ) - { - TEdge* Next = Edge2->NextInSEL; - if( Next ) Next->PrevInSEL = Edge1; - TEdge* Prev = Edge1->PrevInSEL; - if( Prev ) Prev->NextInSEL = Edge2; - Edge2->PrevInSEL = Prev; - Edge2->NextInSEL = Edge1; - Edge1->PrevInSEL = Edge2; - Edge1->NextInSEL = Next; - } - else if( Edge2->NextInSEL == Edge1 ) - { - TEdge* Next = Edge1->NextInSEL; - if( Next ) Next->PrevInSEL = Edge2; - TEdge* Prev = Edge2->PrevInSEL; - if( Prev ) Prev->NextInSEL = Edge1; - Edge1->PrevInSEL = Prev; - Edge1->NextInSEL = Edge2; - Edge2->PrevInSEL = Edge1; - Edge2->NextInSEL = Next; - } - else - { - TEdge* Next = Edge1->NextInSEL; - TEdge* Prev = Edge1->PrevInSEL; - Edge1->NextInSEL = Edge2->NextInSEL; - if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1; - Edge1->PrevInSEL = Edge2->PrevInSEL; - if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1; - Edge2->NextInSEL = Next; - if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2; - Edge2->PrevInSEL = Prev; - if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2; - } - - if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1; - else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2; -} -//------------------------------------------------------------------------------ - -TEdge* GetNextInAEL(TEdge *e, Direction dir) -{ - return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; -} -//------------------------------------------------------------------------------ - -void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) -{ - if (HorzEdge.Bot.X < HorzEdge.Top.X) - { - Left = HorzEdge.Bot.X; - Right = HorzEdge.Top.X; - Dir = dLeftToRight; - } else - { - Left = HorzEdge.Top.X; - Right = HorzEdge.Bot.X; - Dir = dRightToLeft; - } -} -//------------------------------------------------------------------------ - -/******************************************************************************* -* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * -* Bottom of a scanbeam) are processed as if layered. The order in which HEs * -* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * -* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * -* and with other non-horizontal edges [*]. Once these intersections are * -* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * -* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * -*******************************************************************************/ - -void Clipper::ProcessHorizontal(TEdge *horzEdge) -{ - Direction dir; - cInt horzLeft, horzRight; - bool IsOpen = (horzEdge->WindDelta == 0); - - GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); - - TEdge* eLastHorz = horzEdge, *eMaxPair = 0; - while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) - eLastHorz = eLastHorz->NextInLML; - if (!eLastHorz->NextInLML) - eMaxPair = GetMaximaPair(eLastHorz); - - MaximaList::const_iterator maxIt; - MaximaList::const_reverse_iterator maxRit; - if (m_Maxima.size() > 0) - { - //get the first maxima in range (X) ... - if (dir == dLeftToRight) - { - maxIt = m_Maxima.begin(); - while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) maxIt++; - if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X) - maxIt = m_Maxima.end(); - } - else - { - maxRit = m_Maxima.rbegin(); - while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) maxRit++; - if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X) - maxRit = m_Maxima.rend(); - } - } - - OutPt* op1 = 0; - - for (;;) //loop through consec. horizontal edges - { - - bool IsLastHorz = (horzEdge == eLastHorz); - TEdge* e = GetNextInAEL(horzEdge, dir); - while(e) - { - - //this code block inserts extra coords into horizontal edges (in output - //polygons) whereever maxima touch these horizontal edges. This helps - //'simplifying' polygons (ie if the Simplify property is set). - if (m_Maxima.size() > 0) - { - if (dir == dLeftToRight) - { - while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) - { - if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y)); - maxIt++; - } - } - else - { - while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) - { - if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y)); - maxRit++; - } - } - }; - - if ((dir == dLeftToRight && e->Curr.X > horzRight) || - (dir == dRightToLeft && e->Curr.X < horzLeft)) break; - - //Also break if we've got to the end of an intermediate horizontal edge ... - //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. - if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && - e->Dx < horzEdge->NextInLML->Dx) break; - - if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times - { -#ifdef use_xyz - if (dir == dLeftToRight) SetZ(e->Curr, *horzEdge, *e); - else SetZ(e->Curr, *e, *horzEdge); -#endif - op1 = AddOutPt(horzEdge, e->Curr); - TEdge* eNextHorz = m_SortedEdges; - while (eNextHorz) - { - if (eNextHorz->OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge->Bot.X, - horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) - { - OutPt* op2 = GetLastOutPt(eNextHorz); - AddJoin(op2, op1, eNextHorz->Top); - } - eNextHorz = eNextHorz->NextInSEL; - } - AddGhostJoin(op1, horzEdge->Bot); - } - - //OK, so far we're still in range of the horizontal Edge but make sure - //we're at the last of consec. horizontals when matching with eMaxPair - if(e == eMaxPair && IsLastHorz) - { - if (horzEdge->OutIdx >= 0) - AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); - DeleteFromAEL(horzEdge); - DeleteFromAEL(eMaxPair); - return; - } - - if(dir == dLeftToRight) - { - IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); - IntersectEdges(horzEdge, e, Pt); - } - else - { - IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); - IntersectEdges( e, horzEdge, Pt); - } - TEdge* eNext = GetNextInAEL(e, dir); - SwapPositionsInAEL( horzEdge, e ); - e = eNext; - } //end while(e) - - //Break out of loop if HorzEdge.NextInLML is not also horizontal ... - if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) break; - - UpdateEdgeIntoAEL(horzEdge); - if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot); - GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); - - } //end for (;;) - - if (horzEdge->OutIdx >= 0 && !op1) - { - op1 = GetLastOutPt(horzEdge); - TEdge* eNextHorz = m_SortedEdges; - while (eNextHorz) - { - if (eNextHorz->OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge->Bot.X, - horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) - { - OutPt* op2 = GetLastOutPt(eNextHorz); - AddJoin(op2, op1, eNextHorz->Top); - } - eNextHorz = eNextHorz->NextInSEL; - } - AddGhostJoin(op1, horzEdge->Top); - } - - if (horzEdge->NextInLML) - { - if(horzEdge->OutIdx >= 0) - { - op1 = AddOutPt( horzEdge, horzEdge->Top); - UpdateEdgeIntoAEL(horzEdge); - if (horzEdge->WindDelta == 0) return; - //nb: HorzEdge is no longer horizontal here - TEdge* ePrev = horzEdge->PrevInAEL; - TEdge* eNext = horzEdge->NextInAEL; - if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && - ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && - (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && - SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) - { - OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); - AddJoin(op1, op2, horzEdge->Top); - } - else if (eNext && eNext->Curr.X == horzEdge->Bot.X && - eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && - eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && - SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) - { - OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); - AddJoin(op1, op2, horzEdge->Top); - } - } - else - UpdateEdgeIntoAEL(horzEdge); - } - else - { - if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); - DeleteFromAEL(horzEdge); - } -} -//------------------------------------------------------------------------------ - -bool Clipper::ProcessIntersections(const cInt topY) -{ - if( !m_ActiveEdges ) return true; - CLIPPER_TRY { - BuildIntersectList(topY); - size_t IlSize = m_IntersectList.size(); - if (IlSize == 0) return true; - if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); - else return false; - } - CLIPPER_CATCH(...) - { - m_SortedEdges = 0; - DisposeIntersectNodes(); - CLIPPER_THROW(clipperException("ProcessIntersections error")); - } - m_SortedEdges = 0; - return true; -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeIntersectNodes() -{ - for (size_t i = 0; i < m_IntersectList.size(); ++i ) - delete m_IntersectList[i]; - m_IntersectList.clear(); -} -//------------------------------------------------------------------------------ - -void Clipper::BuildIntersectList(const cInt topY) -{ - if ( !m_ActiveEdges ) return; - - //prepare for sorting ... - TEdge* e = m_ActiveEdges; - m_SortedEdges = e; - while( e ) - { - e->PrevInSEL = e->PrevInAEL; - e->NextInSEL = e->NextInAEL; - e->Curr.X = TopX( *e, topY ); - e = e->NextInAEL; - } - - //bubblesort ... - bool isModified; - do - { - isModified = false; - e = m_SortedEdges; - while( e->NextInSEL ) - { - TEdge *eNext = e->NextInSEL; - IntPoint Pt; - if(e->Curr.X > eNext->Curr.X) - { - IntersectPoint(*e, *eNext, Pt); - if (Pt.Y < topY) Pt = IntPoint(TopX(*e, topY), topY); - IntersectNode * newNode = new IntersectNode; - newNode->Edge1 = e; - newNode->Edge2 = eNext; - newNode->Pt = Pt; - m_IntersectList.push_back(newNode); - - SwapPositionsInSEL(e, eNext); - isModified = true; - } - else - e = eNext; - } - if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0; - else break; - } - while ( isModified ); - m_SortedEdges = 0; //important -} -//------------------------------------------------------------------------------ - - -void Clipper::ProcessIntersectList() -{ - for (size_t i = 0; i < m_IntersectList.size(); ++i) - { - IntersectNode* iNode = m_IntersectList[i]; - { - IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt); - SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); - } - delete iNode; - } - m_IntersectList.clear(); -} -//------------------------------------------------------------------------------ - -bool IntersectListSort(IntersectNode* node1, IntersectNode* node2) -{ - return node2->Pt.Y < node1->Pt.Y; -} -//------------------------------------------------------------------------------ - -inline bool EdgesAdjacent(const IntersectNode &inode) -{ - return (inode.Edge1->NextInSEL == inode.Edge2) || - (inode.Edge1->PrevInSEL == inode.Edge2); -} -//------------------------------------------------------------------------------ - -bool Clipper::FixupIntersectionOrder() -{ - //pre-condition: intersections are sorted Bottom-most first. - //Now it's crucial that intersections are made only between adjacent edges, - //so to ensure this the order of intersections may need adjusting ... - CopyAELToSEL(); - std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); - size_t cnt = m_IntersectList.size(); - for (size_t i = 0; i < cnt; ++i) - { - if (!EdgesAdjacent(*m_IntersectList[i])) - { - size_t j = i + 1; - while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++; - if (j == cnt) return false; - std::swap(m_IntersectList[i], m_IntersectList[j]); - } - SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); - } - return true; -} -//------------------------------------------------------------------------------ - -void Clipper::DoMaxima(TEdge *e) -{ - TEdge* eMaxPair = GetMaximaPairEx(e); - if (!eMaxPair) - { - if (e->OutIdx >= 0) - AddOutPt(e, e->Top); - DeleteFromAEL(e); - return; - } - - TEdge* eNext = e->NextInAEL; - while(eNext && eNext != eMaxPair) - { - IntersectEdges(e, eNext, e->Top); - SwapPositionsInAEL(e, eNext); - eNext = e->NextInAEL; - } - - if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) - { - DeleteFromAEL(e); - DeleteFromAEL(eMaxPair); - } - else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) - { - if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top); - DeleteFromAEL(e); - DeleteFromAEL(eMaxPair); - } -#ifdef use_lines - else if (e->WindDelta == 0) - { - if (e->OutIdx >= 0) - { - AddOutPt(e, e->Top); - e->OutIdx = Unassigned; - } - DeleteFromAEL(e); - - if (eMaxPair->OutIdx >= 0) - { - AddOutPt(eMaxPair, e->Top); - eMaxPair->OutIdx = Unassigned; - } - DeleteFromAEL(eMaxPair); - } -#endif - else CLIPPER_THROW(clipperException("DoMaxima error")); -} -//------------------------------------------------------------------------------ - -void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) -{ - TEdge* e = m_ActiveEdges; - while( e ) - { - //1. process maxima, treating them as if they're 'bent' horizontal edges, - // but exclude maxima with horizontal edges. nb: e can't be a horizontal. - bool IsMaximaEdge = IsMaxima(e, topY); - - if(IsMaximaEdge) - { - TEdge* eMaxPair = GetMaximaPairEx(e); - IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); - } - - if(IsMaximaEdge) - { - if (m_StrictSimple) m_Maxima.push_back(e->Top.X); - TEdge* ePrev = e->PrevInAEL; - DoMaxima(e); - if( !ePrev ) e = m_ActiveEdges; - else e = ePrev->NextInAEL; - } - else - { - //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... - if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) - { - UpdateEdgeIntoAEL(e); - if (e->OutIdx >= 0) - AddOutPt(e, e->Bot); - AddEdgeToSEL(e); - } - else - { - e->Curr.X = TopX( *e, topY ); - e->Curr.Y = topY; -#ifdef use_xyz - e->Curr.Z = topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0); -#endif - } - - //When StrictlySimple and 'e' is being touched by another edge, then - //make sure both edges have a vertex here ... - if (m_StrictSimple) - { - TEdge* ePrev = e->PrevInAEL; - if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && - (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) - { - IntPoint pt = e->Curr; -#ifdef use_xyz - SetZ(pt, *ePrev, *e); -#endif - OutPt* op = AddOutPt(ePrev, pt); - OutPt* op2 = AddOutPt(e, pt); - AddJoin(op, op2, pt); //StrictlySimple (type-3) join - } - } - - e = e->NextInAEL; - } - } - - //3. Process horizontals at the Top of the scanbeam ... - m_Maxima.sort(); - ProcessHorizontals(); - m_Maxima.clear(); - - //4. Promote intermediate vertices ... - e = m_ActiveEdges; - while(e) - { - if(IsIntermediate(e, topY)) - { - OutPt* op = 0; - if( e->OutIdx >= 0 ) - op = AddOutPt(e, e->Top); - UpdateEdgeIntoAEL(e); - - //if output polygons share an edge, they'll need joining later ... - TEdge* ePrev = e->PrevInAEL; - TEdge* eNext = e->NextInAEL; - if (ePrev && ePrev->Curr.X == e->Bot.X && - ePrev->Curr.Y == e->Bot.Y && op && - ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && - SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, m_UseFullRange) && - (e->WindDelta != 0) && (ePrev->WindDelta != 0)) - { - OutPt* op2 = AddOutPt(ePrev, e->Bot); - AddJoin(op, op2, e->Top); - } - else if (eNext && eNext->Curr.X == e->Bot.X && - eNext->Curr.Y == e->Bot.Y && op && - eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && - SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, m_UseFullRange) && - (e->WindDelta != 0) && (eNext->WindDelta != 0)) - { - OutPt* op2 = AddOutPt(eNext, e->Bot); - AddJoin(op, op2, e->Top); - } - } - e = e->NextInAEL; - } -} -//------------------------------------------------------------------------------ - -void Clipper::FixupOutPolyline(OutRec &outrec) -{ - OutPt *pp = outrec.Pts; - OutPt *lastPP = pp->Prev; - while (pp != lastPP) - { - pp = pp->Next; - if (pp->Pt == pp->Prev->Pt) - { - if (pp == lastPP) lastPP = pp->Prev; - OutPt *tmpPP = pp->Prev; - tmpPP->Next = pp->Next; - pp->Next->Prev = tmpPP; - delete pp; - pp = tmpPP; - } - } - - if (pp == pp->Prev) - { - DisposeOutPts(pp); - outrec.Pts = 0; - return; - } -} -//------------------------------------------------------------------------------ - -void Clipper::FixupOutPolygon(OutRec &outrec) -{ - //FixupOutPolygon() - removes duplicate points and simplifies consecutive - //parallel edges by removing the middle vertex. - OutPt *lastOK = 0; - outrec.BottomPt = 0; - OutPt *pp = outrec.Pts; - bool preserveCol = m_PreserveCollinear || m_StrictSimple; - - for (;;) - { - if (pp->Prev == pp || pp->Prev == pp->Next) - { - DisposeOutPts(pp); - outrec.Pts = 0; - return; - } - - //test for duplicate points and collinear edges ... - if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || - (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && - (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) - { - lastOK = 0; - OutPt *tmp = pp; - pp->Prev->Next = pp->Next; - pp->Next->Prev = pp->Prev; - pp = pp->Prev; - delete tmp; - } - else if (pp == lastOK) break; - else - { - if (!lastOK) lastOK = pp; - pp = pp->Next; - } - } - outrec.Pts = pp; -} -//------------------------------------------------------------------------------ - -int PointCount(OutPt *Pts) -{ - if (!Pts) return 0; - int result = 0; - OutPt* p = Pts; - do - { - result++; - p = p->Next; - } - while (p != Pts); - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::BuildResult(Paths &polys) -{ - polys.reserve(m_PolyOuts.size()); - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - if (!m_PolyOuts[i]->Pts) continue; - Path pg; - OutPt* p = m_PolyOuts[i]->Pts->Prev; - int cnt = PointCount(p); - if (cnt < 2) continue; - pg.reserve(cnt); - for (int i = 0; i < cnt; ++i) - { - pg.push_back(p->Pt); - p = p->Prev; - } - polys.push_back(pg); - } -} -//------------------------------------------------------------------------------ - -void Clipper::BuildResult2(PolyTree& polytree) -{ - polytree.Clear(); - polytree.AllNodes.reserve(m_PolyOuts.size()); - //add each output polygon/contour to polytree ... - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) - { - OutRec* outRec = m_PolyOuts[i]; - int cnt = PointCount(outRec->Pts); - if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) continue; - FixHoleLinkage(*outRec); - PolyNode* pn = new PolyNode(); - //nb: polytree takes ownership of all the PolyNodes - polytree.AllNodes.push_back(pn); - outRec->PolyNd = pn; - pn->Parent = 0; - pn->Index = 0; - pn->Contour.reserve(cnt); - OutPt *op = outRec->Pts->Prev; - for (int j = 0; j < cnt; j++) - { - pn->Contour.push_back(op->Pt); - op = op->Prev; - } - } - - //fixup PolyNode links etc ... - polytree.Childs.reserve(m_PolyOuts.size()); - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) - { - OutRec* outRec = m_PolyOuts[i]; - if (!outRec->PolyNd) continue; - if (outRec->IsOpen) - { - outRec->PolyNd->m_IsOpen = true; - polytree.AddChild(*outRec->PolyNd); - } - else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) - outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); - else - polytree.AddChild(*outRec->PolyNd); - } -} -//------------------------------------------------------------------------------ - -void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) -{ - //just swap the contents (because fIntersectNodes is a single-linked-list) - IntersectNode inode = int1; //gets a copy of Int1 - int1.Edge1 = int2.Edge1; - int1.Edge2 = int2.Edge2; - int1.Pt = int2.Pt; - int2.Edge1 = inode.Edge1; - int2.Edge2 = inode.Edge2; - int2.Pt = inode.Pt; -} -//------------------------------------------------------------------------------ - -inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) -{ - if (e2.Curr.X == e1.Curr.X) - { - if (e2.Top.Y > e1.Top.Y) - return e2.Top.X < TopX(e1, e2.Top.Y); - else return e1.Top.X > TopX(e2, e1.Top.Y); - } - else return e2.Curr.X < e1.Curr.X; -} -//------------------------------------------------------------------------------ - -bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, - cInt& Left, cInt& Right) -{ - if (a1 < a2) - { - if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);} - else {Left = std::max(a1,b2); Right = std::min(a2,b1);} - } - else - { - if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);} - else {Left = std::max(a2,b2); Right = std::min(a1,b1);} - } - return Left < Right; -} -//------------------------------------------------------------------------------ - -inline void UpdateOutPtIdxs(OutRec& outrec) -{ - OutPt* op = outrec.Pts; - do - { - op->Idx = outrec.Idx; - op = op->Prev; - } - while(op != outrec.Pts); -} -//------------------------------------------------------------------------------ - -void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge) -{ - if(!m_ActiveEdges) - { - edge->PrevInAEL = 0; - edge->NextInAEL = 0; - m_ActiveEdges = edge; - } - else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) - { - edge->PrevInAEL = 0; - edge->NextInAEL = m_ActiveEdges; - m_ActiveEdges->PrevInAEL = edge; - m_ActiveEdges = edge; - } - else - { - if(!startEdge) startEdge = m_ActiveEdges; - while(startEdge->NextInAEL && - !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge)) - startEdge = startEdge->NextInAEL; - edge->NextInAEL = startEdge->NextInAEL; - if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge; - edge->PrevInAEL = startEdge; - startEdge->NextInAEL = edge; - } -} -//---------------------------------------------------------------------- - -OutPt* DupOutPt(OutPt* outPt, bool InsertAfter) -{ - OutPt* result = new OutPt; - result->Pt = outPt->Pt; - result->Idx = outPt->Idx; - if (InsertAfter) - { - result->Next = outPt->Next; - result->Prev = outPt; - outPt->Next->Prev = result; - outPt->Next = result; - } - else - { - result->Prev = outPt->Prev; - result->Next = outPt; - outPt->Prev->Next = result; - outPt->Prev = result; - } - return result; -} -//------------------------------------------------------------------------------ - -bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, - const IntPoint Pt, bool DiscardLeft) -{ - Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); - Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); - if (Dir1 == Dir2) return false; - - //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we - //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) - //So, to facilitate this while inserting Op1b and Op2b ... - //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, - //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) - if (Dir1 == dLeftToRight) - { - while (op1->Next->Pt.X <= Pt.X && - op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) - op1 = op1->Next; - if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; - op1b = DupOutPt(op1, !DiscardLeft); - if (op1b->Pt != Pt) - { - op1 = op1b; - op1->Pt = Pt; - op1b = DupOutPt(op1, !DiscardLeft); - } - } - else - { - while (op1->Next->Pt.X >= Pt.X && - op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) - op1 = op1->Next; - if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; - op1b = DupOutPt(op1, DiscardLeft); - if (op1b->Pt != Pt) - { - op1 = op1b; - op1->Pt = Pt; - op1b = DupOutPt(op1, DiscardLeft); - } - } - - if (Dir2 == dLeftToRight) - { - while (op2->Next->Pt.X <= Pt.X && - op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) - op2 = op2->Next; - if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; - op2b = DupOutPt(op2, !DiscardLeft); - if (op2b->Pt != Pt) - { - op2 = op2b; - op2->Pt = Pt; - op2b = DupOutPt(op2, !DiscardLeft); - }; - } else - { - while (op2->Next->Pt.X >= Pt.X && - op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) - op2 = op2->Next; - if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; - op2b = DupOutPt(op2, DiscardLeft); - if (op2b->Pt != Pt) - { - op2 = op2b; - op2->Pt = Pt; - op2b = DupOutPt(op2, DiscardLeft); - }; - }; - - if ((Dir1 == dLeftToRight) == DiscardLeft) - { - op1->Prev = op2; - op2->Next = op1; - op1b->Next = op2b; - op2b->Prev = op1b; - } - else - { - op1->Next = op2; - op2->Prev = op1; - op1b->Prev = op2b; - op2b->Next = op1b; - } - return true; -} -//------------------------------------------------------------------------------ - -bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) -{ - OutPt *op1 = j->OutPt1, *op1b; - OutPt *op2 = j->OutPt2, *op2b; - - //There are 3 kinds of joins for output polygons ... - //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere - //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). - //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same - //location at the Bottom of the overlapping segment (& Join.OffPt is above). - //3. StrictSimple joins where edges touch but are not collinear and where - //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. - bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); - - if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && - (j->OffPt == j->OutPt2->Pt)) - { - //Strictly Simple join ... - if (outRec1 != outRec2) return false; - op1b = j->OutPt1->Next; - while (op1b != op1 && (op1b->Pt == j->OffPt)) - op1b = op1b->Next; - bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); - op2b = j->OutPt2->Next; - while (op2b != op2 && (op2b->Pt == j->OffPt)) - op2b = op2b->Next; - bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); - if (reverse1 == reverse2) return false; - if (reverse1) - { - op1b = DupOutPt(op1, false); - op2b = DupOutPt(op2, true); - op1->Prev = op2; - op2->Next = op1; - op1b->Next = op2b; - op2b->Prev = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } else - { - op1b = DupOutPt(op1, true); - op2b = DupOutPt(op2, false); - op1->Next = op2; - op2->Prev = op1; - op1b->Prev = op2b; - op2b->Next = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } - } - else if (isHorizontal) - { - //treat horizontal joins differently to non-horizontal joins since with - //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt - //may be anywhere along the horizontal edge. - op1b = op1; - while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) - op1 = op1->Prev; - while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) - op1b = op1b->Next; - if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' - - op2b = op2; - while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) - op2 = op2->Prev; - while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) - op2b = op2b->Next; - if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' - - cInt Left, Right; - //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges - if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) - return false; - - //DiscardLeftSide: when overlapping edges are joined, a spike will created - //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up - //on the discard Side as either may still be needed for other joins ... - IntPoint Pt; - bool DiscardLeftSide; - if (op1->Pt.X >= Left && op1->Pt.X <= Right) - { - Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); - } - else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) - { - Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); - } - else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) - { - Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; - } - else - { - Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); - } - j->OutPt1 = op1; j->OutPt2 = op2; - return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); - } else - { - //nb: For non-horizontal joins ... - // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y - // 2. Jr.OutPt1.Pt > Jr.OffPt.Y - - //make sure the polygons are correctly oriented ... - op1b = op1->Next; - while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; - bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || - !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); - if (Reverse1) - { - op1b = op1->Prev; - while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; - if ((op1b->Pt.Y > op1->Pt.Y) || - !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; - }; - op2b = op2->Next; - while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; - bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || - !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); - if (Reverse2) - { - op2b = op2->Prev; - while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; - if ((op2b->Pt.Y > op2->Pt.Y) || - !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; - } - - if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || - ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; - - if (Reverse1) - { - op1b = DupOutPt(op1, false); - op2b = DupOutPt(op2, true); - op1->Prev = op2; - op2->Next = op1; - op1b->Next = op2b; - op2b->Prev = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } else - { - op1b = DupOutPt(op1, true); - op2b = DupOutPt(op2, false); - op1->Next = op2; - op2->Prev = op1; - op1b->Prev = op2b; - op2b->Next = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } - } -} -//---------------------------------------------------------------------- - -static OutRec* ParseFirstLeft(OutRec* FirstLeft) -{ - while (FirstLeft && !FirstLeft->Pts) - FirstLeft = FirstLeft->FirstLeft; - return FirstLeft; -} -//------------------------------------------------------------------------------ - -void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) -{ - //tests if NewOutRec contains the polygon before reassigning FirstLeft - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec* outRec = m_PolyOuts[i]; - OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); - if (outRec->Pts && firstLeft == OldOutRec) - { - if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) - outRec->FirstLeft = NewOutRec; - } - } -} -//---------------------------------------------------------------------- - -void Clipper::FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec) -{ - //A polygon has split into two such that one is now the inner of the other. - //It's possible that these polygons now wrap around other polygons, so check - //every polygon that's also contained by OuterOutRec's FirstLeft container - //(including 0) to see if they've become inner to the new inner polygon ... - OutRec* orfl = OuterOutRec->FirstLeft; - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec* outRec = m_PolyOuts[i]; - - if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec) - continue; - OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); - if (firstLeft != orfl && firstLeft != InnerOutRec && firstLeft != OuterOutRec) - continue; - if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts)) - outRec->FirstLeft = InnerOutRec; - else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts)) - outRec->FirstLeft = OuterOutRec; - else if (outRec->FirstLeft == InnerOutRec || outRec->FirstLeft == OuterOutRec) - outRec->FirstLeft = orfl; - } -} -//---------------------------------------------------------------------- -void Clipper::FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec) -{ - //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec* outRec = m_PolyOuts[i]; - OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); - if (outRec->Pts && firstLeft == OldOutRec) - outRec->FirstLeft = NewOutRec; - } -} -//---------------------------------------------------------------------- - -void Clipper::JoinCommonEdges() -{ - for (JoinList::size_type i = 0; i < m_Joins.size(); i++) - { - Join* join = m_Joins[i]; - - OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); - OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); - - if (!outRec1->Pts || !outRec2->Pts) continue; - if (outRec1->IsOpen || outRec2->IsOpen) continue; - - //get the polygon fragment with the correct hole state (FirstLeft) - //before calling JoinPoints() ... - OutRec *holeStateRec; - if (outRec1 == outRec2) holeStateRec = outRec1; - else if (OutRec1RightOfOutRec2(outRec1, outRec2)) holeStateRec = outRec2; - else if (OutRec1RightOfOutRec2(outRec2, outRec1)) holeStateRec = outRec1; - else holeStateRec = GetLowermostRec(outRec1, outRec2); - - if (!JoinPoints(join, outRec1, outRec2)) continue; - - if (outRec1 == outRec2) - { - //instead of joining two polygons, we've just created a new one by - //splitting one polygon into two. - outRec1->Pts = join->OutPt1; - outRec1->BottomPt = 0; - outRec2 = CreateOutRec(); - outRec2->Pts = join->OutPt2; - - //update all OutRec2.Pts Idx's ... - UpdateOutPtIdxs(*outRec2); - - if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) - { - //outRec1 contains outRec2 ... - outRec2->IsHole = !outRec1->IsHole; - outRec2->FirstLeft = outRec1; - - if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); - - if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) - ReversePolyPtLinks(outRec2->Pts); - - } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) - { - //outRec2 contains outRec1 ... - outRec2->IsHole = outRec1->IsHole; - outRec1->IsHole = !outRec2->IsHole; - outRec2->FirstLeft = outRec1->FirstLeft; - outRec1->FirstLeft = outRec2; - - if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); - - if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) - ReversePolyPtLinks(outRec1->Pts); - } - else - { - //the 2 polygons are completely separate ... - outRec2->IsHole = outRec1->IsHole; - outRec2->FirstLeft = outRec1->FirstLeft; - - //fixup FirstLeft pointers that may need reassigning to OutRec2 - if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); - } - - } else - { - //joined 2 polygons together ... - - outRec2->Pts = 0; - outRec2->BottomPt = 0; - outRec2->Idx = outRec1->Idx; - - outRec1->IsHole = holeStateRec->IsHole; - if (holeStateRec == outRec2) - outRec1->FirstLeft = outRec2->FirstLeft; - outRec2->FirstLeft = outRec1; - - if (m_UsingPolyTree) FixupFirstLefts3(outRec2, outRec1); - } - } -} - -//------------------------------------------------------------------------------ -// ClipperOffset support functions ... -//------------------------------------------------------------------------------ - -DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) -{ - if(pt2.X == pt1.X && pt2.Y == pt1.Y) - return DoublePoint(0, 0); - - double Dx = (double)(pt2.X - pt1.X); - double dy = (double)(pt2.Y - pt1.Y); - double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); - Dx *= f; - dy *= f; - return DoublePoint(dy, -Dx); -} - -//------------------------------------------------------------------------------ -// ClipperOffset class -//------------------------------------------------------------------------------ - -ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) -{ - this->MiterLimit = miterLimit; - this->ArcTolerance = arcTolerance; - m_lowest.X = -1; -} -//------------------------------------------------------------------------------ - -ClipperOffset::~ClipperOffset() -{ - Clear(); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::Clear() -{ - for (int i = 0; i < m_polyNodes.ChildCount(); ++i) - delete m_polyNodes.Childs[i]; - m_polyNodes.Childs.clear(); - m_lowest.X = -1; -} -//------------------------------------------------------------------------------ - -void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType) -{ - int highI = (int)path.size() - 1; - if (highI < 0) return; - PolyNode* newNode = new PolyNode(); - newNode->m_jointype = joinType; - newNode->m_endtype = endType; - - //strip duplicate points from path and also get index to the lowest point ... - if (endType == etClosedLine || endType == etClosedPolygon) - while (highI > 0 && path[0] == path[highI]) highI--; - newNode->Contour.reserve(highI + 1); - newNode->Contour.push_back(path[0]); - int j = 0, k = 0; - for (int i = 1; i <= highI; i++) - if (newNode->Contour[j] != path[i]) - { - j++; - newNode->Contour.push_back(path[i]); - if (path[i].Y > newNode->Contour[k].Y || - (path[i].Y == newNode->Contour[k].Y && - path[i].X < newNode->Contour[k].X)) k = j; - } - if (endType == etClosedPolygon && j < 2) - { - delete newNode; - return; - } - m_polyNodes.AddChild(*newNode); - - //if this path's lowest pt is lower than all the others then update m_lowest - if (endType != etClosedPolygon) return; - if (m_lowest.X < 0) - m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); - else - { - IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; - if (newNode->Contour[k].Y > ip.Y || - (newNode->Contour[k].Y == ip.Y && - newNode->Contour[k].X < ip.X)) - m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) -{ - for (Paths::size_type i = 0; i < paths.size(); ++i) - AddPath(paths[i], joinType, endType); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::FixOrientations() -{ - //fixup orientations of all closed paths if the orientation of the - //closed path with the lowermost vertex is wrong ... - if (m_lowest.X >= 0 && - !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) - { - for (int i = 0; i < m_polyNodes.ChildCount(); ++i) - { - PolyNode& node = *m_polyNodes.Childs[i]; - if (node.m_endtype == etClosedPolygon || - (node.m_endtype == etClosedLine && Orientation(node.Contour))) - ReversePath(node.Contour); - } - } else - { - for (int i = 0; i < m_polyNodes.ChildCount(); ++i) - { - PolyNode& node = *m_polyNodes.Childs[i]; - if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) - ReversePath(node.Contour); - } - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::Execute(Paths& solution, double delta) -{ - solution.clear(); - FixOrientations(); - DoOffset(delta); - - //now clean up 'corners' ... - Clipper clpr; - clpr.AddPaths(m_destPolys, ptSubject, true); - if (delta > 0) - { - clpr.Execute(ctUnion, solution, pftPositive, pftPositive); - } - else - { - IntRect r = clpr.GetBounds(); - Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); - - clpr.AddPath(outer, ptSubject, true); - clpr.ReverseSolution(true); - clpr.Execute(ctUnion, solution, pftNegative, pftNegative); - if (solution.size() > 0) solution.erase(solution.begin()); - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::Execute(PolyTree& solution, double delta) -{ - solution.Clear(); - FixOrientations(); - DoOffset(delta); - - //now clean up 'corners' ... - Clipper clpr; - clpr.AddPaths(m_destPolys, ptSubject, true); - if (delta > 0) - { - clpr.Execute(ctUnion, solution, pftPositive, pftPositive); - } - else - { - IntRect r = clpr.GetBounds(); - Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); - - clpr.AddPath(outer, ptSubject, true); - clpr.ReverseSolution(true); - clpr.Execute(ctUnion, solution, pftNegative, pftNegative); - //remove the outer PolyNode rectangle ... - if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) - { - PolyNode* outerNode = solution.Childs[0]; - solution.Childs.reserve(outerNode->ChildCount()); - solution.Childs[0] = outerNode->Childs[0]; - solution.Childs[0]->Parent = outerNode->Parent; - for (int i = 1; i < outerNode->ChildCount(); ++i) - solution.AddChild(*outerNode->Childs[i]); - } - else - solution.Clear(); - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoOffset(double delta) -{ - m_destPolys.clear(); - m_delta = delta; - - //if Zero offset, just copy any CLOSED polygons to m_p and return ... - if (NEAR_ZERO(delta)) - { - m_destPolys.reserve(m_polyNodes.ChildCount()); - for (int i = 0; i < m_polyNodes.ChildCount(); i++) - { - PolyNode& node = *m_polyNodes.Childs[i]; - if (node.m_endtype == etClosedPolygon) - m_destPolys.push_back(node.Contour); - } - return; - } - - //see offset_triginometry3.svg in the documentation folder ... - if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); - else m_miterLim = 0.5; - - double y; - if (ArcTolerance <= 0.0) y = def_arc_tolerance; - else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) - y = std::fabs(delta) * def_arc_tolerance; - else y = ArcTolerance; - //see offset_triginometry2.svg in the documentation folder ... - double steps = pi / std::acos(1 - y / std::fabs(delta)); - if (steps > std::fabs(delta) * pi) - steps = std::fabs(delta) * pi; //ie excessive precision check - m_sin = std::sin(two_pi / steps); - m_cos = std::cos(two_pi / steps); - m_StepsPerRad = steps / two_pi; - if (delta < 0.0) m_sin = -m_sin; - - m_destPolys.reserve(m_polyNodes.ChildCount() * 2); - for (int i = 0; i < m_polyNodes.ChildCount(); i++) - { - PolyNode& node = *m_polyNodes.Childs[i]; - m_srcPoly = node.Contour; - - int len = (int)m_srcPoly.size(); - if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) - continue; - - m_destPoly.clear(); - if (len == 1) - { - if (node.m_jointype == jtRound) - { - double X = 1.0, Y = 0.0; - for (cInt j = 1; j <= steps; j++) - { - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[0].X + X * delta), - Round(m_srcPoly[0].Y + Y * delta))); - double X2 = X; - X = X * m_cos - m_sin * Y; - Y = X2 * m_sin + Y * m_cos; - } - } - else - { - double X = -1.0, Y = -1.0; - for (int j = 0; j < 4; ++j) - { - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[0].X + X * delta), - Round(m_srcPoly[0].Y + Y * delta))); - if (X < 0) X = 1; - else if (Y < 0) Y = 1; - else X = -1; - } - } - m_destPolys.push_back(m_destPoly); - continue; - } - //build m_normals ... - m_normals.clear(); - m_normals.reserve(len); - for (int j = 0; j < len - 1; ++j) - m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); - if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) - m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); - else - m_normals.push_back(DoublePoint(m_normals[len - 2])); - - if (node.m_endtype == etClosedPolygon) - { - int k = len - 1; - for (int j = 0; j < len; ++j) - OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); - } - else if (node.m_endtype == etClosedLine) - { - int k = len - 1; - for (int j = 0; j < len; ++j) - OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); - m_destPoly.clear(); - //re-build m_normals ... - DoublePoint n = m_normals[len -1]; - for (int j = len - 1; j > 0; j--) - m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); - m_normals[0] = DoublePoint(-n.X, -n.Y); - k = 0; - for (int j = len - 1; j >= 0; j--) - OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); - } - else - { - int k = 0; - for (int j = 1; j < len - 1; ++j) - OffsetPoint(j, k, node.m_jointype); - - IntPoint pt1; - if (node.m_endtype == etOpenButt) - { - int j = len - 1; - pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * - delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); - m_destPoly.push_back(pt1); - pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * - delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); - m_destPoly.push_back(pt1); - } - else - { - int j = len - 1; - k = len - 2; - m_sinA = 0; - m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); - if (node.m_endtype == etOpenSquare) - DoSquare(j, k); - else - DoRound(j, k); - } - - //re-build m_normals ... - for (int j = len - 1; j > 0; j--) - m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); - m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); - - k = len - 1; - for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); - - if (node.m_endtype == etOpenButt) - { - pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), - (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); - m_destPoly.push_back(pt1); - pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), - (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); - m_destPoly.push_back(pt1); - } - else - { - k = 1; - m_sinA = 0; - if (node.m_endtype == etOpenSquare) - DoSquare(0, 1); - else - DoRound(0, 1); - } - m_destPolys.push_back(m_destPoly); - } - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) -{ - //cross product ... - m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); - if (std::fabs(m_sinA * m_delta) < 1.0) - { - //dot product ... - double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y ); - if (cosA > 0) // angle => 0 degrees - { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); - return; - } - //else angle => 180 degrees - } - else if (m_sinA > 1.0) m_sinA = 1.0; - else if (m_sinA < -1.0) m_sinA = -1.0; - - if (m_sinA * m_delta < 0) - { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); - m_destPoly.push_back(m_srcPoly[j]); - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); - } - else - switch (jointype) - { - case jtMiter: - { - double r = 1 + (m_normals[j].X * m_normals[k].X + - m_normals[j].Y * m_normals[k].Y); - if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); - break; - } - case jtSquare: DoSquare(j, k); break; - case jtRound: DoRound(j, k); break; - } - k = j; -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoSquare(int j, int k) -{ - double dx = std::tan(std::atan2(m_sinA, - m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), - Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), - Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoMiter(int j, int k, double r) -{ - double q = m_delta / r; - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), - Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoRound(int j, int k) -{ - double a = std::atan2(m_sinA, - m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); - int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); - - double X = m_normals[k].X, Y = m_normals[k].Y, X2; - for (int i = 0; i < steps; ++i) - { - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + X * m_delta), - Round(m_srcPoly[j].Y + Y * m_delta))); - X2 = X; - X = X * m_cos - m_sin * Y; - Y = X2 * m_sin + Y * m_cos; - } - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_normals[j].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); -} - -//------------------------------------------------------------------------------ -// Miscellaneous public functions -//------------------------------------------------------------------------------ - -void Clipper::DoSimplePolygons() -{ - PolyOutList::size_type i = 0; - while (i < m_PolyOuts.size()) - { - OutRec* outrec = m_PolyOuts[i++]; - OutPt* op = outrec->Pts; - if (!op || outrec->IsOpen) continue; - do //for each Pt in Polygon until duplicate found do ... - { - OutPt* op2 = op->Next; - while (op2 != outrec->Pts) - { - if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) - { - //split the polygon into two ... - OutPt* op3 = op->Prev; - OutPt* op4 = op2->Prev; - op->Prev = op4; - op4->Next = op; - op2->Prev = op3; - op3->Next = op2; - - outrec->Pts = op; - OutRec* outrec2 = CreateOutRec(); - outrec2->Pts = op2; - UpdateOutPtIdxs(*outrec2); - if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) - { - //OutRec2 is contained by OutRec1 ... - outrec2->IsHole = !outrec->IsHole; - outrec2->FirstLeft = outrec; - if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec); - } - else - if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) - { - //OutRec1 is contained by OutRec2 ... - outrec2->IsHole = outrec->IsHole; - outrec->IsHole = !outrec2->IsHole; - outrec2->FirstLeft = outrec->FirstLeft; - outrec->FirstLeft = outrec2; - if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2); - } - else - { - //the 2 polygons are separate ... - outrec2->IsHole = outrec->IsHole; - outrec2->FirstLeft = outrec->FirstLeft; - if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2); - } - op2 = op; //ie get ready for the Next iteration - } - op2 = op2->Next; - } - op = op->Next; - } - while (op != outrec->Pts); - } -} -//------------------------------------------------------------------------------ - -void ReversePath(Path& p) -{ - std::reverse(p.begin(), p.end()); -} -//------------------------------------------------------------------------------ - -void ReversePaths(Paths& p) -{ - for (Paths::size_type i = 0; i < p.size(); ++i) - ReversePath(p[i]); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) -{ - Clipper c; - c.StrictlySimple(true); - c.AddPath(in_poly, ptSubject, true); - c.Execute(ctUnion, out_polys, fillType, fillType); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) -{ - Clipper c; - c.StrictlySimple(true); - c.AddPaths(in_polys, ptSubject, true); - c.Execute(ctUnion, out_polys, fillType, fillType); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygons(Paths &polys, PolyFillType fillType) -{ - SimplifyPolygons(polys, polys, fillType); -} -//------------------------------------------------------------------------------ - -inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) -{ - double Dx = ((double)pt1.X - pt2.X); - double dy = ((double)pt1.Y - pt2.Y); - return (Dx*Dx + dy*dy); -} -//------------------------------------------------------------------------------ - -double DistanceFromLineSqrd( - const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) -{ - //The equation of a line in general form (Ax + By + C = 0) - //given 2 points (x¹,y¹) & (x²,y²) is ... - //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0 - //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ - //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) - //see http://en.wikipedia.org/wiki/Perpendicular_distance - double A = double(ln1.Y - ln2.Y); - double B = double(ln2.X - ln1.X); - double C = A * ln1.X + B * ln1.Y; - C = A * pt.X + B * pt.Y - C; - return (C * C) / (A * A + B * B); -} -//--------------------------------------------------------------------------- - -bool SlopesNearCollinear(const IntPoint& pt1, - const IntPoint& pt2, const IntPoint& pt3, double distSqrd) -{ - //this function is more accurate when the point that's geometrically - //between the other 2 points is the one that's tested for distance. - //ie makes it more likely to pick up 'spikes' ... - if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) - { - if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) - return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) - return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; - else - return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; - } - else - { - if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) - return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) - return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; - else - return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; - } -} -//------------------------------------------------------------------------------ - -bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) -{ - double Dx = (double)pt1.X - pt2.X; - double dy = (double)pt1.Y - pt2.Y; - return ((Dx * Dx) + (dy * dy) <= distSqrd); -} -//------------------------------------------------------------------------------ - -OutPt* ExcludeOp(OutPt* op) -{ - OutPt* result = op->Prev; - result->Next = op->Next; - op->Next->Prev = result; - result->Idx = 0; - return result; -} -//------------------------------------------------------------------------------ - -void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) -{ - //distance = proximity in units/pixels below which vertices - //will be stripped. Default ~= sqrt(2). - - size_t size = in_poly.size(); - - if (size == 0) - { - out_poly.clear(); - return; - } - - OutPt* outPts = new OutPt[size]; - for (size_t i = 0; i < size; ++i) - { - outPts[i].Pt = in_poly[i]; - outPts[i].Next = &outPts[(i + 1) % size]; - outPts[i].Next->Prev = &outPts[i]; - outPts[i].Idx = 0; - } - - double distSqrd = distance * distance; - OutPt* op = &outPts[0]; - while (op->Idx == 0 && op->Next != op->Prev) - { - if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) - { - op = ExcludeOp(op); - size--; - } - else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) - { - ExcludeOp(op->Next); - op = ExcludeOp(op); - size -= 2; - } - else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) - { - op = ExcludeOp(op); - size--; - } - else - { - op->Idx = 1; - op = op->Next; - } - } - - if (size < 3) size = 0; - out_poly.resize(size); - for (size_t i = 0; i < size; ++i) - { - out_poly[i] = op->Pt; - op = op->Next; - } - delete [] outPts; -} -//------------------------------------------------------------------------------ - -void CleanPolygon(Path& poly, double distance) -{ - CleanPolygon(poly, poly, distance); -} -//------------------------------------------------------------------------------ - -void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) -{ - out_polys.resize(in_polys.size()); - for (Paths::size_type i = 0; i < in_polys.size(); ++i) - CleanPolygon(in_polys[i], out_polys[i], distance); -} -//------------------------------------------------------------------------------ - -void CleanPolygons(Paths& polys, double distance) -{ - CleanPolygons(polys, polys, distance); -} -//------------------------------------------------------------------------------ - -void Minkowski(const Path& poly, const Path& path, - Paths& solution, bool isSum, bool isClosed) -{ - int delta = (isClosed ? 1 : 0); - size_t polyCnt = poly.size(); - size_t pathCnt = path.size(); - Paths pp; - pp.reserve(pathCnt); - if (isSum) - for (size_t i = 0; i < pathCnt; ++i) - { - Path p; - p.reserve(polyCnt); - for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); - pp.push_back(p); - } - else - for (size_t i = 0; i < pathCnt; ++i) - { - Path p; - p.reserve(polyCnt); - for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); - pp.push_back(p); - } - - solution.clear(); - solution.reserve((pathCnt + delta) * (polyCnt + 1)); - for (size_t i = 0; i < pathCnt - 1 + delta; ++i) - for (size_t j = 0; j < polyCnt; ++j) - { - Path quad; - quad.reserve(4); - quad.push_back(pp[i % pathCnt][j % polyCnt]); - quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); - quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); - quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); - if (!Orientation(quad)) ReversePath(quad); - solution.push_back(quad); - } -} -//------------------------------------------------------------------------------ - -void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) -{ - Minkowski(pattern, path, solution, true, pathIsClosed); - Clipper c; - c.AddPaths(solution, ptSubject, true); - c.Execute(ctUnion, solution, pftNonZero, pftNonZero); -} -//------------------------------------------------------------------------------ - -void TranslatePath(const Path& input, Path& output, const IntPoint delta) -{ - //precondition: input != output - output.resize(input.size()); - for (size_t i = 0; i < input.size(); ++i) - output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); -} -//------------------------------------------------------------------------------ - -void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed) -{ - Clipper c; - for (size_t i = 0; i < paths.size(); ++i) - { - Paths tmp; - Minkowski(pattern, paths[i], tmp, true, pathIsClosed); - c.AddPaths(tmp, ptSubject, true); - if (pathIsClosed) - { - Path tmp2; - TranslatePath(paths[i], tmp2, pattern[0]); - c.AddPath(tmp2, ptClip, true); - } - } - c.Execute(ctUnion, solution, pftNonZero, pftNonZero); -} -//------------------------------------------------------------------------------ - -void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution) -{ - Minkowski(poly1, poly2, solution, false, true); - Clipper c; - c.AddPaths(solution, ptSubject, true); - c.Execute(ctUnion, solution, pftNonZero, pftNonZero); -} -//------------------------------------------------------------------------------ - -enum NodeType {ntAny, ntOpen, ntClosed}; - -void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& paths) -{ - bool match = true; - if (nodetype == ntClosed) match = !polynode.IsOpen(); - else if (nodetype == ntOpen) return; - - if (!polynode.Contour.empty() && match) - paths.push_back(polynode.Contour); - for (int i = 0; i < polynode.ChildCount(); ++i) - AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); -} -//------------------------------------------------------------------------------ - -void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) -{ - paths.resize(0); - paths.reserve(polytree.Total()); - AddPolyNodeToPaths(polytree, ntAny, paths); -} -//------------------------------------------------------------------------------ - -void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) -{ - paths.resize(0); - paths.reserve(polytree.Total()); - AddPolyNodeToPaths(polytree, ntClosed, paths); -} -//------------------------------------------------------------------------------ - -void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) -{ - paths.resize(0); - paths.reserve(polytree.Total()); - //Open paths are top level only, so ... - for (int i = 0; i < polytree.ChildCount(); ++i) - if (polytree.Childs[i]->IsOpen()) - paths.push_back(polytree.Childs[i]->Contour); -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, const IntPoint &p) -{ - s << "(" << p.X << "," << p.Y << ")"; - return s; -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, const Path &p) -{ - if (p.empty()) return s; - Path::size_type last = p.size() -1; - for (Path::size_type i = 0; i < last; i++) - s << "(" << p[i].X << "," << p[i].Y << "), "; - s << "(" << p[last].X << "," << p[last].Y << ")\n"; - return s; -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, const Paths &p) -{ - for (Paths::size_type i = 0; i < p.size(); i++) - s << p[i]; - s << "\n"; - return s; -} -//------------------------------------------------------------------------------ - -} //ClipperLib namespace diff --git a/thirdparty/misc/clipper.hpp b/thirdparty/misc/clipper.hpp deleted file mode 100644 index 5a19617bb4..0000000000 --- a/thirdparty/misc/clipper.hpp +++ /dev/null @@ -1,406 +0,0 @@ -/******************************************************************************* -* * -* Author : Angus Johnson * -* Version : 6.4.2 * -* Date : 27 February 2017 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2017 * -* * -* License: * -* Use, modification & distribution is subject to Boost Software License Ver 1. * -* http://www.boost.org/LICENSE_1_0.txt * -* * -* Attributions: * -* The code in this library is an extension of Bala Vatti's clipping algorithm: * -* "A generic solution to polygon clipping" * -* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * -* http://portal.acm.org/citation.cfm?id=129906 * -* * -* Computer graphics and geometric modeling: implementation and algorithms * -* By Max K. Agoston * -* Springer; 1 edition (January 4, 2005) * -* http://books.google.com/books?q=vatti+clipping+agoston * -* * -* See also: * -* "Polygon Offsetting by Computing Winding Numbers" * -* Paper no. DETC2005-85513 pp. 565-575 * -* ASME 2005 International Design Engineering Technical Conferences * -* and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24-28, 2005 , Long Beach, California, USA * -* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * -* * -*******************************************************************************/ - -#ifndef clipper_hpp -#define clipper_hpp - -#define CLIPPER_VERSION "6.4.2" - -//use_int32: When enabled 32bit ints are used instead of 64bit ints. This -//improve performance but coordinate values are limited to the range +/- 46340 -//#define use_int32 - -//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. -//#define use_xyz - -//use_lines: Enables line clipping. Adds a very minor cost to performance. -#define use_lines - -//use_deprecated: Enables temporary support for the obsolete functions -//#define use_deprecated - -#include <vector> -#include <list> -#include <set> -#include <stdexcept> -#include <cstring> -#include <cstdlib> -#include <ostream> -#include <functional> -#include <queue> - -namespace ClipperLib { - -enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; -enum PolyType { ptSubject, ptClip }; -//By far the most widely used winding rules for polygon filling are -//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) -//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) -//see http://glprogramming.com/red/chapter11.html -enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; - -#ifdef use_int32 - typedef int cInt; - static cInt const loRange = 0x7FFF; - static cInt const hiRange = 0x7FFF; -#else - typedef signed long long cInt; - static cInt const loRange = 0x3FFFFFFF; - static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; - typedef signed long long long64; //used by Int128 class - typedef unsigned long long ulong64; - -#endif - -struct IntPoint { - cInt X; - cInt Y; -#ifdef use_xyz - cInt Z; - IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; -#else - IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {}; -#endif - - friend inline bool operator== (const IntPoint& a, const IntPoint& b) - { - return a.X == b.X && a.Y == b.Y; - } - friend inline bool operator!= (const IntPoint& a, const IntPoint& b) - { - return a.X != b.X || a.Y != b.Y; - } -}; -//------------------------------------------------------------------------------ - -typedef std::vector< IntPoint > Path; -typedef std::vector< Path > Paths; - -inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;} -inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;} - -std::ostream& operator <<(std::ostream &s, const IntPoint &p); -std::ostream& operator <<(std::ostream &s, const Path &p); -std::ostream& operator <<(std::ostream &s, const Paths &p); - -struct DoublePoint -{ - double X; - double Y; - DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} - DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} -}; -//------------------------------------------------------------------------------ - -#ifdef use_xyz -typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt); -#endif - -enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; -enum JoinType {jtSquare, jtRound, jtMiter}; -enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound}; - -class PolyNode; -typedef std::vector< PolyNode* > PolyNodes; - -class PolyNode -{ -public: - PolyNode(); - virtual ~PolyNode(){}; - Path Contour; - PolyNodes Childs; - PolyNode* Parent; - PolyNode* GetNext() const; - bool IsHole() const; - bool IsOpen() const; - int ChildCount() const; -private: - //PolyNode& operator =(PolyNode& other); - unsigned Index; //node index in Parent.Childs - bool m_IsOpen; - JoinType m_jointype; - EndType m_endtype; - PolyNode* GetNextSiblingUp() const; - void AddChild(PolyNode& child); - friend class Clipper; //to access Index - friend class ClipperOffset; -}; - -class PolyTree: public PolyNode -{ -public: - ~PolyTree(){ Clear(); }; - PolyNode* GetFirst() const; - void Clear(); - int Total() const; -private: - //PolyTree& operator =(PolyTree& other); - PolyNodes AllNodes; - friend class Clipper; //to access AllNodes -}; - -bool Orientation(const Path &poly); -double Area(const Path &poly); -int PointInPolygon(const IntPoint &pt, const Path &path); - -void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); - -void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); -void CleanPolygon(Path& poly, double distance = 1.415); -void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415); -void CleanPolygons(Paths& polys, double distance = 1.415); - -void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed); -void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed); -void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); - -void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); -void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); -void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); - -void ReversePath(Path& p); -void ReversePaths(Paths& p); - -struct IntRect { cInt left; cInt top; cInt right; cInt bottom; }; - -//enums that are used internally ... -enum EdgeSide { esLeft = 1, esRight = 2}; - -//forward declarations (for stuff used internally) ... -struct TEdge; -struct IntersectNode; -struct LocalMinimum; -struct OutPt; -struct OutRec; -struct Join; - -typedef std::vector < OutRec* > PolyOutList; -typedef std::vector < TEdge* > EdgeList; -typedef std::vector < Join* > JoinList; -typedef std::vector < IntersectNode* > IntersectList; - -//------------------------------------------------------------------------------ - -//ClipperBase is the ancestor to the Clipper class. It should not be -//instantiated directly. This class simply abstracts the conversion of sets of -//polygon coordinates into edge objects that are stored in a LocalMinima list. -class ClipperBase -{ -public: - ClipperBase(); - virtual ~ClipperBase(); - virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); - bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); - virtual void Clear(); - IntRect GetBounds(); - bool PreserveCollinear() {return m_PreserveCollinear;}; - void PreserveCollinear(bool value) {m_PreserveCollinear = value;}; -protected: - void DisposeLocalMinimaList(); - TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); - virtual void Reset(); - TEdge* ProcessBound(TEdge* E, bool IsClockwise); - void InsertScanbeam(const cInt Y); - bool PopScanbeam(cInt &Y); - bool LocalMinimaPending(); - bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin); - OutRec* CreateOutRec(); - void DisposeAllOutRecs(); - void DisposeOutRec(PolyOutList::size_type index); - void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); - void DeleteFromAEL(TEdge *e); - void UpdateEdgeIntoAEL(TEdge *&e); - - typedef std::vector<LocalMinimum> MinimaList; - MinimaList::iterator m_CurrentLM; - MinimaList m_MinimaList; - - bool m_UseFullRange; - EdgeList m_edges; - bool m_PreserveCollinear; - bool m_HasOpenPaths; - PolyOutList m_PolyOuts; - TEdge *m_ActiveEdges; - - typedef std::priority_queue<cInt> ScanbeamList; - ScanbeamList m_Scanbeam; -}; -//------------------------------------------------------------------------------ - -class Clipper : public virtual ClipperBase -{ -public: - Clipper(int initOptions = 0); - bool Execute(ClipType clipType, - Paths &solution, - PolyFillType fillType = pftEvenOdd); - bool Execute(ClipType clipType, - Paths &solution, - PolyFillType subjFillType, - PolyFillType clipFillType); - bool Execute(ClipType clipType, - PolyTree &polytree, - PolyFillType fillType = pftEvenOdd); - bool Execute(ClipType clipType, - PolyTree &polytree, - PolyFillType subjFillType, - PolyFillType clipFillType); - bool ReverseSolution() { return m_ReverseOutput; }; - void ReverseSolution(bool value) {m_ReverseOutput = value;}; - bool StrictlySimple() {return m_StrictSimple;}; - void StrictlySimple(bool value) {m_StrictSimple = value;}; - //set the callback function for z value filling on intersections (otherwise Z is 0) -#ifdef use_xyz - void ZFillFunction(ZFillCallback zFillFunc); -#endif -protected: - virtual bool ExecuteInternal(); -private: - JoinList m_Joins; - JoinList m_GhostJoins; - IntersectList m_IntersectList; - ClipType m_ClipType; - typedef std::list<cInt> MaximaList; - MaximaList m_Maxima; - TEdge *m_SortedEdges; - bool m_ExecuteLocked; - PolyFillType m_ClipFillType; - PolyFillType m_SubjFillType; - bool m_ReverseOutput; - bool m_UsingPolyTree; - bool m_StrictSimple; -#ifdef use_xyz - ZFillCallback m_ZFill; //custom callback -#endif - void SetWindingCount(TEdge& edge); - bool IsEvenOddFillType(const TEdge& edge) const; - bool IsEvenOddAltFillType(const TEdge& edge) const; - void InsertLocalMinimaIntoAEL(const cInt botY); - void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge); - void AddEdgeToSEL(TEdge *edge); - bool PopEdgeFromSEL(TEdge *&edge); - void CopyAELToSEL(); - void DeleteFromSEL(TEdge *e); - void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); - bool IsContributing(const TEdge& edge) const; - bool IsTopHorz(const cInt XPos); - void DoMaxima(TEdge *e); - void ProcessHorizontals(); - void ProcessHorizontal(TEdge *horzEdge); - void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); - OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); - OutRec* GetOutRec(int idx); - void AppendPolygon(TEdge *e1, TEdge *e2); - void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); - OutPt* AddOutPt(TEdge *e, const IntPoint &pt); - OutPt* GetLastOutPt(TEdge *e); - bool ProcessIntersections(const cInt topY); - void BuildIntersectList(const cInt topY); - void ProcessIntersectList(); - void ProcessEdgesAtTopOfScanbeam(const cInt topY); - void BuildResult(Paths& polys); - void BuildResult2(PolyTree& polytree); - void SetHoleState(TEdge *e, OutRec *outrec); - void DisposeIntersectNodes(); - bool FixupIntersectionOrder(); - void FixupOutPolygon(OutRec &outrec); - void FixupOutPolyline(OutRec &outrec); - bool IsHole(TEdge *e); - bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); - void FixHoleLinkage(OutRec &outrec); - void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); - void ClearJoins(); - void ClearGhostJoins(); - void AddGhostJoin(OutPt *op, const IntPoint offPt); - bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2); - void JoinCommonEdges(); - void DoSimplePolygons(); - void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); - void FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec); - void FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec); -#ifdef use_xyz - void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); -#endif -}; -//------------------------------------------------------------------------------ - -class ClipperOffset -{ -public: - ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); - ~ClipperOffset(); - void AddPath(const Path& path, JoinType joinType, EndType endType); - void AddPaths(const Paths& paths, JoinType joinType, EndType endType); - void Execute(Paths& solution, double delta); - void Execute(PolyTree& solution, double delta); - void Clear(); - double MiterLimit; - double ArcTolerance; -private: - Paths m_destPolys; - Path m_srcPoly; - Path m_destPoly; - std::vector<DoublePoint> m_normals; - double m_delta, m_sinA, m_sin, m_cos; - double m_miterLim, m_StepsPerRad; - IntPoint m_lowest; - PolyNode m_polyNodes; - - void FixOrientations(); - void DoOffset(double delta); - void OffsetPoint(int j, int& k, JoinType jointype); - void DoSquare(int j, int k); - void DoMiter(int j, int k, double r); - void DoRound(int j, int k); -}; -//------------------------------------------------------------------------------ - -class clipperException : public std::exception -{ - public: - clipperException(const char* description): m_descr(description) {} - virtual ~clipperException() throw() {} - virtual const char* what() const throw() {return m_descr.c_str();} - private: - std::string m_descr; -}; -//------------------------------------------------------------------------------ - -} //ClipperLib namespace - -#endif //clipper_hpp - - diff --git a/thirdparty/misc/patches/clipper-exceptions.patch b/thirdparty/misc/patches/clipper-exceptions.patch deleted file mode 100644 index 537afd59b3..0000000000 --- a/thirdparty/misc/patches/clipper-exceptions.patch +++ /dev/null @@ -1,154 +0,0 @@ -diff --git a/thirdparty/misc/clipper.cpp b/thirdparty/misc/clipper.cpp -index 8c3a59c4ca..c67045d113 100644 ---- a/thirdparty/misc/clipper.cpp -+++ b/thirdparty/misc/clipper.cpp -@@ -48,6 +48,38 @@ - #include <ostream> - #include <functional> - -+//Explicitly disables exceptions handling for target platform -+//#define CLIPPER_NOEXCEPTION -+ -+#define CLIPPER_THROW(exception) std::abort() -+#define CLIPPER_TRY if(true) -+#define CLIPPER_CATCH(exception) if(false) -+ -+#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) -+ #ifndef CLIPPER_NOEXCEPTION -+ #undef CLIPPER_THROW -+ #define CLIPPER_THROW(exception) throw exception -+ #undef CLIPPER_TRY -+ #define CLIPPER_TRY try -+ #undef CLIPPER_CATCH -+ #define CLIPPER_CATCH(exception) catch(exception) -+ #endif -+#endif -+ -+//Optionally allows to override exception macros -+#if defined(CLIPPER_THROW_USER) -+ #undef CLIPPER_THROW -+ #define CLIPPER_THROW CLIPPER_THROW_USER -+#endif -+#if defined(CLIPPER_TRY_USER) -+ #undef CLIPPER_TRY -+ #define CLIPPER_TRY CLIPPER_TRY_USER -+#endif -+#if defined(CLIPPER_CATCH_USER) -+ #undef CLIPPER_CATCH -+ #define CLIPPER_CATCH CLIPPER_CATCH_USER -+#endif -+ - namespace ClipperLib { - - static double const pi = 3.141592653589793238; -@@ -898,7 +930,7 @@ void RangeTest(const IntPoint& Pt, bool& useFullRange) - if (useFullRange) - { - if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) -- throw clipperException("Coordinate outside allowed range"); -+ CLIPPER_THROW(clipperException("Coordinate outside allowed range")); - } - else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) - { -@@ -1046,10 +1078,10 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) - { - #ifdef use_lines - if (!Closed && PolyTyp == ptClip) -- throw clipperException("AddPath: Open paths must be subject."); -+ CLIPPER_THROW(clipperException("AddPath: Open paths must be subject.")); - #else - if (!Closed) -- throw clipperException("AddPath: Open paths have been disabled."); -+ CLIPPER_THROW(clipperException("AddPath: Open paths have been disabled.")); - #endif - - int highI = (int)pg.size() -1; -@@ -1062,7 +1094,7 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) - - bool IsFlat = true; - //1. Basic (first) edge initialization ... -- try -+ CLIPPER_TRY - { - edges[1].Curr = pg[1]; - RangeTest(pg[0], m_UseFullRange); -@@ -1075,10 +1107,10 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) - InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); - } - } -- catch(...) -+ CLIPPER_CATCH(...) - { - delete [] edges; -- throw; //range test fails -+ CLIPPER_THROW(); //range test fails - } - TEdge *eStart = &edges[0]; - -@@ -1442,7 +1474,7 @@ void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) - void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) - { - if (!e->NextInLML) -- throw clipperException("UpdateEdgeIntoAEL: invalid call"); -+ CLIPPER_THROW(clipperException("UpdateEdgeIntoAEL: invalid call")); - - e->NextInLML->OutIdx = e->OutIdx; - TEdge* AelPrev = e->PrevInAEL; -@@ -1510,7 +1542,7 @@ bool Clipper::Execute(ClipType clipType, Paths &solution, - { - if( m_ExecuteLocked ) return false; - if (m_HasOpenPaths) -- throw clipperException("Error: PolyTree struct is needed for open path clipping."); -+ CLIPPER_THROW(clipperException("Error: PolyTree struct is needed for open path clipping.")); - m_ExecuteLocked = true; - solution.resize(0); - m_SubjFillType = subjFillType; -@@ -1560,7 +1592,7 @@ void Clipper::FixHoleLinkage(OutRec &outrec) - bool Clipper::ExecuteInternal() - { - bool succeeded = true; -- try { -+ CLIPPER_TRY { - Reset(); - m_Maxima = MaximaList(); - m_SortedEdges = 0; -@@ -1583,7 +1615,7 @@ bool Clipper::ExecuteInternal() - InsertLocalMinimaIntoAEL(botY); - } - } -- catch(...) -+ CLIPPER_CATCH(...) - { - succeeded = false; - } -@@ -2827,18 +2859,18 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) - bool Clipper::ProcessIntersections(const cInt topY) - { - if( !m_ActiveEdges ) return true; -- try { -+ CLIPPER_TRY { - BuildIntersectList(topY); - size_t IlSize = m_IntersectList.size(); - if (IlSize == 0) return true; - if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); - else return false; - } -- catch(...) -+ CLIPPER_CATCH(...) - { - m_SortedEdges = 0; - DisposeIntersectNodes(); -- throw clipperException("ProcessIntersections error"); -+ CLIPPER_THROW(clipperException("ProcessIntersections error")); - } - m_SortedEdges = 0; - return true; -@@ -3002,7 +3034,7 @@ void Clipper::DoMaxima(TEdge *e) - DeleteFromAEL(eMaxPair); - } - #endif -- else throw clipperException("DoMaxima error"); -+ else CLIPPER_THROW(clipperException("DoMaxima error")); - } - //------------------------------------------------------------------------------ - |