diff --git a/SConstruct b/SConstruct index 99fb5c54c0f..67180bb3cdf 100644 --- a/SConstruct +++ b/SConstruct @@ -1039,6 +1039,9 @@ env.Append(BUILDERS=GLSL_BUILDERS) if env["compiledb"]: env.Tool("compilation_db") env.Alias("compiledb", env.CompilationDatabase()) + env.NoCache(env.CompilationDatabase()) + if not env["verbose"]: + env["COMPILATIONDB_COMSTR"] = "$GENCOMSTR" if env["ninja"]: if env.scons_version < (4, 2, 0): diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index fd3919bb816..2a52c840552 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -474,13 +474,30 @@ void ProjectSettings::_emit_changed() { emit_signal("settings_changed"); } -bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset) { +bool ProjectSettings::load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset) { + return ProjectSettings::_load_resource_pack(p_pack, p_replace_files, p_offset, false); +} + +bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset, bool p_main_pack) { if (PackedData::get_singleton()->is_disabled()) { return false; } - bool ok = PackedData::get_singleton()->add_pack(p_pack, p_replace_files, p_offset) == OK; + if (p_pack == "res://") { + // Loading the resource directory as a pack source is reserved for internal use only. + return false; + } + if (!p_main_pack && !using_datapack && !OS::get_singleton()->get_resource_dir().is_empty()) { + // Add the project's resource file system to PackedData so directory access keeps working when + // the game is running without a main pack, like in the editor or on Android. + PackedData::get_singleton()->add_pack_source(memnew(PackedSourceDirectory)); + PackedData::get_singleton()->add_pack("res://", false, 0); + DirAccess::make_default(DirAccess::ACCESS_RESOURCES); + using_datapack = true; + } + + bool ok = PackedData::get_singleton()->add_pack(p_pack, p_replace_files, p_offset) == OK; if (!ok) { return false; } @@ -493,9 +510,11 @@ bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_f ResourceUID::get_singleton()->load_from_cache(false); } - //if data.pck is found, all directory access will be from here - DirAccess::make_default(DirAccess::ACCESS_RESOURCES); - using_datapack = true; + // If the data pack was found, all directory access will be from here. + if (!using_datapack) { + DirAccess::make_default(DirAccess::ACCESS_RESOURCES); + using_datapack = true; + } return true; } @@ -574,7 +593,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b // Attempt with a user-defined main pack first if (!p_main_pack.is_empty()) { - bool ok = _load_resource_pack(p_main_pack); + bool ok = _load_resource_pack(p_main_pack, false, 0, true); ERR_FAIL_COND_V_MSG(!ok, ERR_CANT_OPEN, vformat("Cannot open resource pack '%s'.", p_main_pack)); Error err = _load_settings_text_or_binary("res://project.godot", "res://project.binary"); @@ -593,7 +612,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b // and if so, we attempt loading it at the end. // Attempt with PCK bundled into executable. - bool found = _load_resource_pack(exec_path); + bool found = _load_resource_pack(exec_path, false, 0, true); // Attempt with exec_name.pck. // (This is the usual case when distributing a Redot game.) @@ -609,20 +628,20 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b #ifdef MACOS_ENABLED if (!found) { // Attempt to load PCK from macOS .app bundle resources. - found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_basename + ".pck")) || _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_filename + ".pck")); + found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_basename + ".pck"), false, 0, true) || _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_filename + ".pck"), false, 0, true); } #endif if (!found) { // Try to load data pack at the location of the executable. // As mentioned above, we have two potential names to attempt. - found = _load_resource_pack(exec_dir.path_join(exec_basename + ".pck")) || _load_resource_pack(exec_dir.path_join(exec_filename + ".pck")); + found = _load_resource_pack(exec_dir.path_join(exec_basename + ".pck"), false, 0, true) || _load_resource_pack(exec_dir.path_join(exec_filename + ".pck"), false, 0, true); } if (!found) { // If we couldn't find them next to the executable, we attempt // the current working directory. Same story, two tests. - found = _load_resource_pack(exec_basename + ".pck") || _load_resource_pack(exec_filename + ".pck"); + found = _load_resource_pack(exec_basename + ".pck", false, 0, true) || _load_resource_pack(exec_filename + ".pck", false, 0, true); } // If we opened our package, try and load our project. @@ -1420,7 +1439,7 @@ void ProjectSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("localize_path", "path"), &ProjectSettings::localize_path); ClassDB::bind_method(D_METHOD("globalize_path", "path"), &ProjectSettings::globalize_path); ClassDB::bind_method(D_METHOD("save"), &ProjectSettings::save); - ClassDB::bind_method(D_METHOD("load_resource_pack", "pack", "replace_files", "offset"), &ProjectSettings::_load_resource_pack, DEFVAL(true), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("load_resource_pack", "pack", "replace_files", "offset"), &ProjectSettings::load_resource_pack, DEFVAL(true), DEFVAL(0)); ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd); diff --git a/core/config/project_settings.h b/core/config/project_settings.h index 7e25e2e1b5d..74ffdc483ce 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -137,7 +137,8 @@ class ProjectSettings : public Object { void _convert_to_last_version(int p_from_version); - bool _load_resource_pack(const String &p_pack, bool p_replace_files = true, int p_offset = 0); + bool load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset); + bool _load_resource_pack(const String &p_pack, bool p_replace_files = true, int p_offset = 0, bool p_main_pack = false); void _add_property_info_bind(const Dictionary &p_info); diff --git a/core/input/input.cpp b/core/input/input.cpp index 35afe559c6f..6c5682b4b79 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -1604,9 +1604,6 @@ void Input::parse_mapping(const String &p_mapping) { return; } - CharString uid; - uid.resize(17); - mapping.uid = entry[0]; mapping.name = entry[1]; @@ -1714,15 +1711,72 @@ void Input::add_joy_mapping(const String &p_mapping, bool p_update_existing) { } void Input::remove_joy_mapping(const String &p_guid) { + // One GUID can exist multiple times in `map_db`, and + // `add_joy_mapping` can choose not to update the existing mapping, + // so the indices can be all over the place. Therefore we need to remember them. + Vector removed_idx; + int min_removed_idx = -1; + int max_removed_idx = -1; + int fallback_mapping_offset = 0; + for (int i = map_db.size() - 1; i >= 0; i--) { if (p_guid == map_db[i].uid) { map_db.remove_at(i); + + if (max_removed_idx == -1) { + max_removed_idx = i; + } + min_removed_idx = i; + removed_idx.push_back(i); + + if (i < fallback_mapping) { + fallback_mapping_offset++; + } else if (i == fallback_mapping) { + fallback_mapping = -1; + WARN_PRINT_ONCE(vformat("Removed fallback joypad input mapping \"%s\". This could lead to joypads not working as intended.", p_guid)); + } } } + + if (min_removed_idx == -1) { + return; // Nothing removed. + } + + if (fallback_mapping > 0) { + // Fix the shifted index. + fallback_mapping -= fallback_mapping_offset; + } + + int removed_idx_size = removed_idx.size(); + + // Update joypad mapping references: some + // * should use the fallback_mapping (if set; if not, they get unmapped), or + // * need their mapping reference fixed, because the deletion(s) offset them. for (KeyValue &E : joy_names) { Joypad &joy = E.value; - if (joy.uid == p_guid) { - _set_joypad_mapping(joy, -1); + if (joy.mapping < min_removed_idx) { + continue; // Not affected. + } + + if (joy.mapping > max_removed_idx) { + _set_joypad_mapping(joy, joy.mapping - removed_idx_size); + continue; // Simple offset fix. + } + + // removed_idx is in reverse order (ie. high to low), because the first loop is in reverse order. + for (int i = 0; i < removed_idx.size(); i++) { + if (removed_idx[i] == joy.mapping) { + // Set to fallback_mapping, if defined, else unmap the joypad. + // Currently, the fallback_mapping is only set internally, and only for Android. + _set_joypad_mapping(joy, fallback_mapping); + break; + } + if (removed_idx[i] < joy.mapping) { + // Complex offset fix: + // This mapping was shifted by `(removed_idx_size - i)` deletions. + _set_joypad_mapping(joy, joy.mapping - (removed_idx_size - i)); + break; + } } } } diff --git a/core/input/input.h b/core/input/input.h index a4c86e0d03c..fc8cea21193 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -186,7 +186,7 @@ class Input : public Object { HashSet ignored_device_ids; - int fallback_mapping = -1; + int fallback_mapping = -1; // Index of the guid in map_db. CursorShape default_shape = CURSOR_ARROW; diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 6d5a9efaedd..5b7a7021cd6 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -333,6 +333,44 @@ Ref PackedSourcePCK::get_file(const String &p_path, PackedData::Pack ////////////////////////////////////////////////////////////////// +bool PackedSourceDirectory::try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) { + // Load with offset feature only supported for PCK files. + ERR_FAIL_COND_V_MSG(p_offset != 0, false, "Invalid PCK data. Note that loading files with a non-zero offset isn't supported with directories."); + + if (p_path != "res://") { + return false; + } + add_directory(p_path, p_replace_files); + return true; +} + +Ref PackedSourceDirectory::get_file(const String &p_path, PackedData::PackedFile *p_file) { + Ref ret = FileAccess::create_for_path(p_path); + ret->reopen(p_path, FileAccess::READ); + return ret; +} + +void PackedSourceDirectory::add_directory(const String &p_path, bool p_replace_files) { + Ref da = DirAccess::open(p_path); + if (da.is_null()) { + return; + } + da->set_include_hidden(true); + + for (const String &file_name : da->get_files()) { + String file_path = p_path.path_join(file_name); + uint8_t md5[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + PackedData::get_singleton()->add_path(p_path, file_path, 0, 0, md5, this, p_replace_files, false); + } + + for (const String &sub_dir_name : da->get_directories()) { + String sub_dir_path = p_path.path_join(sub_dir_name); + add_directory(sub_dir_path, p_replace_files); + } +} + +////////////////////////////////////////////////////////////////// + Error FileAccessPack::open_internal(const String &p_path, int p_mode_flags) { ERR_PRINT("Can't open pack-referenced file."); return ERR_UNAVAILABLE; diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index 776e46ea2f3..b74839da0f6 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -149,6 +149,14 @@ class PackedSourcePCK : public PackSource { virtual Ref get_file(const String &p_path, PackedData::PackedFile *p_file) override; }; +class PackedSourceDirectory : public PackSource { + void add_directory(const String &p_path, bool p_replace_files); + +public: + virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) override; + virtual Ref get_file(const String &p_path, PackedData::PackedFile *p_file) override; +}; + class FileAccessPack : public FileAccess { PackedData::PackedFile pf; diff --git a/core/io/image.cpp b/core/io/image.cpp index 1624f19581f..1b8c2cf09f0 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -3995,11 +3995,11 @@ uint32_t Image::get_format_component_mask(Format p_format) { case FORMAT_RGBAH: return rgba; case FORMAT_RGBE9995: - return rgba; + return rgb; case FORMAT_DXT1: return rgb; case FORMAT_DXT3: - return rgb; + return rgba; case FORMAT_DXT5: return rgba; case FORMAT_RGTC_R: @@ -4029,9 +4029,9 @@ uint32_t Image::get_format_component_mask(Format p_format) { case FORMAT_ETC2_RGB8A1: return rgba; case FORMAT_ETC2_RA_AS_RG: - return rgba; + return rg; case FORMAT_DXT5_RA_AS_RG: - return rgba; + return rg; case FORMAT_ASTC_4x4: return rgba; case FORMAT_ASTC_4x4_HDR: diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index c589de07b47..ef2802f05ae 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -322,14 +322,34 @@ Ref ResourceLoader::_load(const String &p_path, const String &p_origin print_verbose(vformat("Failed loading resource: %s", p_path)); } +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + if (ResourceFormatImporter::get_singleton()->get_importer_by_extension(p_path.get_extension()).is_valid()) { + // The format is known to the editor, but the file hasn't been imported + // (otherwise, ResourceFormatImporter would have been found as a suitable loader). + found = true; + if (r_error) { + *r_error = ERR_FILE_NOT_FOUND; + } + } + } +#endif ERR_FAIL_COND_V_MSG(found, Ref(), vformat("Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once.", p_path)); #ifdef TOOLS_ENABLED Ref file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES); - ERR_FAIL_COND_V_MSG(!file_check->file_exists(p_path), Ref(), vformat("Resource file not found: %s (expected type: %s)", p_path, p_type_hint)); + if (!file_check->file_exists(p_path)) { + if (r_error) { + *r_error = ERR_FILE_NOT_FOUND; + } + ERR_FAIL_V_MSG(Ref(), vformat("Resource file not found: %s (expected type: %s)", p_path, p_type_hint)); + } #endif + if (r_error) { + *r_error = ERR_FILE_UNRECOGNIZED; + } ERR_FAIL_V_MSG(Ref(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint)); } diff --git a/core/object/method_bind.h b/core/object/method_bind.h index 875877be6ae..906f823bf8b 100644 --- a/core/object/method_bind.h +++ b/core/object/method_bind.h @@ -92,7 +92,7 @@ class MethodBind { } _FORCE_INLINE_ Variant::Type get_argument_type(int p_argument) const { - ERR_FAIL_COND_V(p_argument < -1 || p_argument > argument_count, Variant::NIL); + ERR_FAIL_COND_V(p_argument < -1 || p_argument >= argument_count, Variant::NIL); return argument_types[p_argument + 1]; } diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index d3aa80ba3b3..7ef26c390d1 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -271,12 +271,10 @@ StringName::StringName(const char *p_name, bool p_static) { return; //empty, ignore } - MutexLock lock(mutex); - - uint32_t hash = String::hash(p_name); - - uint32_t idx = hash & STRING_TABLE_MASK; + const uint32_t hash = String::hash(p_name); + const uint32_t idx = hash & STRING_TABLE_MASK; + MutexLock lock(mutex); _data = _table[idx]; while (_data) { @@ -330,12 +328,10 @@ StringName::StringName(const StaticCString &p_static_string, bool p_static) { ERR_FAIL_COND(!p_static_string.ptr || !p_static_string.ptr[0]); - MutexLock lock(mutex); - - uint32_t hash = String::hash(p_static_string.ptr); - - uint32_t idx = hash & STRING_TABLE_MASK; + const uint32_t hash = String::hash(p_static_string.ptr); + const uint32_t idx = hash & STRING_TABLE_MASK; + MutexLock lock(mutex); _data = _table[idx]; while (_data) { @@ -390,11 +386,10 @@ StringName::StringName(const String &p_name, bool p_static) { return; } - MutexLock lock(mutex); - - uint32_t hash = p_name.hash(); - uint32_t idx = hash & STRING_TABLE_MASK; + const uint32_t hash = p_name.hash(); + const uint32_t idx = hash & STRING_TABLE_MASK; + MutexLock lock(mutex); _data = _table[idx]; while (_data) { @@ -448,11 +443,10 @@ StringName StringName::search(const char *p_name) { return StringName(); } - MutexLock lock(mutex); - - uint32_t hash = String::hash(p_name); - uint32_t idx = hash & STRING_TABLE_MASK; + const uint32_t hash = String::hash(p_name); + const uint32_t idx = hash & STRING_TABLE_MASK; + MutexLock lock(mutex); _Data *_data = _table[idx]; while (_data) { @@ -484,12 +478,10 @@ StringName StringName::search(const char32_t *p_name) { return StringName(); } - MutexLock lock(mutex); - - uint32_t hash = String::hash(p_name); - - uint32_t idx = hash & STRING_TABLE_MASK; + const uint32_t hash = String::hash(p_name); + const uint32_t idx = hash & STRING_TABLE_MASK; + MutexLock lock(mutex); _Data *_data = _table[idx]; while (_data) { @@ -510,12 +502,10 @@ StringName StringName::search(const char32_t *p_name) { StringName StringName::search(const String &p_name) { ERR_FAIL_COND_V(p_name.is_empty(), StringName()); - MutexLock lock(mutex); - - uint32_t hash = p_name.hash(); - - uint32_t idx = hash & STRING_TABLE_MASK; + const uint32_t hash = p_name.hash(); + const uint32_t idx = hash & STRING_TABLE_MASK; + MutexLock lock(mutex); _Data *_data = _table[idx]; while (_data) { diff --git a/core/templates/a_hash_map.h b/core/templates/a_hash_map.h index cc8d63a14d6..69d440d7034 100644 --- a/core/templates/a_hash_map.h +++ b/core/templates/a_hash_map.h @@ -37,12 +37,12 @@ struct HashMapData { union { + uint64_t data; struct { uint32_t hash; uint32_t hash_to_key; }; - uint64_t data; }; }; diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 2a70f7041ec..a71b55c2b69 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -214,6 +214,7 @@ Returns the unobscured area of the display where interactive controls should be rendered. See also [method get_display_cutouts]. + [b]Note:[/b] Currently only implemented on Android and iOS. On other platforms, [code]screen_get_usable_rect(SCREEN_OF_MAIN_WINDOW)[/code] will be returned as a fallback. See also [method screen_get_usable_rect]. diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index 97e6521af54..982e9c05c01 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -311,7 +311,8 @@ - Removes all mappings from the internal database that match the given GUID. + Removes all mappings from the internal database that match the given GUID. All currently connected joypads that use this GUID will become unmapped. + On Android, Redot will map to an internal fallback mapping. diff --git a/doc/classes/InputEventWithModifiers.xml b/doc/classes/InputEventWithModifiers.xml index 5c018b0c56d..1434f341157 100644 --- a/doc/classes/InputEventWithModifiers.xml +++ b/doc/classes/InputEventWithModifiers.xml @@ -5,6 +5,7 @@ Stores information about mouse, keyboard, and touch gesture input events. This includes information about which modifier keys are pressed, such as [kbd]Shift[/kbd] or [kbd]Alt[/kbd]. See [method Node._input]. + [b]Note:[/b] Modifier keys are considered modifiers only when used in combination with another key. As a result, their corresponding member variables, such as [member ctrl_pressed], will return [code]false[/code] if the key is pressed on its own. $DOCS_URL/tutorials/inputs/inputevent.html diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml index 8336899e869..7118df8ed85 100644 --- a/doc/classes/ParticleProcessMaterial.xml +++ b/doc/classes/ParticleProcessMaterial.xml @@ -418,6 +418,7 @@ Emitted when this material's emission shape is changed in any way. This includes changes to [member emission_shape], [member emission_shape_scale], or [member emission_sphere_radius], and any other property that affects the emission shape's offset, size, scale, or orientation. + [b]Note:[/b] This signal is only emitted inside the editor for performance reasons. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 4817fe0a7a4..a02327e3446 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -151,6 +151,7 @@ Loads the contents of the .pck or .zip file specified by [param pack] into the resource filesystem ([code]res://[/code]). Returns [code]true[/code] on success. [b]Note:[/b] If a file from [param pack] shares the same path as a file already in the resource filesystem, any attempts to load that file will use the file from [param pack] unless [param replace_files] is set to [code]false[/code]. [b]Note:[/b] The optional [param offset] parameter can be used to specify the offset in bytes to the start of the resource pack. This is only supported for .pck files. + [b]Note:[/b] [DirAccess] will not show changes made to the contents of [code]res://[/code] after calling this function. @@ -2237,8 +2238,10 @@ Sets which physics engine to use for 2D physics. - "DEFAULT" and "GodotPhysics2D" are the same, as there is currently no alternative 2D physics server implemented. - "Dummy" is a 2D physics server that does nothing and returns only dummy values, effectively disabling all 2D physics functionality. + [b]DEFAULT[/b] is currently equivalent to [b]GodotPhysics2D[/b], but may change in future releases. Select an explicit implementation if you want to ensure that your project stays on the same engine. + [b]GodotPhysics2D[/b] is Redot's internal 2D physics engine. + [b]Dummy[/b] is a 2D physics server that does nothing and returns only dummy values, effectively disabling all 2D physics functionality. + Third-party extensions and modules can add other physics engines to select with this setting. If [code]true[/code], the 2D physics server runs on a separate thread, making better use of multi-core CPUs. If [code]false[/code], the 2D physics server runs on the main thread. Running the physics server on a separate thread can increase performance, but restricts API access to only physics process. @@ -2316,8 +2319,11 @@ Sets which physics engine to use for 3D physics. - "DEFAULT" and "GodotPhysics3D" are the same, as there is currently no alternative 3D physics server implemented. - "Dummy" is a 3D physics server that does nothing and returns only dummy values, effectively disabling all 3D physics functionality. + [b]DEFAULT[/b] is currently equivalent to [b]GodotPhysics3D[/b], but may change in future releases. Select an explicit implementation if you want to ensure that your project stays on the same engine. + [b]GodotPhysics3D[/b] is Redot's internal 3D physics engine. + [b]Jolt Physics[/b] is an alternative physics engine that is generally faster and more reliable than [b]GodotPhysics3D[/b]. As it was recently implemented, it is currently considered experimental and its behavior may change in future releases. + [b]Dummy[/b] is a 3D physics server that does nothing and returns only dummy values, effectively disabling all 3D physics functionality. + Third-party extensions and modules can add other physics engines to select with this setting. If [code]true[/code], the 3D physics server runs on a separate thread, making better use of multi-core CPUs. If [code]false[/code], the 3D physics server runs on the main thread. Running the physics server on a separate thread can increase performance, but restricts API access to only physics process. diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 8dd90799bab..78f84389192 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -4269,7 +4269,7 @@ Creates a new 3D visibility notifier object and adds it to the RenderingServer. It can be accessed with the RID that is returned. This RID will be used in all [code]visibility_notifier_*[/code] RenderingServer functions. Once finished with your RID, you will want to free the RID using the RenderingServer's [method free_rid] method. - To place in a scene, attach this mesh to an instance using [method instance_set_base] using the returned RID. + To place in a scene, attach this notifier to an instance using [method instance_set_base] using the returned RID. [b]Note:[/b] The equivalent node is [VisibleOnScreenNotifier3D]. diff --git a/doc/classes/ResourceImporterLayeredTexture.xml b/doc/classes/ResourceImporterLayeredTexture.xml index e1e8ee710d0..f69c44e29bd 100644 --- a/doc/classes/ResourceImporterLayeredTexture.xml +++ b/doc/classes/ResourceImporterLayeredTexture.xml @@ -14,7 +14,7 @@ Controls how color channels should be used in the imported texture. [b]sRGB Friendly:[/b], prevents the RG color format from being used, as it does not support sRGB color. [b]Optimized:[/b], allows the RG color format to be used if the texture does not use the blue channel. This reduces memory usage if the texture's blue channel can be discarded (all pixels must have a blue value of [code]0[/code]). - [b]Normal Map (RG Channels):[/b] This forces all layers from the texture to be imported with the RG color format to reduce memory usage, with only the red and green channels preserved. This only has an effect on textures with the VRAM Compressed or Basis Universal compression modes. This mode is only available in layered textures ([Cubemap], [CubemapArray], [Texture2DArray] and [Texture3D]). + [b]Normal Map (RG Channels):[/b] This forces all layers from the texture to be imported with the RG color format, with only the red and green channels preserved. RGTC (Red-Green Texture Compression) compression is able to preserve its detail much better, while using the same amount of memory as a standard RGBA VRAM-compressed texture. This only has an effect on textures with the VRAM Compressed or Basis Universal compression modes. This mode is only available in layered textures ([Cubemap], [CubemapArray], [Texture2DArray] and [Texture3D]). Controls how VRAM compression should be performed for HDR images. diff --git a/doc/classes/SpringBoneCollision3D.xml b/doc/classes/SpringBoneCollision3D.xml index ea4556674cf..85738b66971 100644 --- a/doc/classes/SpringBoneCollision3D.xml +++ b/doc/classes/SpringBoneCollision3D.xml @@ -7,6 +7,7 @@ A collision can be a child of [SpringBoneSimulator3D]. If it is not a child of [SpringBoneSimulator3D], it has no effect. The colliding and sliding are done in the [SpringBoneSimulator3D]'s modification process in order of its collision list which is set by [method SpringBoneSimulator3D.set_collision_path]. If [method SpringBoneSimulator3D.are_all_child_collisions_enabled] is [code]true[/code], the order matches [SceneTree]. If [member bone] is set, it synchronizes with the bone pose of the ancestor [Skeleton3D], which is done in before the [SpringBoneSimulator3D]'s modification process as the pre-process. + [b]Warning:[/b] A scaled [SpringBoneCollision3D] will likely not behave as expected. Make sure that the parent [Skeleton3D] and its bones are not scaled. diff --git a/doc/classes/SpringBoneSimulator3D.xml b/doc/classes/SpringBoneSimulator3D.xml index 95c357f60bb..6012121685d 100644 --- a/doc/classes/SpringBoneSimulator3D.xml +++ b/doc/classes/SpringBoneSimulator3D.xml @@ -10,6 +10,7 @@ Several properties can be applied to each joint, such as [method set_joint_stiffness], [method set_joint_drag], and [method set_joint_gravity]. For simplicity, you can set values to all joints at the same time by using a [Curve]. If you want to specify detailed values individually, set [method set_individual_config] to [code]true[/code]. For physical simulation, [SpringBoneSimulator3D] can have children as self-standing collisions that are not related to [PhysicsServer3D], see also [SpringBoneCollision3D]. + [b]Warning:[/b] A scaled [SpringBoneSimulator3D] will likely not behave as expected. Make sure that the parent [Skeleton3D] and its bones are not scaled. @@ -446,7 +447,7 @@ - Sets the gravity amount of the bone chain. + Sets the gravity amount of the bone chain. This value is not an acceleration, but a constant velocity of movement in [method set_gravity_direction]. If [param gravity] is not [code]0[/code], the modified pose will not return to the original pose since it is always affected by gravity. The value is scaled by [method set_gravity_damping_curve] and cached in each joint setting in the joint list. @@ -464,7 +465,7 @@ - Sets the gravity direction of the bone chain. + Sets the gravity direction of the bone chain. This value is internally normalized and then multiplied by [method set_gravity]. The value is cached in each joint setting in the joint list. @@ -570,6 +571,7 @@ Sets the rotation axis of the bone chain. If sets a specific axis, it acts like a hinge joint. The value is cached in each joint setting in the joint list. + [b]Note:[/b] The rotation axis and the forward vector shouldn't be colinear to avoid unintended rotation since [SpringBoneSimulator3D] does not factor in twisting forces. diff --git a/doc/classes/WorldBoundaryShape3D.xml b/doc/classes/WorldBoundaryShape3D.xml index 33609916a36..0f6224c40c8 100644 --- a/doc/classes/WorldBoundaryShape3D.xml +++ b/doc/classes/WorldBoundaryShape3D.xml @@ -5,6 +5,7 @@ A 3D world boundary shape, intended for use in physics. [WorldBoundaryShape3D] works like an infinite plane that forces all physics bodies to stay above it. The [member plane]'s normal determines which direction is considered as "above" and in the editor, the line over the plane represents this direction. It can for example be used for endless flat floors. + [b]Note:[/b] When the physics engine is set to [b]Jolt Physics[/b] in the project settings ([member ProjectSettings.physics/3d/physics_engine]), [WorldBoundaryShape3D] has a finite size (centered at the world origin). It can be adjusted by changing [member ProjectSettings.physics/jolt_physics_3d/limits/world_boundary_shape_size]. diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 43c006f64cf..bd34fe8acaa 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -252,6 +252,10 @@ class RasterizerSceneGLES3 : public RendererSceneRender { }; union { + struct { + uint64_t sort_key1; + uint64_t sort_key2; + }; struct { uint64_t lod_index : 8; uint64_t surface_index : 8; @@ -267,10 +271,6 @@ class RasterizerSceneGLES3 : public RendererSceneRender { uint64_t depth_layer : 4; uint64_t priority : 8; }; - struct { - uint64_t sort_key1; - uint64_t sort_key2; - }; } sort; RS::PrimitiveType primitive = RS::PRIMITIVE_MAX; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 109b86bd4b5..315db7fccee 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -6576,7 +6576,7 @@ Vector> EditorNode::find_resource_conversion Vector> EditorNode::find_resource_conversion_plugin_for_type_name(const String &p_type) { Vector> ret; - if (ClassDB::can_instantiate(p_type)) { + if (ClassDB::class_exists(p_type) && ClassDB::can_instantiate(p_type)) { Ref temp = Object::cast_to(ClassDB::instantiate(p_type)); if (temp.is_valid()) { for (Ref resource_conversion_plugin : resource_conversion_plugins) { diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index 6634f46d720..7ed4f270773 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -883,6 +883,7 @@ void EditorPropertyArray::_reorder_button_up() { array.call("remove_at", reorder_slot.index); array.call("insert", reorder_to_index, value_to_move); + slots[reorder_to_index % page_length].reorder_button->grab_focus(); emit_changed(get_edited_property(), array); } diff --git a/editor/icons/SCsub b/editor/icons/SCsub index a66ef56699c..23ac6e69581 100644 --- a/editor/icons/SCsub +++ b/editor/icons/SCsub @@ -7,20 +7,18 @@ import os import editor_icons_builders -env["BUILDERS"]["MakeEditorIconsBuilder"] = Builder( - action=env.Run(editor_icons_builders.make_editor_icons_action), - suffix=".h", - src_suffix=".svg", -) - # Editor's own icons icon_sources = Glob("*.svg") # Module icons for path in env.module_icons_paths: if not os.path.isabs(path): - icon_sources += Glob("#" + path + "/*.svg") # Built-in. + icon_sources += Glob(f"#{path}/*.svg") # Built-in. else: - icon_sources += Glob(path + "/*.svg") # Custom. + icon_sources += Glob(f"{path}/*.svg") # Custom. -env.Alias("editor_icons", [env.MakeEditorIconsBuilder("#editor/themes/editor_icons.gen.h", icon_sources)]) +env.CommandNoCache( + "#editor/themes/editor_icons.gen.h", + icon_sources, + env.Run(editor_icons_builders.make_editor_icons_action), +) diff --git a/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp b/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp index 391717e82c1..511a6dafbca 100644 --- a/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp @@ -87,8 +87,8 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (Object::cast_to(p_gizmo->get_node_3d())) { const GPUParticles3D *particles = Object::cast_to(p_gizmo->get_node_3d()); - if (particles->get_process_material().is_valid()) { - const Ref mat = particles->get_process_material(); + const Ref mat = particles->get_process_material(); + if (mat.is_valid()) { const ParticleProcessMaterial::EmissionShape shape = mat->get_emission_shape(); const Ref material = get_material("particles_emission_shape_material", p_gizmo); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index c5e653c5b69..0358b3760c7 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1432,8 +1432,8 @@ ProjectManager::ProjectManager(bool p_custom_res) { empty_list_message->set_text(vformat("[center][b]%s[/b] %s[/center]", line1, line2)); empty_list_placeholder->add_child(empty_list_message); - HBoxContainer *empty_list_actions = memnew(HBoxContainer); - empty_list_actions->set_alignment(BoxContainer::ALIGNMENT_CENTER); + FlowContainer *empty_list_actions = memnew(FlowContainer); + empty_list_actions->set_alignment(FlowContainer::ALIGNMENT_CENTER); empty_list_placeholder->add_child(empty_list_actions); empty_list_create_project = memnew(Button); diff --git a/gles3_builders.py b/gles3_builders.py index a81d42b42e2..85b2c8e4c43 100644 --- a/gles3_builders.py +++ b/gles3_builders.py @@ -585,5 +585,6 @@ def build_gles3_header( def build_gles3_headers(target, source, env): + env.NoCache(target) for x in source: build_gles3_header(str(x), include="drivers/gles3/shader_gles3.h", class_suffix="GLES3") diff --git a/glsl_builders.py b/glsl_builders.py index 82c15fc93be..98a274dd273 100644 --- a/glsl_builders.py +++ b/glsl_builders.py @@ -148,6 +148,7 @@ class {out_file_class} : public ShaderRD {{ def build_rd_headers(target, source, env): + env.NoCache(target) for x in source: build_rd_header(filename=str(x)) @@ -205,5 +206,6 @@ def build_raw_header( def build_raw_headers(target, source, env): + env.NoCache(target) for x in source: build_raw_header(filename=str(x)) diff --git a/main/main.cpp b/main/main.cpp index 1ccf2d9a4af..d0c6c6e6399 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -166,10 +166,8 @@ static DisplayServer *display_server = nullptr; static RenderingServer *rendering_server = nullptr; static TextServerManager *tsman = nullptr; static ThemeDB *theme_db = nullptr; -static NavigationServer2D *navigation_server_2d = nullptr; static PhysicsServer2DManager *physics_server_2d_manager = nullptr; static PhysicsServer2D *physics_server_2d = nullptr; -static NavigationServer3D *navigation_server_3d = nullptr; #ifndef _3D_DISABLED static PhysicsServer3DManager *physics_server_3d_manager = nullptr; static PhysicsServer3D *physics_server_3d = nullptr; @@ -380,44 +378,6 @@ void finalize_display() { memdelete(display_server); } -void initialize_navigation_server() { - ERR_FAIL_COND(navigation_server_3d != nullptr); - ERR_FAIL_COND(navigation_server_2d != nullptr); - - // Init 3D Navigation Server - navigation_server_3d = NavigationServer3DManager::new_default_server(); - - // Fall back to dummy if no default server has been registered. - if (!navigation_server_3d) { - navigation_server_3d = memnew(NavigationServer3DDummy); - } - - // Should be impossible, but make sure it's not null. - ERR_FAIL_NULL_MSG(navigation_server_3d, "Failed to initialize NavigationServer3D."); - navigation_server_3d->init(); - - // Init 2D Navigation Server - navigation_server_2d = NavigationServer2DManager::new_default_server(); - if (!navigation_server_2d) { - navigation_server_2d = memnew(NavigationServer2DDummy); - } - - ERR_FAIL_NULL_MSG(navigation_server_2d, "Failed to initialize NavigationServer2D."); - navigation_server_2d->init(); -} - -void finalize_navigation_server() { - ERR_FAIL_NULL(navigation_server_3d); - navigation_server_3d->finish(); - memdelete(navigation_server_3d); - navigation_server_3d = nullptr; - - ERR_FAIL_NULL(navigation_server_2d); - navigation_server_2d->finish(); - memdelete(navigation_server_2d); - navigation_server_2d = nullptr; -} - void initialize_theme_db() { theme_db = memnew(ThemeDB); } @@ -774,6 +734,9 @@ Error Main::test_setup() { // Default theme will be initialized later, after modules and ScriptServer are ready. initialize_theme_db(); + NavigationServer3DManager::initialize_server(); // 3D server first because 2D depends on it. + NavigationServer2DManager::initialize_server(); + register_scene_types(); register_driver_types(); @@ -796,8 +759,6 @@ Error Main::test_setup() { // Theme needs modules to be initialized so that sub-resources can be loaded. theme_db->initialize_theme_noproject(); - initialize_navigation_server(); - ERR_FAIL_COND_V(TextServerManager::get_singleton()->get_interface_count() == 0, ERR_CANT_CREATE); /* Use one with the most features available. */ @@ -858,7 +819,8 @@ void Main::test_cleanup() { finalize_theme_db(); - finalize_navigation_server(); + NavigationServer2DManager::finalize_server(); // 2D goes first as it uses the 3D server behind the scene. + NavigationServer3DManager::finalize_server(); GDExtensionManager::get_singleton()->deinitialize_extensions(GDExtension::INITIALIZATION_LEVEL_SERVERS); uninitialize_modules(MODULE_INITIALIZATION_LEVEL_SERVERS); @@ -2553,12 +2515,16 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->set_environment("DISABLE_RTSS_LAYER", "1"); // GH-57937. OS::get_singleton()->set_environment("DISABLE_VKBASALT", "1"); OS::get_singleton()->set_environment("DISABLE_VK_LAYER_reshade_1", "1"); // GH-70849. + OS::get_singleton()->set_environment("VK_LAYER_bandicam_helper_DEBUG_1", "1"); // GH-101480. + OS::get_singleton()->set_environment("DISABLE_VK_LAYER_bandicam_helper_1", "1"); // GH-101480. } else { // Re-allow using Vulkan overlays, disabled while using the editor. OS::get_singleton()->unset_environment("DISABLE_MANGOHUD"); OS::get_singleton()->unset_environment("DISABLE_RTSS_LAYER"); OS::get_singleton()->unset_environment("DISABLE_VKBASALT"); OS::get_singleton()->unset_environment("DISABLE_VK_LAYER_reshade_1"); + OS::get_singleton()->unset_environment("VK_LAYER_bandicam_helper_DEBUG_1"); + OS::get_singleton()->unset_environment("DISABLE_VK_LAYER_bandicam_helper_1"); } #endif @@ -3400,6 +3366,11 @@ Error Main::setup2(bool p_show_boot_logo) { // Default theme will be initialized later, after modules and ScriptServer are ready. initialize_theme_db(); + MAIN_PRINT("Main: Load Navigation"); + + NavigationServer3DManager::initialize_server(); // 3D server first because 2D depends on it. + NavigationServer2DManager::initialize_server(); + register_scene_types(); register_driver_types(); @@ -3470,10 +3441,6 @@ Error Main::setup2(bool p_show_boot_logo) { initialize_physics(); - MAIN_PRINT("Main: Load Navigation"); - - initialize_navigation_server(); - register_server_singletons(); // This loads global classes, so it must happen before custom loaders and savers are registered @@ -4714,7 +4681,8 @@ void Main::cleanup(bool p_force) { finalize_theme_db(); // Before deinitializing server extensions, finalize servers which may be loaded as extensions. - finalize_navigation_server(); + NavigationServer2DManager::finalize_server(); // 2D goes first as it uses the 3D server behind the scene. + NavigationServer3DManager::finalize_server(); finalize_physics(); GDExtensionManager::get_singleton()->deinitialize_extensions(GDExtension::INITIALIZATION_LEVEL_SERVERS); diff --git a/methods.py b/methods.py index 77c49177886..e887e97becb 100644 --- a/methods.py +++ b/methods.py @@ -828,64 +828,40 @@ def get_size(start_path: str = ".") -> int: return total_size -def clean_cache(cache_path: str, cache_limit: int, verbose: bool): +def clean_cache(cache_path: str, cache_limit: int, verbose: bool) -> None: + if not cache_limit: + return + files = glob.glob(os.path.join(cache_path, "*", "*")) if not files: return - # Remove all text files, store binary files in list of (filename, size, atime). - purge = [] - texts = [] + # Store files in list of (filename, size, atime). stats = [] for file in files: try: - # Save file stats to rewrite after modifying. - tmp_stat = os.stat(file) - # Failing a utf-8 decode is the easiest way to determine if a file is binary. - try: - with open(file, encoding="utf-8") as out: - out.read(1024) - except UnicodeDecodeError: - stats.append((file, *tmp_stat[6:8])) - # Restore file stats after reading. - os.utime(file, (tmp_stat[7], tmp_stat[8])) - else: - texts.append(file) + stats.append((file, *os.stat(file)[6:8])) except OSError: print_error(f'Failed to access cache file "{file}"; skipping.') - if texts: - count = len(texts) - for file in texts: - try: - os.remove(file) - except OSError: - print_error(f'Failed to remove cache file "{file}"; skipping.') - count -= 1 - if verbose: - print("Purging %d text %s from cache..." % (count, "files" if count > 1 else "file")) - - if cache_limit: - # Sort by most recent access (most sensible to keep) first. Search for the first entry where - # the cache limit is reached. - stats.sort(key=lambda x: x[2], reverse=True) - sum = 0 - for index, stat in enumerate(stats): - sum += stat[1] - if sum > cache_limit: - purge.extend([x[0] for x in stats[index:]]) - break - - if purge: - count = len(purge) - for file in purge: - try: - os.remove(file) - except OSError: - print_error(f'Failed to remove cache file "{file}"; skipping.') - count -= 1 - if verbose: - print("Purging %d %s from cache..." % (count, "files" if count > 1 else "file")) + # Sort by most recent access (most sensible to keep) first. Search for the first entry where + # the cache limit is reached. + stats.sort(key=lambda x: x[2], reverse=True) + sum = 0 + for index, stat in enumerate(stats): + sum += stat[1] + if sum > cache_limit: + purge = [x[0] for x in stats[index:]] + count = len(purge) + for file in purge: + try: + os.remove(file) + except OSError: + print_error(f'Failed to remove cache file "{file}"; skipping.') + count -= 1 + if verbose and count: + print_info(f"Purged {count} file{'s' if count else ''} from cache.") + break def prepare_cache(env) -> None: diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index ccbf3390aa6..101cefa1e09 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -36,9 +36,45 @@ #include "core/io/json.h" #endif // DEV_ENABLED #include "core/math/geometry_2d.h" +#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h" +#include "scene/resources/navigation_mesh.h" +#include "servers/navigation_server_3d.h" #include +Callable CSGShape3D::_navmesh_source_geometry_parsing_callback; +RID CSGShape3D::_navmesh_source_geometry_parser; + +void CSGShape3D::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer3D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&CSGShape3D::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create(); + NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void CSGShape3D::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + CSGShape3D *csgshape3d = Object::cast_to(p_node); + + if (csgshape3d == nullptr) { + return; + } + + NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask(); + + if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS && csgshape3d->is_using_collision() && (csgshape3d->get_collision_layer() & parsed_collision_mask)) || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) { + Array meshes = csgshape3d->get_meshes(); + if (!meshes.is_empty()) { + Ref mesh = meshes[1]; + if (mesh.is_valid()) { + p_source_geometry_data->add_mesh(mesh, csgshape3d->get_global_transform()); + } + } + } +} + void CSGShape3D::set_use_collision(bool p_enable) { if (use_collision == p_enable) { return; diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index e97fdb491a2..753edba3fc9 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -41,6 +41,9 @@ #include "thirdparty/misc/mikktspace.h" +class NavigationMesh; +class NavigationMeshSourceGeometryData3D; + class CSGShape3D : public GeometryInstance3D { GDCLASS(CSGShape3D, GeometryInstance3D); @@ -173,6 +176,14 @@ class CSGShape3D : public GeometryInstance3D { virtual Ref generate_triangle_mesh() const override; +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + CSGShape3D(); ~CSGShape3D(); }; diff --git a/modules/csg/register_types.cpp b/modules/csg/register_types.cpp index 4ee6a0ecb7d..744d9bec5a5 100644 --- a/modules/csg/register_types.cpp +++ b/modules/csg/register_types.cpp @@ -49,6 +49,7 @@ void initialize_csg_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(CSGTorus3D); GDREGISTER_CLASS(CSGPolygon3D); GDREGISTER_CLASS(CSGCombiner3D); + CSGShape3D::navmesh_parse_init(); } #ifdef TOOLS_ENABLED if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { diff --git a/modules/gdscript/editor/script_templates/SCsub b/modules/gdscript/editor/script_templates/SCsub index 28a27db3fae..d4e83e95ded 100644 --- a/modules/gdscript/editor/script_templates/SCsub +++ b/modules/gdscript/editor/script_templates/SCsub @@ -5,13 +5,4 @@ Import("env") import editor.template_builders as build_template_gd -env["BUILDERS"]["MakeGDTemplateBuilder"] = Builder( - action=env.Run(build_template_gd.make_templates), - suffix=".h", - src_suffix=".gd", -) - -# Template files -templates_sources = Glob("*/*.gd") - -env.Alias("editor_template_gd", [env.MakeGDTemplateBuilder("templates.gen.h", templates_sources)]) +env.CommandNoCache("templates.gen.h", Glob("*/*.gd"), env.Run(build_template_gd.make_templates)) diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index a9aa93b1a59..9f2e814566a 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2862,7 +2862,15 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c if (p_argidx == 1 && p_context.node && p_context.node->type == GDScriptParser::Node::CALL && ClassDB::is_parent_class(class_name, SNAME("Tween")) && p_method == SNAME("tween_property")) { // Get tweened objects properties. + if (static_cast(p_context.node)->arguments.is_empty()) { + base_type.kind = GDScriptParser::DataType::UNRESOLVED; + break; + } GDScriptParser::ExpressionNode *tweened_object = static_cast(p_context.node)->arguments[0]; + if (!tweened_object) { + base_type.kind = GDScriptParser::DataType::UNRESOLVED; + break; + } StringName native_type = tweened_object->datatype.native_type; switch (tweened_object->datatype.kind) { case GDScriptParser::DataType::SCRIPT: { diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 0eadbd0fb40..210b82212d0 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -33,12 +33,26 @@ #include "grid_map.h" #include "core/io/marshalls.h" +#include "core/math/convex_hull.h" +#include "scene/resources/3d/box_shape_3d.h" +#include "scene/resources/3d/capsule_shape_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" +#include "scene/resources/3d/cylinder_shape_3d.h" +#include "scene/resources/3d/height_map_shape_3d.h" #include "scene/resources/3d/mesh_library.h" +#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h" +#include "scene/resources/3d/primitive_meshes.h" +#include "scene/resources/3d/shape_3d.h" +#include "scene/resources/3d/sphere_shape_3d.h" #include "scene/resources/physics_material.h" #include "scene/resources/surface_tool.h" #include "servers/navigation_server_3d.h" #include "servers/rendering_server.h" +Callable GridMap::_navmesh_source_geometry_parsing_callback; +RID GridMap::_navmesh_source_geometry_parser; + bool GridMap::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; @@ -1338,6 +1352,143 @@ GridMap::GridMap() { #endif // DEBUG_ENABLED } +void GridMap::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer3D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&GridMap::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create(); + NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void GridMap::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + GridMap *gridmap = Object::cast_to(p_node); + + if (gridmap == nullptr) { + return; + } + + NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask(); + + if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) { + Array meshes = gridmap->get_meshes(); + Transform3D xform = gridmap->get_global_transform(); + for (int i = 0; i < meshes.size(); i += 2) { + Ref mesh = meshes[i + 1]; + if (mesh.is_valid()) { + p_source_geometry_data->add_mesh(mesh, xform * (Transform3D)meshes[i]); + } + } + } + + else if ((parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) && (gridmap->get_collision_layer() & parsed_collision_mask)) { + Array shapes = gridmap->get_collision_shapes(); + for (int i = 0; i < shapes.size(); i += 2) { + RID shape = shapes[i + 1]; + PhysicsServer3D::ShapeType type = PhysicsServer3D::get_singleton()->shape_get_type(shape); + Variant data = PhysicsServer3D::get_singleton()->shape_get_data(shape); + + switch (type) { + case PhysicsServer3D::SHAPE_SPHERE: { + real_t radius = data; + Array arr; + arr.resize(RS::ARRAY_MAX); + SphereMesh::create_mesh_array(arr, radius, radius * 2.0); + p_source_geometry_data->add_mesh_array(arr, shapes[i]); + } break; + case PhysicsServer3D::SHAPE_BOX: { + Vector3 extents = data; + Array arr; + arr.resize(RS::ARRAY_MAX); + BoxMesh::create_mesh_array(arr, extents * 2.0); + p_source_geometry_data->add_mesh_array(arr, shapes[i]); + } break; + case PhysicsServer3D::SHAPE_CAPSULE: { + Dictionary dict = data; + real_t radius = dict["radius"]; + real_t height = dict["height"]; + Array arr; + arr.resize(RS::ARRAY_MAX); + CapsuleMesh::create_mesh_array(arr, radius, height); + p_source_geometry_data->add_mesh_array(arr, shapes[i]); + } break; + case PhysicsServer3D::SHAPE_CYLINDER: { + Dictionary dict = data; + real_t radius = dict["radius"]; + real_t height = dict["height"]; + Array arr; + arr.resize(RS::ARRAY_MAX); + CylinderMesh::create_mesh_array(arr, radius, radius, height); + p_source_geometry_data->add_mesh_array(arr, shapes[i]); + } break; + case PhysicsServer3D::SHAPE_CONVEX_POLYGON: { + PackedVector3Array vertices = data; + Geometry3D::MeshData md; + + Error err = ConvexHullComputer::convex_hull(vertices, md); + + if (err == OK) { + PackedVector3Array faces; + + for (const Geometry3D::MeshData::Face &face : md.faces) { + for (uint32_t k = 2; k < face.indices.size(); ++k) { + faces.push_back(md.vertices[face.indices[0]]); + faces.push_back(md.vertices[face.indices[k - 1]]); + faces.push_back(md.vertices[face.indices[k]]); + } + } + + p_source_geometry_data->add_faces(faces, shapes[i]); + } + } break; + case PhysicsServer3D::SHAPE_CONCAVE_POLYGON: { + Dictionary dict = data; + PackedVector3Array faces = Variant(dict["faces"]); + p_source_geometry_data->add_faces(faces, shapes[i]); + } break; + case PhysicsServer3D::SHAPE_HEIGHTMAP: { + Dictionary dict = data; + ///< dict( int:"width", int:"depth",float:"cell_size", float_array:"heights" + int heightmap_depth = dict["depth"]; + int heightmap_width = dict["width"]; + + if (heightmap_depth >= 2 && heightmap_width >= 2) { + const Vector &map_data = dict["heights"]; + + Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1); + Vector3 start = Vector3(heightmap_gridsize.x, 0, heightmap_gridsize.y) * -0.5; + + Vector vertex_array; + vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6); + Vector3 *vertex_array_ptrw = vertex_array.ptrw(); + const real_t *map_data_ptr = map_data.ptr(); + int vertex_index = 0; + + for (int d = 0; d < heightmap_depth - 1; d++) { + for (int w = 0; w < heightmap_width - 1; w++) { + vertex_array_ptrw[vertex_index] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + w], d); + vertex_array_ptrw[vertex_index + 1] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); + vertex_array_ptrw[vertex_index + 2] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); + vertex_array_ptrw[vertex_index + 3] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); + vertex_array_ptrw[vertex_index + 4] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + heightmap_width + w + 1], d + 1); + vertex_array_ptrw[vertex_index + 5] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); + vertex_index += 6; + } + } + if (vertex_array.size() > 0) { + p_source_geometry_data->add_faces(vertex_array, shapes[i]); + } + } + } break; + default: { + WARN_PRINT("Unsupported collision shape type."); + } break; + } + } + } +} + #ifdef DEBUG_ENABLED void GridMap::_update_navigation_debug_edge_connections() { if (bake_navigation) { diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index 1b0016608b3..4b9d003bb86 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -40,6 +40,8 @@ //heh heh, godotsphir!! this shares no code and the design is completely different with previous projects i've done.. //should scale better with hardware that supports instancing +class NavigationMesh; +class NavigationMeshSourceGeometryData3D; class PhysicsMaterial; class GridMap : public Node3D { @@ -302,6 +304,14 @@ class GridMap : public Node3D { Array get_bake_meshes(); RID get_bake_mesh_instance(int p_idx); +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + GridMap(); ~GridMap(); }; diff --git a/modules/gridmap/register_types.cpp b/modules/gridmap/register_types.cpp index e9029d0772e..094396400ca 100644 --- a/modules/gridmap/register_types.cpp +++ b/modules/gridmap/register_types.cpp @@ -45,6 +45,7 @@ void initialize_gridmap_module(ModuleInitializationLevel p_level) { if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { GDREGISTER_CLASS(GridMap); + GridMap::navmesh_parse_init(); } #ifdef TOOLS_ENABLED if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { diff --git a/modules/mono/editor/script_templates/SCsub b/modules/mono/editor/script_templates/SCsub index f465374758a..1503b316807 100644 --- a/modules/mono/editor/script_templates/SCsub +++ b/modules/mono/editor/script_templates/SCsub @@ -5,13 +5,4 @@ Import("env") import editor.template_builders as build_template_cs -env["BUILDERS"]["MakeCSharpTemplateBuilder"] = Builder( - action=env.Run(build_template_cs.make_templates), - suffix=".h", - src_suffix=".cs", -) - -# Template files -templates_sources = Glob("*/*.cs") - -env.Alias("editor_template_cs", [env.MakeCSharpTemplateBuilder("templates.gen.h", templates_sources)]) +env.CommandNoCache("templates.gen.h", Glob("*/*.cs"), env.Run(build_template_cs.make_templates)) diff --git a/modules/navigation/2d/godot_navigation_server_2d.cpp b/modules/navigation/2d/godot_navigation_server_2d.cpp index e53de774cc7..1c7a3e9988a 100644 --- a/modules/navigation/2d/godot_navigation_server_2d.cpp +++ b/modules/navigation/2d/godot_navigation_server_2d.cpp @@ -171,6 +171,8 @@ void GodotNavigationServer2D::init() { #ifdef CLIPPER2_ENABLED navmesh_generator_2d = memnew(NavMeshGenerator2D); ERR_FAIL_NULL_MSG(navmesh_generator_2d, "Failed to init NavMeshGenerator2D."); + RWLockRead read_lock(geometry_parser_rwlock); + navmesh_generator_2d->set_generator_parsers(generator_parsers); #endif // CLIPPER2_ENABLED } @@ -413,12 +415,19 @@ void FORWARD_2(agent_set_paused, RID, p_agent, bool, p_paused, rid_to_rid, bool_ bool FORWARD_1_C(agent_get_paused, RID, p_agent, 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); + if (geometry_parser_owner.owns(p_object)) { + RWLockWrite write_lock(geometry_parser_rwlock); + + NavMeshGeometryParser2D *parser = geometry_parser_owner.get_or_null(p_object); + ERR_FAIL_NULL(parser); + + generator_parsers.erase(parser); +#ifndef CLIPPER2_ENABLED + NavMeshGenerator2D::get_singleton()->set_generator_parsers(generator_parsers); +#endif + geometry_parser_owner.free(parser->self); return; } -#endif // CLIPPER2_ENABLED NavigationServer3D::get_singleton()->free(p_object); } @@ -519,18 +528,25 @@ void GodotNavigationServer2D::query_path(const Refself = rid; + + generator_parsers.push_back(parser); #ifdef CLIPPER2_ENABLED - if (navmesh_generator_2d) { - return navmesh_generator_2d->source_geometry_parser_create(); - } -#endif // CLIPPER2_ENABLED - return RID(); + NavMeshGenerator2D::get_singleton()->set_generator_parsers(generator_parsers); +#endif + 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 + RWLockWrite write_lock(geometry_parser_rwlock); + + NavMeshGeometryParser2D *parser = geometry_parser_owner.get_or_null(p_parser); + ERR_FAIL_NULL(parser); + + parser->callback = p_callback; } diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 0eeb678228e..2ee64cea7f9 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -35,19 +35,8 @@ #include "nav_mesh_generator_2d.h" #include "core/config/project_settings.h" -#include "scene/2d/mesh_instance_2d.h" -#include "scene/2d/multimesh_instance_2d.h" -#include "scene/2d/navigation_obstacle_2d.h" -#include "scene/2d/physics/static_body_2d.h" -#include "scene/2d/polygon_2d.h" -#include "scene/2d/tile_map.h" -#include "scene/resources/2d/capsule_shape_2d.h" -#include "scene/resources/2d/circle_shape_2d.h" -#include "scene/resources/2d/concave_polygon_shape_2d.h" -#include "scene/resources/2d/convex_polygon_shape_2d.h" #include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h" #include "scene/resources/2d/navigation_polygon.h" -#include "scene/resources/2d/rectangle_shape_2d.h" #include "thirdparty/clipper2/include/clipper2/clipper.h" #include "thirdparty/misc/polypartition.h" @@ -55,14 +44,13 @@ NavMeshGenerator2D *NavMeshGenerator2D::singleton = nullptr; Mutex NavMeshGenerator2D::baking_navmesh_mutex; Mutex NavMeshGenerator2D::generator_task_mutex; -RWLock NavMeshGenerator2D::generator_rid_rwlock; +RWLock NavMeshGenerator2D::generator_parsers_rwlock; bool NavMeshGenerator2D::use_threads = true; bool NavMeshGenerator2D::baking_use_multiple_threads = true; bool NavMeshGenerator2D::baking_use_high_priority_threads = true; HashSet> NavMeshGenerator2D::baking_navmeshes; HashMap NavMeshGenerator2D::generator_tasks; -RID_Owner NavMeshGenerator2D::generator_parser_owner; -LocalVector NavMeshGenerator2D::generator_parsers; +LocalVector NavMeshGenerator2D::generator_parsers; NavMeshGenerator2D *NavMeshGenerator2D::get_singleton() { return singleton; @@ -131,12 +119,9 @@ void NavMeshGenerator2D::cleanup() { } generator_tasks.clear(); - generator_rid_rwlock.write_lock(); - for (NavMeshGeometryParser2D *parser : generator_parsers) { - generator_parser_owner.free(parser->self); - } + generator_parsers_rwlock.write_lock(); generator_parsers.clear(); - generator_rid_rwlock.write_unlock(); + generator_parsers_rwlock.write_unlock(); } } @@ -236,510 +221,25 @@ void NavMeshGenerator2D::generator_thread_bake(void *p_arg) { } void NavMeshGenerator2D::generator_parse_geometry_node(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_node, bool p_recurse_children) { - generator_parse_meshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node); - generator_parse_multimeshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node); - generator_parse_polygon2d_node(p_navigation_mesh, p_source_geometry_data, p_node); - generator_parse_staticbody2d_node(p_navigation_mesh, p_source_geometry_data, p_node); - generator_parse_tile_map_layer_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(); + generator_parsers_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(); + generator_parsers_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); } - } else if (Object::cast_to(p_node)) { - // Special case for TileMap, so that internal layer get parsed even if p_recurse_children is false. - for (int i = 0; i < p_node->get_child_count(); i++) { - TileMapLayer *tile_map_layer = Object::cast_to(p_node->get_child(i)); - if (tile_map_layer && tile_map_layer->get_index_in_tile_map() >= 0) { - generator_parse_tile_map_layer_node(p_navigation_mesh, p_source_geometry_data, tile_map_layer); - } - } - } -} - -void NavMeshGenerator2D::generator_parse_meshinstance2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - MeshInstance2D *mesh_instance = Object::cast_to(p_node); - - if (mesh_instance == nullptr) { - return; - } - - NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); - - if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { - return; - } - - Ref mesh = mesh_instance->get_mesh(); - if (mesh.is_null()) { - return; - } - - const Transform2D mesh_instance_xform = p_source_geometry_data->root_node_transform * mesh_instance->get_global_transform(); - - using namespace Clipper2Lib; - - 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) { - continue; - } - - if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) { - continue; - } - - PathD subject_path; - - int index_count = 0; - if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { - index_count = mesh->surface_get_array_index_len(i); - } else { - index_count = mesh->surface_get_array_len(i); - } - - ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0)); - - Array a = mesh->surface_get_arrays(i); - - Vector mesh_vertices = a[Mesh::ARRAY_VERTEX]; - - if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { - Vector mesh_indices = a[Mesh::ARRAY_INDEX]; - for (int vertex_index : mesh_indices) { - const Vector2 &vertex = mesh_vertices[vertex_index]; - const PointD &point = PointD(vertex.x, vertex.y); - subject_path.push_back(point); - } - } else { - for (const Vector2 &vertex : mesh_vertices) { - const PointD &point = PointD(vertex.x, vertex.y); - subject_path.push_back(point); - } - } - subject_paths.push_back(subject_path); - } - - PathsD path_solution; - - path_solution = Union(subject_paths, dummy_clip_paths, FillRule::NonZero); - - //path_solution = RamerDouglasPeucker(path_solution, 0.025); - - Vector> polypaths; - - for (const PathD &scaled_path : path_solution) { - Vector shape_outline; - for (const PointD &scaled_point : scaled_path) { - shape_outline.push_back(Point2(static_cast(scaled_point.x), static_cast(scaled_point.y))); - } - - for (int i = 0; i < shape_outline.size(); i++) { - shape_outline.write[i] = mesh_instance_xform.xform(shape_outline[i]); - } - - p_source_geometry_data->add_obstruction_outline(shape_outline); - } -} - -void NavMeshGenerator2D::generator_parse_multimeshinstance2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - MultiMeshInstance2D *multimesh_instance = Object::cast_to(p_node); - - if (multimesh_instance == nullptr) { - return; - } - - NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); - if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { - return; - } - - Ref multimesh = multimesh_instance->get_multimesh(); - if (!(multimesh.is_valid() && multimesh->get_transform_format() == MultiMesh::TRANSFORM_2D)) { - return; - } - - Ref mesh = multimesh->get_mesh(); - if (mesh.is_null()) { - return; - } - - using namespace Clipper2Lib; - - 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) { - continue; - } - - if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) { - continue; - } - - PathD subject_path; - - int index_count = 0; - if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { - index_count = mesh->surface_get_array_index_len(i); - } else { - index_count = mesh->surface_get_array_len(i); - } - - ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0)); - - Array a = mesh->surface_get_arrays(i); - - Vector mesh_vertices = a[Mesh::ARRAY_VERTEX]; - - if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { - Vector mesh_indices = a[Mesh::ARRAY_INDEX]; - for (int vertex_index : mesh_indices) { - const Vector2 &vertex = mesh_vertices[vertex_index]; - const PointD &point = PointD(vertex.x, vertex.y); - subject_path.push_back(point); - } - } else { - for (const Vector2 &vertex : mesh_vertices) { - const PointD &point = PointD(vertex.x, vertex.y); - subject_path.push_back(point); - } - } - mesh_subject_paths.push_back(subject_path); - } - - PathsD mesh_path_solution = Union(mesh_subject_paths, dummy_clip_paths, FillRule::NonZero); - - //path_solution = RamerDouglasPeucker(path_solution, 0.025); - - int multimesh_instance_count = multimesh->get_visible_instance_count(); - if (multimesh_instance_count == -1) { - multimesh_instance_count = multimesh->get_instance_count(); - } - - const Transform2D multimesh_instance_xform = p_source_geometry_data->root_node_transform * multimesh_instance->get_global_transform(); - - 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 PathD &mesh_path : mesh_path_solution) { - Vector shape_outline; - - for (const PointD &mesh_path_point : mesh_path) { - shape_outline.push_back(Point2(static_cast(mesh_path_point.x), static_cast(mesh_path_point.y))); - } - - for (int j = 0; j < shape_outline.size(); j++) { - shape_outline.write[j] = multimesh_instance_mesh_instance_xform.xform(shape_outline[j]); - } - p_source_geometry_data->add_obstruction_outline(shape_outline); - } - } -} - -void NavMeshGenerator2D::generator_parse_polygon2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - Polygon2D *polygon_2d = Object::cast_to(p_node); - - if (polygon_2d == nullptr) { - return; - } - - NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); - - if (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) { - const Transform2D polygon_2d_xform = p_source_geometry_data->root_node_transform * polygon_2d->get_global_transform(); - - Vector shape_outline = polygon_2d->get_polygon(); - for (int i = 0; i < shape_outline.size(); i++) { - shape_outline.write[i] = polygon_2d_xform.xform(shape_outline[i]); - } - - p_source_geometry_data->add_obstruction_outline(shape_outline); - } -} - -void NavMeshGenerator2D::generator_parse_staticbody2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - StaticBody2D *static_body = Object::cast_to(p_node); - - if (static_body == nullptr) { - return; - } - - NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); - if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { - return; - } - - uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask(); - if (!(static_body->get_collision_layer() & parsed_collision_mask)) { - return; - } - - List shape_owners; - static_body->get_shape_owners(&shape_owners); - - for (uint32_t shape_owner : shape_owners) { - if (static_body->is_shape_owner_disabled(shape_owner)) { - continue; - } - - const int shape_count = static_body->shape_owner_get_shape_count(shape_owner); - - for (int shape_index = 0; shape_index < shape_count; shape_index++) { - Ref s = static_body->shape_owner_get_shape(shape_owner, shape_index); - - if (s.is_null()) { - continue; - } - - const Transform2D static_body_xform = p_source_geometry_data->root_node_transform * static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner); - - RectangleShape2D *rectangle_shape = Object::cast_to(*s); - if (rectangle_shape) { - Vector shape_outline; - - const Vector2 &rectangle_size = rectangle_shape->get_size(); - - shape_outline.resize(5); - shape_outline.write[0] = static_body_xform.xform(-rectangle_size * 0.5); - shape_outline.write[1] = static_body_xform.xform(Vector2(rectangle_size.x, -rectangle_size.y) * 0.5); - shape_outline.write[2] = static_body_xform.xform(rectangle_size * 0.5); - shape_outline.write[3] = static_body_xform.xform(Vector2(-rectangle_size.x, rectangle_size.y) * 0.5); - shape_outline.write[4] = static_body_xform.xform(-rectangle_size * 0.5); - - p_source_geometry_data->add_obstruction_outline(shape_outline); - } - - CapsuleShape2D *capsule_shape = Object::cast_to(*s); - if (capsule_shape) { - const real_t capsule_height = capsule_shape->get_height(); - const real_t capsule_radius = capsule_shape->get_radius(); - - Vector shape_outline; - const real_t turn_step = Math_TAU / 12.0; - shape_outline.resize(14); - int shape_outline_inx = 0; - for (int i = 0; i < 12; i++) { - Vector2 ofs = Vector2(0, (i > 3 && i <= 9) ? -capsule_height * 0.5 + capsule_radius : capsule_height * 0.5 - capsule_radius); - - shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius + ofs); - shape_outline_inx += 1; - if (i == 3 || i == 9) { - shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius - ofs); - shape_outline_inx += 1; - } - } - - p_source_geometry_data->add_obstruction_outline(shape_outline); - } - - CircleShape2D *circle_shape = Object::cast_to(*s); - if (circle_shape) { - const real_t circle_radius = circle_shape->get_radius(); - - Vector shape_outline; - int circle_edge_count = 12; - shape_outline.resize(circle_edge_count); - - const real_t turn_step = Math_TAU / real_t(circle_edge_count); - for (int i = 0; i < circle_edge_count; i++) { - shape_outline.write[i] = static_body_xform.xform(Vector2(Math::cos(i * turn_step), Math::sin(i * turn_step)) * circle_radius); - } - - p_source_geometry_data->add_obstruction_outline(shape_outline); - } - - ConcavePolygonShape2D *concave_polygon_shape = Object::cast_to(*s); - if (concave_polygon_shape) { - Vector shape_outline = concave_polygon_shape->get_segments(); - - for (int i = 0; i < shape_outline.size(); i++) { - shape_outline.write[i] = static_body_xform.xform(shape_outline[i]); - } - - p_source_geometry_data->add_obstruction_outline(shape_outline); - } - - ConvexPolygonShape2D *convex_polygon_shape = Object::cast_to(*s); - if (convex_polygon_shape) { - Vector shape_outline = convex_polygon_shape->get_points(); - - for (int i = 0; i < shape_outline.size(); i++) { - shape_outline.write[i] = static_body_xform.xform(shape_outline[i]); - } - - p_source_geometry_data->add_obstruction_outline(shape_outline); - } - } - } -} - -void NavMeshGenerator2D::generator_parse_tile_map_layer_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - TileMapLayer *tile_map_layer = Object::cast_to(p_node); - if (tile_map_layer == nullptr) { - return; - } - - Ref tile_set = tile_map_layer->get_tile_set(); - if (tile_set.is_null()) { - return; - } - - int physics_layers_count = tile_set->get_physics_layers_count(); - int navigation_layers_count = tile_set->get_navigation_layers_count(); - if (physics_layers_count <= 0 && navigation_layers_count <= 0) { - return; - } - - NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); - uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask(); - - const Transform2D tilemap_xform = p_source_geometry_data->root_node_transform * tile_map_layer->get_global_transform(); - - TypedArray used_cells = tile_map_layer->get_used_cells(); - for (int used_cell_index = 0; used_cell_index < used_cells.size(); used_cell_index++) { - const Vector2i &cell = used_cells[used_cell_index]; - - const TileData *tile_data = tile_map_layer->get_cell_tile_data(cell); - if (tile_data == nullptr) { - continue; - } - - // Transform flags. - const int alternative_id = tile_map_layer->get_cell_alternative_tile(cell); - bool flip_h = (alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_H); - bool flip_v = (alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_V); - bool transpose = (alternative_id & TileSetAtlasSource::TRANSFORM_TRANSPOSE); - - Transform2D tile_transform; - tile_transform.set_origin(tile_map_layer->map_to_local(cell)); - - const Transform2D tile_transform_offset = tilemap_xform * tile_transform; - - // Parse traversable polygons. - for (int navigation_layer = 0; navigation_layer < navigation_layers_count; navigation_layer++) { - Ref navigation_polygon = tile_data->get_navigation_polygon(navigation_layer, flip_h, flip_v, transpose); - if (navigation_polygon.is_valid()) { - for (int outline_index = 0; outline_index < navigation_polygon->get_outline_count(); outline_index++) { - const Vector &navigation_polygon_outline = navigation_polygon->get_outline(outline_index); - if (navigation_polygon_outline.is_empty()) { - continue; - } - - Vector traversable_outline; - traversable_outline.resize(navigation_polygon_outline.size()); - - const Vector2 *navigation_polygon_outline_ptr = navigation_polygon_outline.ptr(); - Vector2 *traversable_outline_ptrw = traversable_outline.ptrw(); - - for (int traversable_outline_index = 0; traversable_outline_index < traversable_outline.size(); traversable_outline_index++) { - traversable_outline_ptrw[traversable_outline_index] = tile_transform_offset.xform(navigation_polygon_outline_ptr[traversable_outline_index]); - } - - p_source_geometry_data->_add_traversable_outline(traversable_outline); - } - } - } - - // Parse obstacles. - for (int physics_layer = 0; physics_layer < physics_layers_count; physics_layer++) { - if ((parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) && - (tile_set->get_physics_layer_collision_layer(physics_layer) & parsed_collision_mask)) { - for (int collision_polygon_index = 0; collision_polygon_index < tile_data->get_collision_polygons_count(physics_layer); collision_polygon_index++) { - PackedVector2Array collision_polygon_points = tile_data->get_collision_polygon_points(physics_layer, collision_polygon_index); - if (collision_polygon_points.is_empty()) { - continue; - } - - if (flip_h || flip_v || transpose) { - collision_polygon_points = TileData::get_transformed_vertices(collision_polygon_points, flip_h, flip_v, transpose); - } - - Vector obstruction_outline; - obstruction_outline.resize(collision_polygon_points.size()); - - const Vector2 *collision_polygon_points_ptr = collision_polygon_points.ptr(); - Vector2 *obstruction_outline_ptrw = obstruction_outline.ptrw(); - - for (int obstruction_outline_index = 0; obstruction_outline_index < obstruction_outline.size(); obstruction_outline_index++) { - obstruction_outline_ptrw[obstruction_outline_index] = tile_transform_offset.xform(collision_polygon_points_ptr[obstruction_outline_index]); - } - - p_source_geometry_data->_add_obstruction_outline(obstruction_outline); - } - } - } } } -void NavMeshGenerator2D::generator_parse_navigationobstacle_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - NavigationObstacle2D *obstacle = Object::cast_to(p_node); - if (obstacle == nullptr) { - return; - } - - if (!obstacle->get_affect_navigation_mesh()) { - return; - } - - const Vector2 safe_scale = obstacle->get_global_scale().abs().maxf(0.001); - const float obstacle_radius = obstacle->get_radius(); - - if (obstacle_radius > 0.0) { - // Radius defined obstacle should be uniformly scaled from obstacle basis max scale axis. - const float scaling_max_value = safe_scale[safe_scale.max_axis_index()]; - const Vector2 uniform_max_scale = Vector2(scaling_max_value, scaling_max_value); - const Transform2D obstacle_circle_transform = p_source_geometry_data->root_node_transform * Transform2D(obstacle->get_global_rotation(), uniform_max_scale, 0.0, obstacle->get_global_position()); - - Vector obstruction_circle_vertices; - - // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding. - // Without, navigation paths can go directly through the middle of the obstacle and conflict with the avoidance to get agents stuck. - // No place for excessive "round" detail here. Every additional edge adds a high cost for something that needs to be quick, not pretty. - static const int circle_points = 12; - - obstruction_circle_vertices.resize(circle_points); - Vector2 *circle_vertices_ptrw = obstruction_circle_vertices.ptrw(); - const real_t circle_point_step = Math_TAU / circle_points; - - for (int i = 0; i < circle_points; i++) { - const float angle = i * circle_point_step; - circle_vertices_ptrw[i] = obstacle_circle_transform.xform(Vector2(Math::cos(angle) * obstacle_radius, Math::sin(angle) * obstacle_radius)); - } - - p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_carve_navigation_mesh()); - } - - // Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account. - const Transform2D node_xform = p_source_geometry_data->root_node_transform * obstacle->get_global_transform(); - - const Vector &obstacle_vertices = obstacle->get_vertices(); - - if (obstacle_vertices.is_empty()) { - return; - } - - Vector obstruction_shape_vertices; - obstruction_shape_vertices.resize(obstacle_vertices.size()); - - const Vector2 *obstacle_vertices_ptr = obstacle_vertices.ptr(); - Vector2 *obstruction_shape_vertices_ptrw = obstruction_shape_vertices.ptrw(); - - for (int i = 0; i < obstacle_vertices.size(); i++) { - obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]); - } - p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, obstacle->get_carve_navigation_mesh()); +void NavMeshGenerator2D::set_generator_parsers(LocalVector p_parsers) { + RWLockWrite write_lock(generator_parsers_rwlock); + generator_parsers = p_parsers; } void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node) { @@ -803,47 +303,6 @@ 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 p_navigation_mesh, Ref p_source_geometry_data) { if (p_navigation_mesh.is_null() || p_source_geometry_data.is_null()) { return; diff --git a/modules/navigation/2d/nav_mesh_generator_2d.h b/modules/navigation/2d/nav_mesh_generator_2d.h index ccba56735bf..00cf54fa8c0 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.h +++ b/modules/navigation/2d/nav_mesh_generator_2d.h @@ -38,6 +38,7 @@ #include "core/object/class_db.h" #include "core/object/worker_thread_pool.h" #include "core/templates/rid_owner.h" +#include "servers/navigation_server_2d.h" class Node; class NavigationPolygon; @@ -49,12 +50,7 @@ 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 generator_parser_owner; + static RWLock generator_parsers_rwlock; static LocalVector generator_parsers; static bool use_threads; @@ -87,13 +83,6 @@ class NavMeshGenerator2D : public Object { static void generator_parse_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node); static void generator_bake_from_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data); - static void generator_parse_meshinstance2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); - static void generator_parse_multimeshinstance2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); - static void generator_parse_polygon2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); - static void generator_parse_staticbody2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); - static void generator_parse_tile_map_layer_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); - static void generator_parse_navigationobstacle_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); - static bool generator_emit_callback(const Callable &p_callback); public: @@ -103,17 +92,13 @@ class NavMeshGenerator2D : public Object { static void cleanup(); static void finish(); + static void set_generator_parsers(LocalVector p_parsers); + static void parse_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()); static void bake_from_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, const Callable &p_callback = Callable()); static void bake_from_source_geometry_data_async(Ref p_navigation_mesh, Ref p_source_geometry_data, const Callable &p_callback = Callable()); static bool is_baking(Ref 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 0b91d504889..e89aa68b858 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.cpp +++ b/modules/navigation/3d/godot_navigation_server_3d.cpp @@ -1272,10 +1272,17 @@ COMMAND_1(free, RID, p_object) { } else if (obstacle_owner.owns(p_object)) { internal_free_obstacle(p_object); + } else if (geometry_parser_owner.owns(p_object)) { + RWLockWrite write_lock(geometry_parser_rwlock); + + NavMeshGeometryParser3D *parser = geometry_parser_owner.get_or_null(p_object); + ERR_FAIL_NULL(parser); + + generator_parsers.erase(parser); #ifndef _3D_DISABLED - } else if (navmesh_generator_3d && navmesh_generator_3d->owns(p_object)) { - navmesh_generator_3d->free(p_object); -#endif // _3D_DISABLED + NavMeshGenerator3D::get_singleton()->set_generator_parsers(generator_parsers); +#endif + geometry_parser_owner.free(parser->self); } else { ERR_PRINT("Attempted to free a NavigationServer RID that did not exist (or was already freed)."); @@ -1410,6 +1417,8 @@ void GodotNavigationServer3D::process(real_t p_delta_time) { void GodotNavigationServer3D::init() { #ifndef _3D_DISABLED navmesh_generator_3d = memnew(NavMeshGenerator3D); + RWLockRead read_lock(geometry_parser_rwlock); + navmesh_generator_3d->set_generator_parsers(generator_parsers); #endif // _3D_DISABLED } @@ -1435,20 +1444,27 @@ void GodotNavigationServer3D::query_path(const Refself = rid; + + generator_parsers.push_back(parser); #ifndef _3D_DISABLED - if (navmesh_generator_3d) { - return navmesh_generator_3d->source_geometry_parser_create(); - } -#endif // _3D_DISABLED - return RID(); + NavMeshGenerator3D::get_singleton()->set_generator_parsers(generator_parsers); +#endif + 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 + RWLockWrite write_lock(geometry_parser_rwlock); + + NavMeshGeometryParser3D *parser = geometry_parser_owner.get_or_null(p_parser); + ERR_FAIL_NULL(parser); + + parser->callback = p_callback; } Vector GodotNavigationServer3D::simplify_path(const Vector &p_path, real_t p_epsilon) { diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index 2c1704d6b45..25e9baa9fc9 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -35,46 +35,23 @@ #include "nav_mesh_generator_3d.h" #include "core/config/project_settings.h" -#include "core/math/convex_hull.h" #include "core/os/thread.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/3d/multimesh_instance_3d.h" -#include "scene/3d/navigation_obstacle_3d.h" -#include "scene/3d/physics/static_body_3d.h" -#include "scene/resources/3d/box_shape_3d.h" -#include "scene/resources/3d/capsule_shape_3d.h" -#include "scene/resources/3d/concave_polygon_shape_3d.h" -#include "scene/resources/3d/convex_polygon_shape_3d.h" -#include "scene/resources/3d/cylinder_shape_3d.h" -#include "scene/resources/3d/height_map_shape_3d.h" +#include "scene/3d/node_3d.h" #include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h" -#include "scene/resources/3d/primitive_meshes.h" -#include "scene/resources/3d/shape_3d.h" -#include "scene/resources/3d/sphere_shape_3d.h" #include "scene/resources/navigation_mesh.h" -#include "modules/modules_enabled.gen.h" // For csg, gridmap. - -#ifdef MODULE_CSG_ENABLED -#include "modules/csg/csg_shape.h" -#endif -#ifdef MODULE_GRIDMAP_ENABLED -#include "modules/gridmap/grid_map.h" -#endif - #include NavMeshGenerator3D *NavMeshGenerator3D::singleton = nullptr; Mutex NavMeshGenerator3D::baking_navmesh_mutex; Mutex NavMeshGenerator3D::generator_task_mutex; -RWLock NavMeshGenerator3D::generator_rid_rwlock; +RWLock NavMeshGenerator3D::generator_parsers_rwlock; bool NavMeshGenerator3D::use_threads = true; bool NavMeshGenerator3D::baking_use_multiple_threads = true; bool NavMeshGenerator3D::baking_use_high_priority_threads = true; HashSet> NavMeshGenerator3D::baking_navmeshes; HashMap NavMeshGenerator3D::generator_tasks; -RID_Owner NavMeshGenerator3D::generator_parser_owner; -LocalVector NavMeshGenerator3D::generator_parsers; +LocalVector NavMeshGenerator3D::generator_parsers; NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() { return singleton; @@ -143,12 +120,9 @@ void NavMeshGenerator3D::cleanup() { } generator_tasks.clear(); - generator_rid_rwlock.write_lock(); - for (NavMeshGeometryParser3D *parser : generator_parsers) { - generator_parser_owner.free(parser->self); - } + generator_parsers_rwlock.write_lock(); generator_parsers.clear(); - generator_rid_rwlock.write_unlock(); + generator_parsers_rwlock.write_unlock(); } } @@ -249,25 +223,14 @@ void NavMeshGenerator3D::generator_thread_bake(void *p_arg) { } void NavMeshGenerator3D::generator_parse_geometry_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node, bool p_recurse_children) { - generator_parse_meshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node); - generator_parse_multimeshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node); - generator_parse_staticbody3d_node(p_navigation_mesh, p_source_geometry_data, p_node); -#ifdef MODULE_CSG_ENABLED - generator_parse_csgshape3d_node(p_navigation_mesh, p_source_geometry_data, p_node); -#endif -#ifdef MODULE_GRIDMAP_ENABLED - generator_parse_gridmap_node(p_navigation_mesh, p_source_geometry_data, p_node); -#endif - generator_parse_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node); - - generator_rid_rwlock.read_lock(); + generator_parsers_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(); + generator_parsers_rwlock.read_unlock(); if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { @@ -276,376 +239,9 @@ void NavMeshGenerator3D::generator_parse_geometry_node(const Ref } } -void NavMeshGenerator3D::generator_parse_meshinstance3d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - MeshInstance3D *mesh_instance = Object::cast_to(p_node); - - if (mesh_instance) { - NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); - - if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) { - Ref mesh = mesh_instance->get_mesh(); - if (mesh.is_valid()) { - p_source_geometry_data->add_mesh(mesh, mesh_instance->get_global_transform()); - } - } - } -} - -void NavMeshGenerator3D::generator_parse_multimeshinstance3d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - MultiMeshInstance3D *multimesh_instance = Object::cast_to(p_node); - - if (multimesh_instance) { - NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); - - if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) { - Ref multimesh = multimesh_instance->get_multimesh(); - if (multimesh.is_valid()) { - Ref mesh = multimesh->get_mesh(); - if (mesh.is_valid()) { - int n = multimesh->get_visible_instance_count(); - if (n == -1) { - n = multimesh->get_instance_count(); - } - for (int i = 0; i < n; i++) { - p_source_geometry_data->add_mesh(mesh, multimesh_instance->get_global_transform() * multimesh->get_instance_transform(i)); - } - } - } - } - } -} - -void NavMeshGenerator3D::generator_parse_staticbody3d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - StaticBody3D *static_body = Object::cast_to(p_node); - - if (static_body) { - NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); - uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask(); - - if ((parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) && (static_body->get_collision_layer() & parsed_collision_mask)) { - List shape_owners; - static_body->get_shape_owners(&shape_owners); - for (uint32_t shape_owner : shape_owners) { - if (static_body->is_shape_owner_disabled(shape_owner)) { - continue; - } - const int shape_count = static_body->shape_owner_get_shape_count(shape_owner); - for (int shape_index = 0; shape_index < shape_count; shape_index++) { - Ref s = static_body->shape_owner_get_shape(shape_owner, shape_index); - if (s.is_null()) { - continue; - } - - const Transform3D transform = static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner); - - BoxShape3D *box = Object::cast_to(*s); - if (box) { - Array arr; - arr.resize(RS::ARRAY_MAX); - BoxMesh::create_mesh_array(arr, box->get_size()); - p_source_geometry_data->add_mesh_array(arr, transform); - } - - CapsuleShape3D *capsule = Object::cast_to(*s); - if (capsule) { - Array arr; - arr.resize(RS::ARRAY_MAX); - CapsuleMesh::create_mesh_array(arr, capsule->get_radius(), capsule->get_height()); - p_source_geometry_data->add_mesh_array(arr, transform); - } - - CylinderShape3D *cylinder = Object::cast_to(*s); - if (cylinder) { - Array arr; - arr.resize(RS::ARRAY_MAX); - CylinderMesh::create_mesh_array(arr, cylinder->get_radius(), cylinder->get_radius(), cylinder->get_height()); - p_source_geometry_data->add_mesh_array(arr, transform); - } - - SphereShape3D *sphere = Object::cast_to(*s); - if (sphere) { - Array arr; - arr.resize(RS::ARRAY_MAX); - SphereMesh::create_mesh_array(arr, sphere->get_radius(), sphere->get_radius() * 2.0); - p_source_geometry_data->add_mesh_array(arr, transform); - } - - ConcavePolygonShape3D *concave_polygon = Object::cast_to(*s); - if (concave_polygon) { - p_source_geometry_data->add_faces(concave_polygon->get_faces(), transform); - } - - ConvexPolygonShape3D *convex_polygon = Object::cast_to(*s); - if (convex_polygon) { - Vector varr = Variant(convex_polygon->get_points()); - Geometry3D::MeshData md; - - Error err = ConvexHullComputer::convex_hull(varr, md); - - if (err == OK) { - PackedVector3Array faces; - - for (const Geometry3D::MeshData::Face &face : md.faces) { - for (uint32_t k = 2; k < face.indices.size(); ++k) { - faces.push_back(md.vertices[face.indices[0]]); - faces.push_back(md.vertices[face.indices[k - 1]]); - faces.push_back(md.vertices[face.indices[k]]); - } - } - - p_source_geometry_data->add_faces(faces, transform); - } - } - - HeightMapShape3D *heightmap_shape = Object::cast_to(*s); - if (heightmap_shape) { - int heightmap_depth = heightmap_shape->get_map_depth(); - int heightmap_width = heightmap_shape->get_map_width(); - - if (heightmap_depth >= 2 && heightmap_width >= 2) { - const Vector &map_data = heightmap_shape->get_map_data(); - - Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1); - Vector3 start = Vector3(heightmap_gridsize.x, 0, heightmap_gridsize.y) * -0.5; - - Vector vertex_array; - vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6); - Vector3 *vertex_array_ptrw = vertex_array.ptrw(); - const real_t *map_data_ptr = map_data.ptr(); - int vertex_index = 0; - - for (int d = 0; d < heightmap_depth - 1; d++) { - for (int w = 0; w < heightmap_width - 1; w++) { - vertex_array_ptrw[vertex_index] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + w], d); - vertex_array_ptrw[vertex_index + 1] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); - vertex_array_ptrw[vertex_index + 2] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); - vertex_array_ptrw[vertex_index + 3] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); - vertex_array_ptrw[vertex_index + 4] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + heightmap_width + w + 1], d + 1); - vertex_array_ptrw[vertex_index + 5] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); - vertex_index += 6; - } - } - if (vertex_array.size() > 0) { - p_source_geometry_data->add_faces(vertex_array, transform); - } - } - } - } - } - } - } -} - -#ifdef MODULE_CSG_ENABLED -void NavMeshGenerator3D::generator_parse_csgshape3d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - CSGShape3D *csgshape3d = Object::cast_to(p_node); - - if (csgshape3d) { - NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); - uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask(); - - if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS && csgshape3d->is_using_collision() && (csgshape3d->get_collision_layer() & parsed_collision_mask)) || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) { - CSGShape3D *csg_shape = Object::cast_to(p_node); - Array meshes = csg_shape->get_meshes(); - if (!meshes.is_empty()) { - Ref mesh = meshes[1]; - if (mesh.is_valid()) { - p_source_geometry_data->add_mesh(mesh, csg_shape->get_global_transform()); - } - } - } - } -} -#endif // MODULE_CSG_ENABLED - -#ifdef MODULE_GRIDMAP_ENABLED -void NavMeshGenerator3D::generator_parse_gridmap_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - GridMap *gridmap = Object::cast_to(p_node); - - if (gridmap) { - NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); - uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask(); - - if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) { - Array meshes = gridmap->get_meshes(); - Transform3D xform = gridmap->get_global_transform(); - for (int i = 0; i < meshes.size(); i += 2) { - Ref mesh = meshes[i + 1]; - if (mesh.is_valid()) { - p_source_geometry_data->add_mesh(mesh, xform * (Transform3D)meshes[i]); - } - } - } - - else if ((parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) && (gridmap->get_collision_layer() & parsed_collision_mask)) { - Array shapes = gridmap->get_collision_shapes(); - for (int i = 0; i < shapes.size(); i += 2) { - RID shape = shapes[i + 1]; - PhysicsServer3D::ShapeType type = PhysicsServer3D::get_singleton()->shape_get_type(shape); - Variant data = PhysicsServer3D::get_singleton()->shape_get_data(shape); - - switch (type) { - case PhysicsServer3D::SHAPE_SPHERE: { - real_t radius = data; - Array arr; - arr.resize(RS::ARRAY_MAX); - SphereMesh::create_mesh_array(arr, radius, radius * 2.0); - p_source_geometry_data->add_mesh_array(arr, shapes[i]); - } break; - case PhysicsServer3D::SHAPE_BOX: { - Vector3 extents = data; - Array arr; - arr.resize(RS::ARRAY_MAX); - BoxMesh::create_mesh_array(arr, extents * 2.0); - p_source_geometry_data->add_mesh_array(arr, shapes[i]); - } break; - case PhysicsServer3D::SHAPE_CAPSULE: { - Dictionary dict = data; - real_t radius = dict["radius"]; - real_t height = dict["height"]; - Array arr; - arr.resize(RS::ARRAY_MAX); - CapsuleMesh::create_mesh_array(arr, radius, height); - p_source_geometry_data->add_mesh_array(arr, shapes[i]); - } break; - case PhysicsServer3D::SHAPE_CYLINDER: { - Dictionary dict = data; - real_t radius = dict["radius"]; - real_t height = dict["height"]; - Array arr; - arr.resize(RS::ARRAY_MAX); - CylinderMesh::create_mesh_array(arr, radius, radius, height); - p_source_geometry_data->add_mesh_array(arr, shapes[i]); - } break; - case PhysicsServer3D::SHAPE_CONVEX_POLYGON: { - PackedVector3Array vertices = data; - Geometry3D::MeshData md; - - Error err = ConvexHullComputer::convex_hull(vertices, md); - - if (err == OK) { - PackedVector3Array faces; - - for (const Geometry3D::MeshData::Face &face : md.faces) { - for (uint32_t k = 2; k < face.indices.size(); ++k) { - faces.push_back(md.vertices[face.indices[0]]); - faces.push_back(md.vertices[face.indices[k - 1]]); - faces.push_back(md.vertices[face.indices[k]]); - } - } - - p_source_geometry_data->add_faces(faces, shapes[i]); - } - } break; - case PhysicsServer3D::SHAPE_CONCAVE_POLYGON: { - Dictionary dict = data; - PackedVector3Array faces = Variant(dict["faces"]); - p_source_geometry_data->add_faces(faces, shapes[i]); - } break; - case PhysicsServer3D::SHAPE_HEIGHTMAP: { - Dictionary dict = data; - ///< dict( int:"width", int:"depth",float:"cell_size", float_array:"heights" - int heightmap_depth = dict["depth"]; - int heightmap_width = dict["width"]; - - if (heightmap_depth >= 2 && heightmap_width >= 2) { - const Vector &map_data = dict["heights"]; - - Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1); - Vector3 start = Vector3(heightmap_gridsize.x, 0, heightmap_gridsize.y) * -0.5; - - Vector vertex_array; - vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6); - Vector3 *vertex_array_ptrw = vertex_array.ptrw(); - const real_t *map_data_ptr = map_data.ptr(); - int vertex_index = 0; - - for (int d = 0; d < heightmap_depth - 1; d++) { - for (int w = 0; w < heightmap_width - 1; w++) { - vertex_array_ptrw[vertex_index] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + w], d); - vertex_array_ptrw[vertex_index + 1] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); - vertex_array_ptrw[vertex_index + 2] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); - vertex_array_ptrw[vertex_index + 3] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); - vertex_array_ptrw[vertex_index + 4] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + heightmap_width + w + 1], d + 1); - vertex_array_ptrw[vertex_index + 5] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); - vertex_index += 6; - } - } - if (vertex_array.size() > 0) { - p_source_geometry_data->add_faces(vertex_array, shapes[i]); - } - } - } break; - default: { - WARN_PRINT("Unsupported collision shape type."); - } break; - } - } - } - } -} -#endif // MODULE_GRIDMAP_ENABLED - -void NavMeshGenerator3D::generator_parse_navigationobstacle_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { - NavigationObstacle3D *obstacle = Object::cast_to(p_node); - if (obstacle == nullptr) { - return; - } - - if (!obstacle->get_affect_navigation_mesh()) { - return; - } - - const float elevation = obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y; - // Prevent non-positive scaling. - const Vector3 safe_scale = obstacle->get_global_basis().get_scale().abs().maxf(0.001); - const float obstacle_radius = obstacle->get_radius(); - - if (obstacle_radius > 0.0) { - // Radius defined obstacle should be uniformly scaled from obstacle basis max scale axis. - const float scaling_max_value = safe_scale[safe_scale.max_axis_index()]; - const Vector3 uniform_max_scale = Vector3(scaling_max_value, scaling_max_value, scaling_max_value); - const Transform3D obstacle_circle_transform = p_source_geometry_data->root_node_transform * Transform3D(Basis().scaled(uniform_max_scale), obstacle->get_global_position()); - - Vector obstruction_circle_vertices; - - // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding. - // Without, navigation paths can go directly through the middle of the obstacle and conflict with the avoidance to get agents stuck. - // No place for excessive "round" detail here. Every additional edge adds a high cost for something that needs to be quick, not pretty. - static const int circle_points = 12; - - obstruction_circle_vertices.resize(circle_points); - Vector3 *circle_vertices_ptrw = obstruction_circle_vertices.ptrw(); - const real_t circle_point_step = Math_TAU / circle_points; - - for (int i = 0; i < circle_points; i++) { - const float angle = i * circle_point_step; - circle_vertices_ptrw[i] = obstacle_circle_transform.xform(Vector3(Math::cos(angle) * obstacle_radius, 0.0, Math::sin(angle) * obstacle_radius)); - } - - p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, elevation - obstacle_radius, scaling_max_value * obstacle_radius, obstacle->get_carve_navigation_mesh()); - } - - // Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account. - const Transform3D node_xform = p_source_geometry_data->root_node_transform * Transform3D(Basis().scaled(safe_scale).rotated(Vector3(0.0, 1.0, 0.0), obstacle->get_global_rotation().y), obstacle->get_global_position()); - - const Vector &obstacle_vertices = obstacle->get_vertices(); - - if (obstacle_vertices.is_empty()) { - return; - } - - Vector obstruction_shape_vertices; - obstruction_shape_vertices.resize(obstacle_vertices.size()); - - const Vector3 *obstacle_vertices_ptr = obstacle_vertices.ptr(); - Vector3 *obstruction_shape_vertices_ptrw = obstruction_shape_vertices.ptrw(); - - for (int i = 0; i < obstacle_vertices.size(); i++) { - obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]); - obstruction_shape_vertices_ptrw[i].y = 0.0; - } - p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, elevation, safe_scale.y * obstacle->get_height(), obstacle->get_carve_navigation_mesh()); +void NavMeshGenerator3D::set_generator_parsers(LocalVector p_parsers) { + RWLockWrite write_lock(generator_parsers_rwlock); + generator_parsers = p_parsers; } void NavMeshGenerator3D::generator_parse_source_geometry_data(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node) { @@ -967,45 +563,4 @@ 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 6cf537333e9..3b6c0b2f357 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.h +++ b/modules/navigation/3d/nav_mesh_generator_3d.h @@ -38,8 +38,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. +#include "servers/navigation_server_3d.h" class Node; class NavigationMesh; @@ -51,12 +50,7 @@ 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 generator_parser_owner; + static RWLock generator_parsers_rwlock; static LocalVector generator_parsers; static bool use_threads; @@ -89,17 +83,6 @@ class NavMeshGenerator3D : public Object { static void generator_parse_source_geometry_data(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node); static void generator_bake_from_source_geometry_data(Ref p_navigation_mesh, const Ref &p_source_geometry_data); - static void generator_parse_meshinstance3d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); - static void generator_parse_multimeshinstance3d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); - static void generator_parse_staticbody3d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); -#ifdef MODULE_CSG_ENABLED - static void generator_parse_csgshape3d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); -#endif // MODULE_CSG_ENABLED -#ifdef MODULE_GRIDMAP_ENABLED - static void generator_parse_gridmap_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); -#endif // MODULE_GRIDMAP_ENABLED - static void generator_parse_navigationobstacle_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); - static bool generator_emit_callback(const Callable &p_callback); public: @@ -109,17 +92,13 @@ class NavMeshGenerator3D : public Object { static void cleanup(); static void finish(); + static void set_generator_parsers(LocalVector p_parsers); + static void parse_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()); static void bake_from_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, const Callable &p_callback = Callable()); static void bake_from_source_geometry_data_async(Ref p_navigation_mesh, Ref p_source_geometry_data, const Callable &p_callback = Callable()); static bool is_baking(Ref 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/text_server_adv/SCsub b/modules/text_server_adv/SCsub index d265af31484..30f21eb8628 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -479,8 +479,9 @@ if env["builtin_icu4c"]: thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] if env.editor_build: - env_icu.Depends("#thirdparty/icu4c/icudata.gen.h", "#thirdparty/icu4c/icudt_godot.dat") - env_icu.Command("#thirdparty/icu4c/icudata.gen.h", "#thirdparty/icu4c/icudt_godot.dat", make_icu_data) + env_icu.CommandNoCache( + "#thirdparty/icu4c/icudata.gen.h", "#thirdparty/icu4c/icudt_godot.dat", env.Run(make_icu_data) + ) env_text_server_adv.Prepend(CPPPATH=["#thirdparty/icu4c/"]) else: thirdparty_sources += ["icu_data/icudata_stub.cpp"] diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index d1cd37b4817..466f4607702 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -460,13 +460,13 @@ bool TextServerAdvanced::_load_support_data(const String &p_filename) { } err = U_ZERO_ERROR; + icu_data_loaded = true; } u_init(&err); if (U_FAILURE(err)) { ERR_FAIL_V_MSG(false, u_errorName(err)); } - icu_data_loaded = true; } #endif return true; @@ -4775,17 +4775,24 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S } } + Vector bidi_ranges; + if (p_sd->bidi_override.is_empty()) { + bidi_ranges.push_back(Vector3i(p_sd->start, p_sd->end, DIRECTION_INHERITED)); + } else { + bidi_ranges = p_sd->bidi_override; + } + int sd_size = p_sd->glyphs.size(); const Glyph *sd_glyphs = p_sd->glyphs.ptr(); - for (int ov = 0; ov < p_sd->bidi_override.size(); ov++) { + for (int ov = 0; ov < bidi_ranges.size(); ov++) { UErrorCode err = U_ZERO_ERROR; - if (p_sd->bidi_override[ov].x >= p_start + p_length || p_sd->bidi_override[ov].y <= p_start) { + if (bidi_ranges[ov].x >= p_start + p_length || bidi_ranges[ov].y <= p_start) { continue; } - int ov_start = _convert_pos_inv(p_sd, p_sd->bidi_override[ov].x); + int ov_start = _convert_pos_inv(p_sd, bidi_ranges[ov].x); int start = MAX(0, _convert_pos_inv(p_sd, p_start) - ov_start); - int end = MIN(_convert_pos_inv(p_sd, p_start + p_length), _convert_pos_inv(p_sd, p_sd->bidi_override[ov].y)) - ov_start; + int end = MIN(_convert_pos_inv(p_sd, p_start + p_length), _convert_pos_inv(p_sd, bidi_ranges[ov].y)) - ov_start; ERR_FAIL_COND_V_MSG((start < 0 || end - start > p_new_sd->utf16.length()), false, "Invalid BiDi override range."); @@ -4799,7 +4806,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S // Line BiDi failed (string contains incompatible control characters), try full paragraph BiDi instead. err = U_ZERO_ERROR; const UChar *data = p_sd->utf16.get_data(); - switch (static_cast(p_sd->bidi_override[ov].z)) { + switch (static_cast(bidi_ranges[ov].z)) { case DIRECTION_LTR: { ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_LTR, nullptr, &err); } break; @@ -6492,14 +6499,17 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { } break; } + Vector bidi_ranges; if (sd->bidi_override.is_empty()) { - sd->bidi_override.push_back(Vector3i(sd->start, sd->end, DIRECTION_INHERITED)); + bidi_ranges.push_back(Vector3i(sd->start, sd->end, DIRECTION_INHERITED)); + } else { + bidi_ranges = sd->bidi_override; } - for (int ov = 0; ov < sd->bidi_override.size(); ov++) { + for (int ov = 0; ov < bidi_ranges.size(); ov++) { // Create BiDi iterator. - int start = _convert_pos_inv(sd, sd->bidi_override[ov].x - sd->start); - int end = _convert_pos_inv(sd, sd->bidi_override[ov].y - sd->start); + int start = _convert_pos_inv(sd, bidi_ranges[ov].x - sd->start); + int end = _convert_pos_inv(sd, bidi_ranges[ov].y - sd->start); if (start < 0 || end - start > sd->utf16.length()) { continue; @@ -6508,7 +6518,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { UErrorCode err = U_ZERO_ERROR; UBiDi *bidi_iter = ubidi_openSized(end - start, 0, &err); if (U_SUCCESS(err)) { - switch (static_cast(sd->bidi_override[ov].z)) { + switch (static_cast(bidi_ranges[ov].z)) { case DIRECTION_LTR: { ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_LTR, nullptr, &err); } break; diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 5cd3fc2f2e9..0deb0e47974 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -2842,6 +2842,13 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Refget("screen/immersive_mode"); diff --git a/platform/android/java/editor/src/android/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/android/java/org/godotengine/editor/GodotEditor.kt index 049dc508bc8..bb2e815ba8a 100644 --- a/platform/android/java/editor/src/android/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/android/java/org/godotengine/editor/GodotEditor.kt @@ -37,5 +37,4 @@ package org.redotengine.editor * * This is the implementation of the editor used when running on regular Android devices. */ -open class GodotEditor : BaseGodotEditor() { -} +open class GodotEditor : BaseGodotEditor() diff --git a/platform/android/java/editor/src/horizonos/AndroidManifest.xml b/platform/android/java/editor/src/horizonos/AndroidManifest.xml index 68ba11fd843..3f2b87e0485 100644 --- a/platform/android/java/editor/src/horizonos/AndroidManifest.xml +++ b/platform/android/java/editor/src/horizonos/AndroidManifest.xml @@ -57,15 +57,8 @@ + tools:node="merge"> diff --git a/platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotEditor.kt index b2a1b4ee653..eeb78cc5fe3 100644 --- a/platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotEditor.kt @@ -32,9 +32,6 @@ package org.redotengine.editor -import org.redotengine.godot.GodotLib -import org.redotengine.godot.utils.isNativeXRDevice - /** * Primary window of the Godot Editor. * @@ -42,66 +39,10 @@ import org.redotengine.godot.utils.isNativeXRDevice */ open class GodotEditor : BaseGodotEditor() { - companion object { - private val TAG = GodotEditor::class.java.simpleName - - /** Default behavior, means we check project settings **/ - private const val XR_MODE_DEFAULT = "default" - - /** - * Ignore project settings, OpenXR is disabled - */ - private const val XR_MODE_OFF = "off" - - /** - * Ignore project settings, OpenXR is enabled - */ - private const val XR_MODE_ON = "on" - - internal val XR_RUN_GAME_INFO = EditorWindowInfo(GodotXRGame::class.java, 1667, ":GodotXRGame") - - internal val USE_SCENE_PERMISSIONS = listOf("com.oculus.permission.USE_SCENE", "horizonos.permission.USE_SCENE") - } - - override fun getExcludedPermissions(): MutableSet { - val excludedPermissions = super.getExcludedPermissions() - // The USE_SCENE permission is requested when the "xr/openxr/enabled" project setting - // is enabled. - excludedPermissions.addAll(USE_SCENE_PERMISSIONS) - return excludedPermissions - } - - override fun retrieveEditorWindowInfo(args: Array): EditorWindowInfo { - var hasEditor = false - var xrMode = XR_MODE_DEFAULT - - var i = 0 - while (i < args.size) { - when (args[i++]) { - EDITOR_ARG, EDITOR_ARG_SHORT, EDITOR_PROJECT_MANAGER_ARG, EDITOR_PROJECT_MANAGER_ARG_SHORT -> hasEditor = true - XR_MODE_ARG -> { - xrMode = args[i++] - } - } - } - - return if (hasEditor) { - EDITOR_MAIN_INFO - } else { - val openxrEnabled = xrMode == XR_MODE_ON || - (xrMode == XR_MODE_DEFAULT && GodotLib.getGlobal("xr/openxr/enabled").toBoolean()) - if (openxrEnabled && isNativeXRDevice()) { - XR_RUN_GAME_INFO - } else { - RUN_GAME_INFO - } - } - } - - override fun getEditorWindowInfoForInstanceId(instanceId: Int): EditorWindowInfo? { - return when (instanceId) { - XR_RUN_GAME_INFO.windowId -> XR_RUN_GAME_INFO - else -> super.getEditorWindowInfoForInstanceId(instanceId) - } + override fun getXRRuntimePermissions(): MutableSet { + val xrRuntimePermissions = super.getXRRuntimePermissions() + xrRuntimePermissions.add("com.oculus.permission.USE_SCENE") + xrRuntimePermissions.add("horizonos.permission.USE_SCENE") + return xrRuntimePermissions } } diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml index f67c34e4485..78ccc9ca96f 100644 --- a/platform/android/java/editor/src/main/AndroidManifest.xml +++ b/platform/android/java/editor/src/main/AndroidManifest.xml @@ -71,6 +71,18 @@ android:defaultWidth="@dimen/editor_default_window_width" android:defaultHeight="@dimen/editor_default_window_height" /> + + diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt index 706cfd9d008..b166cf1d361 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt @@ -49,13 +49,12 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.window.layout.WindowMetricsCalculator import org.redotengine.editor.utils.signApk import org.redotengine.editor.utils.verifyApk +import org.redotengine.godot.BuildConfig import org.redotengine.godot.GodotActivity import org.redotengine.godot.GodotLib import org.redotengine.godot.error.Error import org.redotengine.godot.utils.PermissionsUtil import org.redotengine.godot.utils.ProcessPhoenix -import org.redotengine.godot.utils.isHorizonOSDevice -import org.redotengine.godot.utils.isPicoOSDevice import org.redotengine.godot.utils.isNativeXRDevice import java.util.* import kotlin.math.min @@ -95,6 +94,20 @@ abstract class BaseGodotEditor : GodotActivity() { // Info for the various classes used by the editor internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "") internal val RUN_GAME_INFO = EditorWindowInfo(GodotGame::class.java, 667, ":GodotGame", LaunchPolicy.AUTO, true) + internal val XR_RUN_GAME_INFO = EditorWindowInfo(GodotXRGame::class.java, 1667, ":GodotXRGame") + + /** Default behavior, means we check project settings **/ + private const val XR_MODE_DEFAULT = "default" + + /** + * Ignore project settings, OpenXR is disabled + */ + private const val XR_MODE_OFF = "off" + + /** + * Ignore project settings, OpenXR is enabled + */ + private const val XR_MODE_ON = "on" /** * Sets of constants to specify the window to use to run the project. @@ -131,8 +144,7 @@ abstract class BaseGodotEditor : GodotActivity() { * * The permissions in this set will be requested on demand based on use cases. */ - @CallSuper - protected open fun getExcludedPermissions(): MutableSet { + private fun getExcludedPermissions(): MutableSet { val excludedPermissions = mutableSetOf( // The RECORD_AUDIO permission is requested when the "audio/driver/enable_input" project // setting is enabled. @@ -146,9 +158,21 @@ abstract class BaseGodotEditor : GodotActivity() { Manifest.permission.REQUEST_INSTALL_PACKAGES, ) } + + // XR runtime permissions should only be requested when the "xr/openxr/enabled" project setting + // is enabled. + excludedPermissions.addAll(getXRRuntimePermissions()) return excludedPermissions } + /** + * Set of permissions to request when the "xr/openxr/enabled" project setting is enabled. + */ + @CallSuper + protected open fun getXRRuntimePermissions(): MutableSet { + return mutableSetOf() + } + override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() @@ -210,27 +234,38 @@ abstract class BaseGodotEditor : GodotActivity() { final override fun getCommandLine() = commandLineParams - protected open fun retrieveEditorWindowInfo(args: Array): EditorWindowInfo { + protected fun retrieveEditorWindowInfo(args: Array): EditorWindowInfo { var hasEditor = false + var xrMode = XR_MODE_DEFAULT var i = 0 while (i < args.size) { when (args[i++]) { EDITOR_ARG, EDITOR_ARG_SHORT, EDITOR_PROJECT_MANAGER_ARG, EDITOR_PROJECT_MANAGER_ARG_SHORT -> hasEditor = true + XR_MODE_ARG -> { + xrMode = args[i++] + } } } return if (hasEditor) { EDITOR_MAIN_INFO } else { - RUN_GAME_INFO + val openxrEnabled = xrMode == XR_MODE_ON || + (xrMode == XR_MODE_DEFAULT && GodotLib.getGlobal("xr/openxr/enabled").toBoolean()) + if (openxrEnabled && isNativeXRDevice(applicationContext)) { + XR_RUN_GAME_INFO + } else { + RUN_GAME_INFO + } } } - protected open fun getEditorWindowInfoForInstanceId(instanceId: Int): EditorWindowInfo? { + private fun getEditorWindowInfoForInstanceId(instanceId: Int): EditorWindowInfo? { return when (instanceId) { RUN_GAME_INFO.windowId -> RUN_GAME_INFO EDITOR_MAIN_INFO.windowId -> EDITOR_MAIN_INFO + XR_RUN_GAME_INFO.windowId -> XR_RUN_GAME_INFO else -> null } } @@ -419,8 +454,8 @@ abstract class BaseGodotEditor : GodotActivity() { return when (policy) { LaunchPolicy.AUTO -> { - if (isHorizonOSDevice()) { - // Horizon OS UX is more desktop-like and has support for launching adjacent + if (isNativeXRDevice(applicationContext)) { + // Native XR devices are more desktop-like and have support for launching adjacent // windows. So we always want to launch in adjacent mode when auto is selected. LaunchPolicy.ADJACENT } else { @@ -452,12 +487,6 @@ abstract class BaseGodotEditor : GodotActivity() { * Returns true the if the device supports picture-in-picture (PiP) */ protected open fun hasPiPSystemFeature(): Boolean { - if (isNativeXRDevice()) { - // Known native XR devices do not support PiP. - // Will need to revisit as they update their OS. - return false - } - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) } @@ -536,15 +565,15 @@ abstract class BaseGodotEditor : GodotActivity() { override fun supportsFeature(featureTag: String): Boolean { if (featureTag == "xr_editor") { - return isNativeXRDevice() + return isNativeXRDevice(applicationContext) } if (featureTag == "horizonos") { - return isHorizonOSDevice() + return BuildConfig.FLAVOR == "horizonos" } if (featureTag == "picoos") { - return isPicoOSDevice() + return BuildConfig.FLAVOR == "picoos" } return false diff --git a/platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotXRGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotXRGame.kt similarity index 95% rename from platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotXRGame.kt rename to platform/android/java/editor/src/main/java/org/godotengine/editor/GodotXRGame.kt index 00626a3fa32..26f589f3471 100644 --- a/platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotXRGame.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotXRGame.kt @@ -61,8 +61,8 @@ open class GodotXRGame: GodotGame() { override fun getProjectPermissionsToEnable(): MutableList { val permissionsToEnable = super.getProjectPermissionsToEnable() - val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean() - if (openxrEnabled) { + val xrRuntimePermission = getXRRuntimePermissions() + if (xrRuntimePermission.isNotEmpty() && GodotLib.getGlobal("xr/openxr/enabled").toBoolean()) { // We only request permissions when the `automatically_request_runtime_permissions` // project setting is enabled. // If the project setting is not defined, we fall-back to the default behavior which is @@ -71,7 +71,7 @@ open class GodotXRGame: GodotGame() { val automaticPermissionsRequestEnabled = automaticallyRequestPermissionsSetting.isNullOrEmpty() || automaticallyRequestPermissionsSetting.toBoolean() if (automaticPermissionsRequestEnabled) { - permissionsToEnable.addAll(USE_SCENE_PERMISSIONS) + permissionsToEnable.addAll(xrRuntimePermission) } } diff --git a/platform/android/java/editor/src/main/res/values/dimens.xml b/platform/android/java/editor/src/main/res/values/dimens.xml index 1e486872e6f..7e999dcce3d 100644 --- a/platform/android/java/editor/src/main/res/values/dimens.xml +++ b/platform/android/java/editor/src/main/res/values/dimens.xml @@ -1,5 +1,5 @@ - 640dp + 720dp 1024dp diff --git a/platform/android/java/editor/src/picoos/AndroidManifest.xml b/platform/android/java/editor/src/picoos/AndroidManifest.xml index 13ab40f90fc..53a13c09d2d 100644 --- a/platform/android/java/editor/src/picoos/AndroidManifest.xml +++ b/platform/android/java/editor/src/picoos/AndroidManifest.xml @@ -29,15 +29,8 @@ + tools:node="merge"> diff --git a/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotEditor.kt index ee08c3bb1d4..eff12068c0d 100644 --- a/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotEditor.kt @@ -30,53 +30,9 @@ package org.redotengine.editor -import org.redotengine.godot.GodotLib -import org.redotengine.godot.utils.isNativeXRDevice - /** * Primary window of the Godot Editor. * * This is the implementation of the editor used when running on PicoOS devices. */ -open class GodotEditor : BaseGodotEditor() { - - companion object { - private val TAG = GodotEditor::class.java.simpleName - - internal val XR_RUN_GAME_INFO = EditorWindowInfo(GodotXRGame::class.java, 1667, ":GodotXRGame") - } - - override fun retrieveEditorWindowInfo(args: Array): EditorWindowInfo { - var hasEditor = false - var xrModeOn = false - - var i = 0 - while (i < args.size) { - when (args[i++]) { - EDITOR_ARG, EDITOR_ARG_SHORT, EDITOR_PROJECT_MANAGER_ARG, EDITOR_PROJECT_MANAGER_ARG_SHORT -> hasEditor = true - XR_MODE_ARG -> { - val argValue = args[i++] - xrModeOn = xrModeOn || ("on" == argValue) - } - } - } - - return if (hasEditor) { - EDITOR_MAIN_INFO - } else { - val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean() - if (openxrEnabled && isNativeXRDevice()) { - XR_RUN_GAME_INFO - } else { - RUN_GAME_INFO - } - } - } - - override fun getEditorWindowInfoForInstanceId(instanceId: Int): EditorWindowInfo? { - return when (instanceId) { - XR_RUN_GAME_INFO.windowId -> XR_RUN_GAME_INFO - else -> super.getEditorWindowInfoForInstanceId(instanceId) - } - } -} +open class GodotEditor : BaseGodotEditor() diff --git a/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotXRGame.kt b/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotXRGame.kt deleted file mode 100644 index 6108b994e58..00000000000 --- a/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotXRGame.kt +++ /dev/null @@ -1,59 +0,0 @@ -/*************************************************************************/ -/* GodotXRGame.kt */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -package org.redotengine.editor - -import org.redotengine.godot.GodotLib -import org.redotengine.godot.xr.XRMode - -/** - * Provide support for running XR apps / games from the editor window. - */ -open class GodotXRGame: GodotGame() { - - override fun overrideOrientationRequest() = true - - override fun updateCommandLineParams(args: List) { - val updatedArgs = ArrayList() - if (!args.contains(XRMode.OPENXR.cmdLineArg)) { - updatedArgs.add(XRMode.OPENXR.cmdLineArg) - } - if (!args.contains(XR_MODE_ARG)) { - updatedArgs.add(XR_MODE_ARG) - updatedArgs.add("on") - } - updatedArgs.addAll(args) - - super.updateCommandLineParams(updatedArgs) - } - - override fun getEditorWindowInfo() = XR_RUN_GAME_INFO - -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java index 9f60a2bac26..201ffb2fc89 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -70,7 +70,7 @@ public interface GodotRenderView { * @return true if pointer capture is supported. */ default boolean canCapturePointer() { - // Pointer capture is not supported on Horizon OS - return !DeviceUtils.isHorizonOSDevice() && getInputHandler().canCapturePointer(); + // Pointer capture is not supported on native XR devices. + return !DeviceUtils.isNativeXRDevice(getView().getContext()) && getInputHandler().canCapturePointer(); } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt index b93e42df214..32c0e067587 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt @@ -37,13 +37,14 @@ package org.redotengine.godot.utils +import android.content.Context import android.os.Build /** * Returns true if running on Meta Horizon OS. */ -fun isHorizonOSDevice(): Boolean { - return "Oculus".equals(Build.BRAND, true) +fun isHorizonOSDevice(context: Context): Boolean { + return context.packageManager.hasSystemFeature("oculus.hardware.standalone_vr") } /** @@ -56,6 +57,6 @@ fun isPicoOSDevice(): Boolean { /** * Returns true if running on a native Android XR device. */ -fun isNativeXRDevice(): Boolean { - return isHorizonOSDevice() || isPicoOSDevice() +fun isNativeXRDevice(context: Context): Boolean { + return isHorizonOSDevice(context) || isPicoOSDevice() } diff --git a/platform/linuxbsd/wayland/SCsub b/platform/linuxbsd/wayland/SCsub index c5d8c00c6d7..4cceb49977a 100644 --- a/platform/linuxbsd/wayland/SCsub +++ b/platform/linuxbsd/wayland/SCsub @@ -11,16 +11,16 @@ if env["use_sowrap"]: WAYLAND_BUILDERS_SOWRAP = { "WAYLAND_API_HEADER": Builder( - action=Action( - r"wayland-scanner -c client-header < ${SOURCE} | sed 's:wayland-client-core\.h:../dynwrappers/wayland-client-core-so_wrap\.h:' > ${TARGET}", - 'Generating Wayland client header: "${TARGET}"', + action=env.Run( + r"wayland-scanner -c client-header < ${SOURCE} | " + r"sed 's:wayland-client-core\.h:../dynwrappers/wayland-client-core-so_wrap\.h:' > ${TARGET}", ), single_source=True, ), "WAYLAND_API_CODE": Builder( - action=Action( - r"wayland-scanner -c private-code < ${SOURCE} | sed 's:wayland-util\.h:../dynwrappers/wayland-client-core-so_wrap\.h:' > ${TARGET}", - 'Generating Wayland protocol marshaling code: "${TARGET}"', + action=env.Run( + r"wayland-scanner -c private-code < ${SOURCE} | " + r"sed 's:wayland-util\.h:../dynwrappers/wayland-client-core-so_wrap\.h:' > ${TARGET}", ), single_source=True, ), @@ -29,168 +29,131 @@ if env["use_sowrap"]: else: WAYLAND_BUILDERS = { "WAYLAND_API_HEADER": Builder( - action=Action( - r"wayland-scanner -c client-header < ${SOURCE} > ${TARGET}", - 'Generating Wayland client header: "${TARGET}"', - ), + action=env.Run(r"wayland-scanner -c client-header < ${SOURCE} > ${TARGET}"), single_source=True, ), "WAYLAND_API_CODE": Builder( - action=Action( - r"wayland-scanner -c private-code < ${SOURCE} > ${TARGET}", - 'Generating Wayland protocol marshaling code: "${TARGET}"', - ), + action=env.Run(r"wayland-scanner -c private-code < ${SOURCE} > ${TARGET}"), single_source=True, ), } env.Append(BUILDERS=WAYLAND_BUILDERS) -env.WAYLAND_API_HEADER(target="protocol/wayland.gen.h", source="#thirdparty/wayland/protocol/wayland.xml") -env.WAYLAND_API_CODE(target="protocol/wayland.gen.c", source="#thirdparty/wayland/protocol/wayland.xml") - -env.WAYLAND_API_HEADER( - target="protocol/viewporter.gen.h", source="#thirdparty/wayland-protocols/stable/viewporter/viewporter.xml" -) -env.WAYLAND_API_CODE( - target="protocol/viewporter.gen.c", source="#thirdparty/wayland-protocols/stable/viewporter/viewporter.xml" -) - -env.WAYLAND_API_HEADER( - target="protocol/fractional_scale.gen.h", - source="#thirdparty/wayland-protocols/staging/fractional-scale/fractional-scale-v1.xml", -) -env.WAYLAND_API_CODE( - target="protocol/fractional_scale.gen.c", - source="#thirdparty/wayland-protocols/staging/fractional-scale/fractional-scale-v1.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/xdg_shell.gen.h", source="#thirdparty/wayland-protocols/stable/xdg-shell/xdg-shell.xml" -) - -env.WAYLAND_API_CODE( - target="protocol/xdg_shell.gen.c", source="#thirdparty/wayland-protocols/stable/xdg-shell/xdg-shell.xml" -) - -env.WAYLAND_API_HEADER( - target="protocol/xdg_decoration.gen.h", - source="#thirdparty/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml", -) - -env.WAYLAND_API_CODE( - target="protocol/xdg_decoration.gen.c", - source="#thirdparty/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/xdg_activation.gen.h", - source="#thirdparty/wayland-protocols/staging/xdg-activation/xdg-activation-v1.xml", -) - -env.WAYLAND_API_CODE( - target="protocol/xdg_activation.gen.c", - source="#thirdparty/wayland-protocols/staging/xdg-activation/xdg-activation-v1.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/relative_pointer.gen.h", - source="#thirdparty/wayland-protocols/unstable/relative-pointer/relative-pointer-unstable-v1.xml", -) - -env.WAYLAND_API_CODE( - target="protocol/relative_pointer.gen.c", - source="#thirdparty/wayland-protocols/unstable/relative-pointer/relative-pointer-unstable-v1.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/pointer_constraints.gen.h", - source="#thirdparty/wayland-protocols/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml", -) - -env.WAYLAND_API_CODE( - target="protocol/pointer_constraints.gen.c", - source="#thirdparty/wayland-protocols/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/pointer_gestures.gen.h", - source="#thirdparty/wayland-protocols/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml", +env.NoCache( + env.WAYLAND_API_HEADER("protocol/wayland.gen.h", "#thirdparty/wayland/protocol/wayland.xml"), + env.WAYLAND_API_CODE("protocol/wayland.gen.c", "#thirdparty/wayland/protocol/wayland.xml"), + env.WAYLAND_API_HEADER( + "protocol/viewporter.gen.h", "#thirdparty/wayland-protocols/stable/viewporter/viewporter.xml" + ), + env.WAYLAND_API_CODE("protocol/viewporter.gen.c", "#thirdparty/wayland-protocols/stable/viewporter/viewporter.xml"), + env.WAYLAND_API_HEADER( + "protocol/fractional_scale.gen.h", + "#thirdparty/wayland-protocols/staging/fractional-scale/fractional-scale-v1.xml", + ), + env.WAYLAND_API_CODE( + "protocol/fractional_scale.gen.c", + "#thirdparty/wayland-protocols/staging/fractional-scale/fractional-scale-v1.xml", + ), + env.WAYLAND_API_HEADER("protocol/xdg_shell.gen.h", "#thirdparty/wayland-protocols/stable/xdg-shell/xdg-shell.xml"), + env.WAYLAND_API_CODE("protocol/xdg_shell.gen.c", "#thirdparty/wayland-protocols/stable/xdg-shell/xdg-shell.xml"), + env.WAYLAND_API_HEADER( + "protocol/xdg_decoration.gen.h", + "#thirdparty/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml", + ), + env.WAYLAND_API_CODE( + "protocol/xdg_decoration.gen.c", + "#thirdparty/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml", + ), + env.WAYLAND_API_HEADER( + "protocol/xdg_activation.gen.h", + "#thirdparty/wayland-protocols/staging/xdg-activation/xdg-activation-v1.xml", + ), + env.WAYLAND_API_CODE( + "protocol/xdg_activation.gen.c", + "#thirdparty/wayland-protocols/staging/xdg-activation/xdg-activation-v1.xml", + ), + env.WAYLAND_API_HEADER( + "protocol/relative_pointer.gen.h", + "#thirdparty/wayland-protocols/unstable/relative-pointer/relative-pointer-unstable-v1.xml", + ), + env.WAYLAND_API_CODE( + "protocol/relative_pointer.gen.c", + "#thirdparty/wayland-protocols/unstable/relative-pointer/relative-pointer-unstable-v1.xml", + ), + env.WAYLAND_API_HEADER( + "protocol/pointer_constraints.gen.h", + "#thirdparty/wayland-protocols/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml", + ), + env.WAYLAND_API_CODE( + "protocol/pointer_constraints.gen.c", + "#thirdparty/wayland-protocols/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml", + ), + env.WAYLAND_API_HEADER( + "protocol/pointer_gestures.gen.h", + "#thirdparty/wayland-protocols/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml", + ), + env.WAYLAND_API_CODE( + "protocol/pointer_gestures.gen.c", + "#thirdparty/wayland-protocols/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml", + ), + env.WAYLAND_API_HEADER( + "protocol/primary_selection.gen.h", + "#thirdparty/wayland-protocols/unstable/primary-selection/primary-selection-unstable-v1.xml", + ), + env.WAYLAND_API_CODE( + "protocol/primary_selection.gen.c", + "#thirdparty/wayland-protocols/unstable/primary-selection/primary-selection-unstable-v1.xml", + ), + env.WAYLAND_API_HEADER( + "protocol/idle_inhibit.gen.h", + "#thirdparty/wayland-protocols/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml", + ), + env.WAYLAND_API_CODE( + "protocol/idle_inhibit.gen.c", + "#thirdparty/wayland-protocols/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml", + ), + env.WAYLAND_API_HEADER( + "protocol/tablet.gen.h", + "#thirdparty/wayland-protocols/unstable/tablet/tablet-unstable-v2.xml", + ), + env.WAYLAND_API_CODE( + "protocol/tablet.gen.c", + "#thirdparty/wayland-protocols/unstable/tablet/tablet-unstable-v2.xml", + ), + env.WAYLAND_API_HEADER( + "protocol/text_input.gen.h", + "#thirdparty/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml", + ), + env.WAYLAND_API_CODE( + "protocol/text_input.gen.c", + "#thirdparty/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml", + ), + env.WAYLAND_API_HEADER( + "protocol/xdg_foreign_v1.gen.h", + "#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml", + ), + env.WAYLAND_API_CODE( + "protocol/xdg_foreign_v1.gen.c", + "#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml", + ), + env.WAYLAND_API_HEADER( + "protocol/xdg_foreign_v2.gen.h", + "#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml", + ), + env.WAYLAND_API_CODE( + "protocol/xdg_foreign_v2.gen.c", + "#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml", + ), + env.WAYLAND_API_HEADER( + "protocol/xdg_system_bell.gen.h", + "#thirdparty/wayland-protocols/staging/xdg-system-bell/xdg-system-bell-v1.xml", + ), + env.WAYLAND_API_CODE( + "protocol/xdg_system_bell.gen.c", + "#thirdparty/wayland-protocols/staging/xdg-system-bell/xdg-system-bell-v1.xml", + ), ) -env.WAYLAND_API_CODE( - target="protocol/pointer_gestures.gen.c", - source="#thirdparty/wayland-protocols/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/primary_selection.gen.h", - source="#thirdparty/wayland-protocols/unstable/primary-selection/primary-selection-unstable-v1.xml", -) - -env.WAYLAND_API_CODE( - target="protocol/primary_selection.gen.c", - source="#thirdparty/wayland-protocols/unstable/primary-selection/primary-selection-unstable-v1.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/idle_inhibit.gen.h", - source="#thirdparty/wayland-protocols/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml", -) - -env.WAYLAND_API_CODE( - target="protocol/idle_inhibit.gen.c", - source="#thirdparty/wayland-protocols/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/tablet.gen.h", - source="#thirdparty/wayland-protocols/unstable/tablet/tablet-unstable-v2.xml", -) - -env.WAYLAND_API_CODE( - target="protocol/tablet.gen.c", - source="#thirdparty/wayland-protocols/unstable/tablet/tablet-unstable-v2.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/text_input.gen.h", - source="#thirdparty/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml", -) - -env.WAYLAND_API_CODE( - target="protocol/text_input.gen.c", - source="#thirdparty/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/xdg_foreign_v1.gen.h", - source="#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml", -) - -env.WAYLAND_API_CODE( - target="protocol/xdg_foreign_v1.gen.c", - source="#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/xdg_foreign_v2.gen.h", - source="#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml", -) - -env.WAYLAND_API_CODE( - target="protocol/xdg_foreign_v2.gen.c", - source="#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml", -) - -env.WAYLAND_API_HEADER( - target="protocol/xdg_system_bell.gen.h", - source="#thirdparty/wayland-protocols/staging/xdg-system-bell/xdg-system-bell-v1.xml", -) - -env.WAYLAND_API_CODE( - target="protocol/xdg_system_bell.gen.c", - source="#thirdparty/wayland-protocols/staging/xdg-system-bell/xdg-system-bell-v1.xml", -) source_files = [ "protocol/wayland.gen.c", diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index c6940fe0ea1..7a3de359a13 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -1225,10 +1225,12 @@ void DisplayServerWayland::process_events() { Ref ime_update_msg = msg; if (ime_update_msg.is_valid()) { - ime_text = ime_update_msg->text; - ime_selection = ime_update_msg->selection; + if (ime_text != ime_update_msg->text || ime_selection != ime_update_msg->selection) { + ime_text = ime_update_msg->text; + ime_selection = ime_update_msg->selection; - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); + } } } diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 670b9d37216..b35ad57f08c 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -3494,7 +3494,8 @@ bool WaylandThread::window_can_set_mode(DisplayServer::WindowID p_window_id, Dis return ws.can_maximize; }; - case DisplayServer::WINDOW_MODE_FULLSCREEN: { + case DisplayServer::WINDOW_MODE_FULLSCREEN: + case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: { #ifdef LIBDECOR_ENABLED if (ws.libdecor_frame) { return libdecor_frame_has_capability(ws.libdecor_frame, LIBDECOR_ACTION_FULLSCREEN); @@ -3503,13 +3504,6 @@ bool WaylandThread::window_can_set_mode(DisplayServer::WindowID p_window_id, Dis return ws.can_fullscreen; }; - - case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: { - // I'm not really sure but from what I can find Wayland doesn't really have - // the concept of exclusive fullscreen. - // TODO: Discuss whether to fallback to regular fullscreen or not. - return false; - }; } return false; diff --git a/scene/2d/mesh_instance_2d.cpp b/scene/2d/mesh_instance_2d.cpp index 86b807cbcfe..e2a2129bdea 100644 --- a/scene/2d/mesh_instance_2d.cpp +++ b/scene/2d/mesh_instance_2d.cpp @@ -32,6 +32,17 @@ #include "mesh_instance_2d.h" +#include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h" +#include "scene/resources/2d/navigation_polygon.h" +#include "scene/scene_string_names.h" +#include "servers/navigation_server_2d.h" + +#include "thirdparty/clipper2/include/clipper2/clipper.h" +#include "thirdparty/misc/polypartition.h" + +Callable MeshInstance2D::_navmesh_source_geometry_parsing_callback; +RID MeshInstance2D::_navmesh_source_geometry_parser; + void MeshInstance2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { @@ -108,5 +119,100 @@ bool MeshInstance2D::_edit_use_rect() const { } #endif // DEBUG_ENABLED +void MeshInstance2D::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer2D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&MeshInstance2D::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer2D::get_singleton()->source_geometry_parser_create(); + NavigationServer2D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void MeshInstance2D::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + MeshInstance2D *mesh_instance = Object::cast_to(p_node); + + if (mesh_instance == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + Ref mesh = mesh_instance->get_mesh(); + if (mesh.is_null()) { + return; + } + + const Transform2D mesh_instance_xform = p_source_geometry_data->root_node_transform * mesh_instance->get_global_transform(); + + using namespace Clipper2Lib; + + 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) { + continue; + } + + if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) { + continue; + } + + PathD subject_path; + + int index_count = 0; + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + index_count = mesh->surface_get_array_index_len(i); + } else { + index_count = mesh->surface_get_array_len(i); + } + + ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0)); + + Array a = mesh->surface_get_arrays(i); + + Vector mesh_vertices = a[Mesh::ARRAY_VERTEX]; + + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + Vector mesh_indices = a[Mesh::ARRAY_INDEX]; + for (int vertex_index : mesh_indices) { + const Vector2 &vertex = mesh_vertices[vertex_index]; + const PointD &point = PointD(vertex.x, vertex.y); + subject_path.push_back(point); + } + } else { + for (const Vector2 &vertex : mesh_vertices) { + const PointD &point = PointD(vertex.x, vertex.y); + subject_path.push_back(point); + } + } + subject_paths.push_back(subject_path); + } + + PathsD path_solution; + + path_solution = Union(subject_paths, dummy_clip_paths, FillRule::NonZero); + + //path_solution = RamerDouglasPeucker(path_solution, 0.025); + + Vector> polypaths; + + for (const PathD &scaled_path : path_solution) { + Vector shape_outline; + for (const PointD &scaled_point : scaled_path) { + shape_outline.push_back(Point2(static_cast(scaled_point.x), static_cast(scaled_point.y))); + } + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = mesh_instance_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } +} + MeshInstance2D::MeshInstance2D() { } diff --git a/scene/2d/mesh_instance_2d.h b/scene/2d/mesh_instance_2d.h index 1166dff4e07..cafdb4c4233 100644 --- a/scene/2d/mesh_instance_2d.h +++ b/scene/2d/mesh_instance_2d.h @@ -35,6 +35,9 @@ #include "scene/2d/node_2d.h" +class NavigationPolygon; +class NavigationMeshSourceGeometryData2D; + class MeshInstance2D : public Node2D { GDCLASS(MeshInstance2D, Node2D); @@ -58,6 +61,14 @@ class MeshInstance2D : public Node2D { void set_texture(const Ref &p_texture); Ref get_texture() const; +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + MeshInstance2D(); }; diff --git a/scene/2d/multimesh_instance_2d.cpp b/scene/2d/multimesh_instance_2d.cpp index 2ab5612cff4..67e2d091c0f 100644 --- a/scene/2d/multimesh_instance_2d.cpp +++ b/scene/2d/multimesh_instance_2d.cpp @@ -32,6 +32,17 @@ #include "multimesh_instance_2d.h" +#include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h" +#include "scene/resources/2d/navigation_polygon.h" +#include "scene/scene_string_names.h" +#include "servers/navigation_server_2d.h" + +#include "thirdparty/clipper2/include/clipper2/clipper.h" +#include "thirdparty/misc/polypartition.h" + +Callable MultiMeshInstance2D::_navmesh_source_geometry_parsing_callback; +RID MultiMeshInstance2D::_navmesh_source_geometry_parser; + void MultiMeshInstance2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { @@ -97,6 +108,110 @@ Rect2 MultiMeshInstance2D::_edit_get_rect() const { } #endif // DEBUG_ENABLED +void MultiMeshInstance2D::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer2D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&MultiMeshInstance2D::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer2D::get_singleton()->source_geometry_parser_create(); + NavigationServer2D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void MultiMeshInstance2D::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + MultiMeshInstance2D *multimesh_instance = Object::cast_to(p_node); + + if (multimesh_instance == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + Ref multimesh = multimesh_instance->get_multimesh(); + if (!(multimesh.is_valid() && multimesh->get_transform_format() == MultiMesh::TRANSFORM_2D)) { + return; + } + + Ref mesh = multimesh->get_mesh(); + if (mesh.is_null()) { + return; + } + + using namespace Clipper2Lib; + + 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) { + continue; + } + + if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) { + continue; + } + + PathD subject_path; + + int index_count = 0; + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + index_count = mesh->surface_get_array_index_len(i); + } else { + index_count = mesh->surface_get_array_len(i); + } + + ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0)); + + Array a = mesh->surface_get_arrays(i); + + Vector mesh_vertices = a[Mesh::ARRAY_VERTEX]; + + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + Vector mesh_indices = a[Mesh::ARRAY_INDEX]; + for (int vertex_index : mesh_indices) { + const Vector2 &vertex = mesh_vertices[vertex_index]; + const PointD &point = PointD(vertex.x, vertex.y); + subject_path.push_back(point); + } + } else { + for (const Vector2 &vertex : mesh_vertices) { + const PointD &point = PointD(vertex.x, vertex.y); + subject_path.push_back(point); + } + } + mesh_subject_paths.push_back(subject_path); + } + + PathsD mesh_path_solution = Union(mesh_subject_paths, dummy_clip_paths, FillRule::NonZero); + + //path_solution = RamerDouglasPeucker(path_solution, 0.025); + + int multimesh_instance_count = multimesh->get_visible_instance_count(); + if (multimesh_instance_count == -1) { + multimesh_instance_count = multimesh->get_instance_count(); + } + + const Transform2D multimesh_instance_xform = p_source_geometry_data->root_node_transform * multimesh_instance->get_global_transform(); + + 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 PathD &mesh_path : mesh_path_solution) { + Vector shape_outline; + + for (const PointD &mesh_path_point : mesh_path) { + shape_outline.push_back(Point2(static_cast(mesh_path_point.x), static_cast(mesh_path_point.y))); + } + + for (int j = 0; j < shape_outline.size(); j++) { + shape_outline.write[j] = multimesh_instance_mesh_instance_xform.xform(shape_outline[j]); + } + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + } +} + MultiMeshInstance2D::MultiMeshInstance2D() { } diff --git a/scene/2d/multimesh_instance_2d.h b/scene/2d/multimesh_instance_2d.h index dd3c6d2256b..ae68a27e92f 100644 --- a/scene/2d/multimesh_instance_2d.h +++ b/scene/2d/multimesh_instance_2d.h @@ -36,6 +36,9 @@ #include "scene/2d/node_2d.h" #include "scene/resources/multimesh.h" +class NavigationPolygon; +class NavigationMeshSourceGeometryData2D; + class MultiMeshInstance2D : public Node2D { GDCLASS(MultiMeshInstance2D, Node2D); @@ -58,6 +61,14 @@ class MultiMeshInstance2D : public Node2D { void set_texture(const Ref &p_texture); Ref get_texture() const; +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + MultiMeshInstance2D(); ~MultiMeshInstance2D(); }; diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index a74b5d616b8..0f2c60360a4 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -33,9 +33,14 @@ #include "navigation_obstacle_2d.h" #include "core/math/geometry_2d.h" +#include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h" +#include "scene/resources/2d/navigation_polygon.h" #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" +Callable NavigationObstacle2D::_navmesh_source_geometry_parsing_callback; +RID NavigationObstacle2D::_navmesh_source_geometry_parser; + void NavigationObstacle2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_rid"), &NavigationObstacle2D::get_rid); @@ -341,6 +346,75 @@ PackedStringArray NavigationObstacle2D::get_configuration_warnings() const { return warnings; } +void NavigationObstacle2D::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer2D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&NavigationObstacle2D::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer2D::get_singleton()->source_geometry_parser_create(); + NavigationServer2D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void NavigationObstacle2D::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + NavigationObstacle2D *obstacle = Object::cast_to(p_node); + + if (obstacle == nullptr) { + return; + } + + if (!obstacle->get_affect_navigation_mesh()) { + return; + } + + const Vector2 safe_scale = obstacle->get_global_scale().abs().maxf(0.001); + const float obstacle_radius = obstacle->get_radius(); + + if (obstacle_radius > 0.0) { + // Radius defined obstacle should be uniformly scaled from obstacle basis max scale axis. + const float scaling_max_value = safe_scale[safe_scale.max_axis_index()]; + const Vector2 uniform_max_scale = Vector2(scaling_max_value, scaling_max_value); + const Transform2D obstacle_circle_transform = p_source_geometry_data->root_node_transform * Transform2D(obstacle->get_global_rotation(), uniform_max_scale, 0.0, obstacle->get_global_position()); + + Vector obstruction_circle_vertices; + + // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding. + // Without, navigation paths can go directly through the middle of the obstacle and conflict with the avoidance to get agents stuck. + // No place for excessive "round" detail here. Every additional edge adds a high cost for something that needs to be quick, not pretty. + static const int circle_points = 12; + + obstruction_circle_vertices.resize(circle_points); + Vector2 *circle_vertices_ptrw = obstruction_circle_vertices.ptrw(); + const real_t circle_point_step = Math_TAU / circle_points; + + for (int i = 0; i < circle_points; i++) { + const float angle = i * circle_point_step; + circle_vertices_ptrw[i] = obstacle_circle_transform.xform(Vector2(Math::cos(angle) * obstacle_radius, Math::sin(angle) * obstacle_radius)); + } + + p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_carve_navigation_mesh()); + } + + // Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account. + const Transform2D node_xform = p_source_geometry_data->root_node_transform * obstacle->get_global_transform(); + + const Vector &obstacle_vertices = obstacle->get_vertices(); + + if (obstacle_vertices.is_empty()) { + return; + } + + Vector obstruction_shape_vertices; + obstruction_shape_vertices.resize(obstacle_vertices.size()); + + const Vector2 *obstacle_vertices_ptr = obstacle_vertices.ptr(); + Vector2 *obstruction_shape_vertices_ptrw = obstruction_shape_vertices.ptrw(); + + for (int i = 0; i < obstacle_vertices.size(); i++) { + obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]); + } + p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, obstacle->get_carve_navigation_mesh()); +} + void NavigationObstacle2D::_update_map(RID p_map) { map_current = p_map; NavigationServer2D::get_singleton()->obstacle_set_map(obstacle, p_map); diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h index 6a148dd3617..e9bffe3f9c6 100644 --- a/scene/2d/navigation_obstacle_2d.h +++ b/scene/2d/navigation_obstacle_2d.h @@ -35,6 +35,9 @@ #include "scene/2d/node_2d.h" +class NavigationPolygon; +class NavigationMeshSourceGeometryData2D; + class NavigationObstacle2D : public Node2D { GDCLASS(NavigationObstacle2D, Node2D); @@ -117,6 +120,14 @@ class NavigationObstacle2D : public Node2D { PackedStringArray get_configuration_warnings() const override; +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + private: void _update_map(RID p_map); void _update_position(const Vector2 p_position); diff --git a/scene/2d/physics/static_body_2d.cpp b/scene/2d/physics/static_body_2d.cpp index 5d883a3f002..2494d8daae7 100644 --- a/scene/2d/physics/static_body_2d.cpp +++ b/scene/2d/physics/static_body_2d.cpp @@ -32,6 +32,18 @@ #include "static_body_2d.h" +#include "scene/resources/2d/capsule_shape_2d.h" +#include "scene/resources/2d/circle_shape_2d.h" +#include "scene/resources/2d/concave_polygon_shape_2d.h" +#include "scene/resources/2d/convex_polygon_shape_2d.h" +#include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h" +#include "scene/resources/2d/navigation_polygon.h" +#include "scene/resources/2d/rectangle_shape_2d.h" +#include "servers/navigation_server_2d.h" + +Callable StaticBody2D::_navmesh_source_geometry_parsing_callback; +RID StaticBody2D::_navmesh_source_geometry_parser; + void StaticBody2D::set_constant_linear_velocity(const Vector2 &p_vel) { constant_linear_velocity = p_vel; @@ -79,6 +91,131 @@ void StaticBody2D::_reload_physics_characteristics() { } } +void StaticBody2D::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer2D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&StaticBody2D::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer2D::get_singleton()->source_geometry_parser_create(); + NavigationServer2D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void StaticBody2D::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + StaticBody2D *static_body = Object::cast_to(p_node); + + if (static_body == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask(); + if (!(static_body->get_collision_layer() & parsed_collision_mask)) { + return; + } + + List shape_owners; + static_body->get_shape_owners(&shape_owners); + + for (uint32_t shape_owner : shape_owners) { + if (static_body->is_shape_owner_disabled(shape_owner)) { + continue; + } + + const int shape_count = static_body->shape_owner_get_shape_count(shape_owner); + + for (int shape_index = 0; shape_index < shape_count; shape_index++) { + Ref s = static_body->shape_owner_get_shape(shape_owner, shape_index); + + if (s.is_null()) { + continue; + } + + const Transform2D static_body_xform = p_source_geometry_data->root_node_transform * static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner); + + RectangleShape2D *rectangle_shape = Object::cast_to(*s); + if (rectangle_shape) { + Vector shape_outline; + + const Vector2 &rectangle_size = rectangle_shape->get_size(); + + shape_outline.resize(5); + shape_outline.write[0] = static_body_xform.xform(-rectangle_size * 0.5); + shape_outline.write[1] = static_body_xform.xform(Vector2(rectangle_size.x, -rectangle_size.y) * 0.5); + shape_outline.write[2] = static_body_xform.xform(rectangle_size * 0.5); + shape_outline.write[3] = static_body_xform.xform(Vector2(-rectangle_size.x, rectangle_size.y) * 0.5); + shape_outline.write[4] = static_body_xform.xform(-rectangle_size * 0.5); + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + CapsuleShape2D *capsule_shape = Object::cast_to(*s); + if (capsule_shape) { + const real_t capsule_height = capsule_shape->get_height(); + const real_t capsule_radius = capsule_shape->get_radius(); + + Vector shape_outline; + const real_t turn_step = Math_TAU / 12.0; + shape_outline.resize(14); + int shape_outline_inx = 0; + for (int i = 0; i < 12; i++) { + Vector2 ofs = Vector2(0, (i > 3 && i <= 9) ? -capsule_height * 0.5 + capsule_radius : capsule_height * 0.5 - capsule_radius); + + shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius + ofs); + shape_outline_inx += 1; + if (i == 3 || i == 9) { + shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius - ofs); + shape_outline_inx += 1; + } + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + CircleShape2D *circle_shape = Object::cast_to(*s); + if (circle_shape) { + const real_t circle_radius = circle_shape->get_radius(); + + Vector shape_outline; + int circle_edge_count = 12; + shape_outline.resize(circle_edge_count); + + const real_t turn_step = Math_TAU / real_t(circle_edge_count); + for (int i = 0; i < circle_edge_count; i++) { + shape_outline.write[i] = static_body_xform.xform(Vector2(Math::cos(i * turn_step), Math::sin(i * turn_step)) * circle_radius); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + ConcavePolygonShape2D *concave_polygon_shape = Object::cast_to(*s); + if (concave_polygon_shape) { + Vector shape_outline = concave_polygon_shape->get_segments(); + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = static_body_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + ConvexPolygonShape2D *convex_polygon_shape = Object::cast_to(*s); + if (convex_polygon_shape) { + Vector shape_outline = convex_polygon_shape->get_points(); + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = static_body_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + } + } +} + void StaticBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody2D::set_constant_linear_velocity); ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody2D::set_constant_angular_velocity); diff --git a/scene/2d/physics/static_body_2d.h b/scene/2d/physics/static_body_2d.h index 974829bf5ac..a8de77a6d16 100644 --- a/scene/2d/physics/static_body_2d.h +++ b/scene/2d/physics/static_body_2d.h @@ -35,6 +35,9 @@ #include "scene/2d/physics/physics_body_2d.h" +class NavigationPolygon; +class NavigationMeshSourceGeometryData2D; + class StaticBody2D : public PhysicsBody2D { GDCLASS(StaticBody2D, PhysicsBody2D); @@ -59,6 +62,14 @@ class StaticBody2D : public PhysicsBody2D { StaticBody2D(PhysicsServer2D::BodyMode p_mode = PhysicsServer2D::BODY_MODE_STATIC); +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + private: void _reload_physics_characteristics(); }; diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index a2026179a60..f318769bb9a 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -33,8 +33,14 @@ #include "polygon_2d.h" #include "core/math/geometry_2d.h" +#include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h" +#include "scene/resources/2d/navigation_polygon.h" +#include "servers/navigation_server_2d.h" #include "skeleton_2d.h" +Callable Polygon2D::_navmesh_source_geometry_parsing_callback; +RID Polygon2D::_navmesh_source_geometry_parser; + #ifdef TOOLS_ENABLED Dictionary Polygon2D::_edit_get_state() const { Dictionary state = Node2D::_edit_get_state(); @@ -606,6 +612,36 @@ NodePath Polygon2D::get_skeleton() const { return skeleton; } +void Polygon2D::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer2D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&Polygon2D::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer2D::get_singleton()->source_geometry_parser_create(); + NavigationServer2D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void Polygon2D::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + Polygon2D *polygon_2d = Object::cast_to(p_node); + + if (polygon_2d == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + + if (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) { + const Transform2D polygon_2d_xform = p_source_geometry_data->root_node_transform * polygon_2d->get_global_transform(); + + Vector shape_outline = polygon_2d->get_polygon(); + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = polygon_2d_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } +} + void Polygon2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &Polygon2D::set_polygon); ClassDB::bind_method(D_METHOD("get_polygon"), &Polygon2D::get_polygon); diff --git a/scene/2d/polygon_2d.h b/scene/2d/polygon_2d.h index d5fd5b772b5..50412492c8d 100644 --- a/scene/2d/polygon_2d.h +++ b/scene/2d/polygon_2d.h @@ -35,6 +35,9 @@ #include "scene/2d/node_2d.h" +class NavigationPolygon; +class NavigationMeshSourceGeometryData2D; + class Polygon2D : public Node2D { GDCLASS(Polygon2D, Node2D); @@ -152,6 +155,14 @@ class Polygon2D : public Node2D { void set_skeleton(const NodePath &p_skeleton); NodePath get_skeleton() const; +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + Polygon2D(); ~Polygon2D(); }; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 33c1b0cce7c..8a2e82afbda 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -34,6 +34,8 @@ #include "tile_map.compat.inc" #include "core/io/marshalls.h" +#include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h" +#include "servers/navigation_server_2d.h" #define TILEMAP_CALL_FOR_LAYER(layer, function, ...) \ if (layer < 0) { \ @@ -49,6 +51,9 @@ ERR_FAIL_INDEX_V(layer, (int)layers.size(), err_value); \ return layers[layer]->function(__VA_ARGS__); +Callable TileMap::_navmesh_source_geometry_parsing_callback; +RID TileMap::_navmesh_source_geometry_parser; + void TileMap::_tile_set_changed() { update_configuration_warnings(); } @@ -1024,5 +1029,33 @@ TileMap::TileMap() { property_helper.setup_for_instance(base_property_helper, this); } +void TileMap::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer2D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&TileMap::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer2D::get_singleton()->source_geometry_parser_create(); + NavigationServer2D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void TileMap::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + TileMap *nb_tilemap = Object::cast_to(p_node); + + if (nb_tilemap == nullptr) { + return; + } + + // Special case for TileMap, so that internal layer get parsed even if p_recurse_children is false. + bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationPolygon::SOURCE_GEOMETRY_GROUPS_EXPLICIT; + if (!recurse_children) { + for (int i = 0; i < p_node->get_child_count(); i++) { + TileMapLayer *tile_map_layer = Object::cast_to(p_node->get_child(i)); + if (tile_map_layer && tile_map_layer->get_index_in_tile_map() >= 0) { + tile_map_layer->navmesh_parse_source_geometry(p_navigation_mesh, p_source_geometry_data, tile_map_layer); + } + } + } +} + #undef TILEMAP_CALL_FOR_LAYER #undef TILEMAP_CALL_FOR_LAYER_V diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 7783eba5088..c65f347ca62 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -38,6 +38,7 @@ #include "scene/resources/2d/tile_set.h" class Control; +class NavigationMeshSourceGeometryData2D; class TileMapLayer; class TerrainConstraint; @@ -240,6 +241,14 @@ class TileMap : public Node2D { // Configuration warnings. PackedStringArray get_configuration_warnings() const override; +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + TileMap(); }; diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index 0c99fcd7042..d91ef1bd6ec 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -35,9 +35,13 @@ #include "core/io/marshalls.h" #include "scene/2d/tile_map.h" #include "scene/gui/control.h" +#include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h" #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" +Callable TileMapLayer::_navmesh_source_geometry_parsing_callback; +RID TileMapLayer::_navmesh_source_geometry_parser; + #ifdef DEBUG_ENABLED /////////////////////////////// Debug ////////////////////////////////////////// constexpr int TILE_MAP_DEBUG_QUADRANT_SIZE = 16; @@ -3067,6 +3071,114 @@ TileMapLayer::DebugVisibilityMode TileMapLayer::get_navigation_visibility_mode() return navigation_visibility_mode; } +void TileMapLayer::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer2D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&TileMapLayer::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer2D::get_singleton()->source_geometry_parser_create(); + NavigationServer2D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void TileMapLayer::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + TileMapLayer *tile_map_layer = Object::cast_to(p_node); + + if (tile_map_layer == nullptr) { + return; + } + + Ref tile_set = tile_map_layer->get_tile_set(); + if (tile_set.is_null()) { + return; + } + + int physics_layers_count = tile_set->get_physics_layers_count(); + int navigation_layers_count = tile_set->get_navigation_layers_count(); + if (physics_layers_count <= 0 && navigation_layers_count <= 0) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask(); + + const Transform2D tilemap_xform = p_source_geometry_data->root_node_transform * tile_map_layer->get_global_transform(); + + TypedArray used_cells = tile_map_layer->get_used_cells(); + for (int used_cell_index = 0; used_cell_index < used_cells.size(); used_cell_index++) { + const Vector2i &cell = used_cells[used_cell_index]; + + const TileData *tile_data = tile_map_layer->get_cell_tile_data(cell); + if (tile_data == nullptr) { + continue; + } + + // Transform flags. + const int alternative_id = tile_map_layer->get_cell_alternative_tile(cell); + bool flip_h = (alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_H); + bool flip_v = (alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_V); + bool transpose = (alternative_id & TileSetAtlasSource::TRANSFORM_TRANSPOSE); + + Transform2D tile_transform; + tile_transform.set_origin(tile_map_layer->map_to_local(cell)); + + const Transform2D tile_transform_offset = tilemap_xform * tile_transform; + + // Parse traversable polygons. + for (int navigation_layer = 0; navigation_layer < navigation_layers_count; navigation_layer++) { + Ref navigation_polygon = tile_data->get_navigation_polygon(navigation_layer, flip_h, flip_v, transpose); + if (navigation_polygon.is_valid()) { + for (int outline_index = 0; outline_index < navigation_polygon->get_outline_count(); outline_index++) { + const Vector &navigation_polygon_outline = navigation_polygon->get_outline(outline_index); + if (navigation_polygon_outline.is_empty()) { + continue; + } + + Vector traversable_outline; + traversable_outline.resize(navigation_polygon_outline.size()); + + const Vector2 *navigation_polygon_outline_ptr = navigation_polygon_outline.ptr(); + Vector2 *traversable_outline_ptrw = traversable_outline.ptrw(); + + for (int traversable_outline_index = 0; traversable_outline_index < traversable_outline.size(); traversable_outline_index++) { + traversable_outline_ptrw[traversable_outline_index] = tile_transform_offset.xform(navigation_polygon_outline_ptr[traversable_outline_index]); + } + + p_source_geometry_data->_add_traversable_outline(traversable_outline); + } + } + } + + // Parse obstacles. + for (int physics_layer = 0; physics_layer < physics_layers_count; physics_layer++) { + if ((parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) && + (tile_set->get_physics_layer_collision_layer(physics_layer) & parsed_collision_mask)) { + for (int collision_polygon_index = 0; collision_polygon_index < tile_data->get_collision_polygons_count(physics_layer); collision_polygon_index++) { + PackedVector2Array collision_polygon_points = tile_data->get_collision_polygon_points(physics_layer, collision_polygon_index); + if (collision_polygon_points.is_empty()) { + continue; + } + + if (flip_h || flip_v || transpose) { + collision_polygon_points = TileData::get_transformed_vertices(collision_polygon_points, flip_h, flip_v, transpose); + } + + Vector obstruction_outline; + obstruction_outline.resize(collision_polygon_points.size()); + + const Vector2 *collision_polygon_points_ptr = collision_polygon_points.ptr(); + Vector2 *obstruction_outline_ptrw = obstruction_outline.ptrw(); + + for (int obstruction_outline_index = 0; obstruction_outline_index < obstruction_outline.size(); obstruction_outline_index++) { + obstruction_outline_ptrw[obstruction_outline_index] = tile_transform_offset.xform(collision_polygon_points_ptr[obstruction_outline_index]); + } + + p_source_geometry_data->_add_obstruction_outline(obstruction_outline); + } + } + } + } +} + TileMapLayer::TileMapLayer() { set_notify_transform(true); } diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index 9bc70dab6b4..84b5b48fdfc 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -35,6 +35,7 @@ #include "scene/resources/2d/tile_set.h" +class NavigationMeshSourceGeometryData2D; class TileSetAtlasSource; class TileMap; @@ -514,6 +515,14 @@ class TileMapLayer : public Node2D { void set_navigation_visibility_mode(DebugVisibilityMode p_show_navigation); DebugVisibilityMode get_navigation_visibility_mode() const; +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + TileMapLayer(); ~TileMapLayer(); }; diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index fb45773417e..0ed5c6519b5 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -147,11 +147,23 @@ void GPUParticles3D::set_use_local_coordinates(bool p_enable) { } void GPUParticles3D::set_process_material(const Ref &p_material) { +#ifdef TOOLS_ENABLED + if (process_material.is_valid()) { + if (Ref(process_material).is_valid()) { + process_material->disconnect("emission_shape_changed", callable_mp((Node3D *)this, &GPUParticles3D::update_gizmos)); + } + } +#endif + process_material = p_material; RID material_rid; if (process_material.is_valid()) { material_rid = process_material->get_rid(); - process_material->connect("emission_shape_changed", callable_mp((Node3D *)this, &GPUParticles3D::update_gizmos)); +#ifdef TOOLS_ENABLED + if (Ref(process_material).is_valid()) { + process_material->connect("emission_shape_changed", callable_mp((Node3D *)this, &GPUParticles3D::update_gizmos)); + } +#endif } RS::get_singleton()->particles_set_process_material(particles, material_rid); @@ -556,9 +568,6 @@ void GPUParticles3D::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { RS::get_singleton()->particles_set_subemitter(particles, RID()); - - Ref material = get_process_material(); - ERR_FAIL_COND(material.is_null()); } break; case NOTIFICATION_SUSPENDED: diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 1c2beb2f992..105d9d014c3 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -38,6 +38,13 @@ #include "scene/resources/3d/concave_polygon_shape_3d.h" #include "scene/resources/3d/convex_polygon_shape_3d.h" +#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h" +#include "scene/resources/navigation_mesh.h" +#include "servers/navigation_server_3d.h" + +Callable MeshInstance3D::_navmesh_source_geometry_parsing_callback; +RID MeshInstance3D::_navmesh_source_geometry_parser; + bool MeshInstance3D::_set(const StringName &p_name, const Variant &p_value) { //this is not _too_ bad performance wise, really. it only arrives here if the property was not set anywhere else. //add to it that it's probably found on first call to _set anyway. @@ -844,6 +851,32 @@ Ref MeshInstance3D::generate_triangle_mesh() const { return Ref(); } +void MeshInstance3D::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer3D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&MeshInstance3D::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create(); + NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void MeshInstance3D::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + MeshInstance3D *mesh_instance = Object::cast_to(p_node); + + if (mesh_instance == nullptr) { + return; + } + + NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + + if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) { + Ref mesh = mesh_instance->get_mesh(); + if (mesh.is_valid()) { + p_source_geometry_data->add_mesh(mesh, mesh_instance->get_global_transform()); + } + } +} + 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); diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index 251384f8a47..d33f37e0a69 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -35,6 +35,9 @@ #include "core/templates/local_vector.h" #include "scene/3d/visual_instance_3d.h" + +class NavigationMesh; +class NavigationMeshSourceGeometryData3D; class Skin; class SkinReference; @@ -108,6 +111,14 @@ class MeshInstance3D : public GeometryInstance3D { virtual Ref generate_triangle_mesh() const override; +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + MeshInstance3D(); ~MeshInstance3D(); }; diff --git a/scene/3d/multimesh_instance_3d.cpp b/scene/3d/multimesh_instance_3d.cpp index ed7a21c3681..6fcab34bf94 100644 --- a/scene/3d/multimesh_instance_3d.cpp +++ b/scene/3d/multimesh_instance_3d.cpp @@ -32,6 +32,13 @@ #include "multimesh_instance_3d.h" +#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h" +#include "scene/resources/navigation_mesh.h" +#include "servers/navigation_server_3d.h" + +Callable MultiMeshInstance3D::_navmesh_source_geometry_parsing_callback; +RID MultiMeshInstance3D::_navmesh_source_geometry_parser; + void MultiMeshInstance3D::_refresh_interpolated() { if (is_inside_tree() && multimesh.is_valid()) { bool interpolated = is_physics_interpolated_and_enabled(); @@ -98,6 +105,41 @@ AABB MultiMeshInstance3D::get_aabb() const { } } +void MultiMeshInstance3D::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer3D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&MultiMeshInstance3D::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create(); + NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void MultiMeshInstance3D::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + MultiMeshInstance3D *multimesh_instance = Object::cast_to(p_node); + + if (multimesh_instance == nullptr) { + return; + } + + NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + + if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) { + Ref multimesh = multimesh_instance->get_multimesh(); + if (multimesh.is_valid()) { + Ref mesh = multimesh->get_mesh(); + if (mesh.is_valid()) { + int n = multimesh->get_visible_instance_count(); + if (n == -1) { + n = multimesh->get_instance_count(); + } + for (int i = 0; i < n; i++) { + p_source_geometry_data->add_mesh(mesh, multimesh_instance->get_global_transform() * multimesh->get_instance_transform(i)); + } + } + } + } +} + MultiMeshInstance3D::MultiMeshInstance3D() { } diff --git a/scene/3d/multimesh_instance_3d.h b/scene/3d/multimesh_instance_3d.h index 8ab00e612f3..008f0932658 100644 --- a/scene/3d/multimesh_instance_3d.h +++ b/scene/3d/multimesh_instance_3d.h @@ -36,6 +36,9 @@ #include "scene/3d/visual_instance_3d.h" #include "scene/resources/multimesh.h" +class NavigationMesh; +class NavigationMeshSourceGeometryData3D; + class MultiMeshInstance3D : public GeometryInstance3D { GDCLASS(MultiMeshInstance3D, GeometryInstance3D); @@ -56,6 +59,14 @@ class MultiMeshInstance3D : public GeometryInstance3D { virtual AABB get_aabb() const override; +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + MultiMeshInstance3D(); ~MultiMeshInstance3D(); }; diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp index af47208e5b2..43b3996b32f 100644 --- a/scene/3d/navigation_obstacle_3d.cpp +++ b/scene/3d/navigation_obstacle_3d.cpp @@ -33,8 +33,13 @@ #include "navigation_obstacle_3d.h" #include "core/math/geometry_2d.h" +#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h" +#include "scene/resources/navigation_mesh.h" #include "servers/navigation_server_3d.h" +Callable NavigationObstacle3D::_navmesh_source_geometry_parsing_callback; +RID NavigationObstacle3D::_navmesh_source_geometry_parser; + void NavigationObstacle3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_rid"), &NavigationObstacle3D::get_rid); @@ -421,6 +426,78 @@ PackedStringArray NavigationObstacle3D::get_configuration_warnings() const { return warnings; } +void NavigationObstacle3D::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer3D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&NavigationObstacle3D::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create(); + NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void NavigationObstacle3D::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + NavigationObstacle3D *obstacle = Object::cast_to(p_node); + + if (obstacle == nullptr) { + return; + } + + if (!obstacle->get_affect_navigation_mesh()) { + return; + } + + const float elevation = obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y; + // Prevent non-positive scaling. + const Vector3 safe_scale = obstacle->get_global_basis().get_scale().abs().maxf(0.001); + const float obstacle_radius = obstacle->get_radius(); + + if (obstacle_radius > 0.0) { + // Radius defined obstacle should be uniformly scaled from obstacle basis max scale axis. + const float scaling_max_value = safe_scale[safe_scale.max_axis_index()]; + const Vector3 uniform_max_scale = Vector3(scaling_max_value, scaling_max_value, scaling_max_value); + const Transform3D obstacle_circle_transform = p_source_geometry_data->root_node_transform * Transform3D(Basis().scaled(uniform_max_scale), obstacle->get_global_position()); + + Vector obstruction_circle_vertices; + + // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding. + // Without, navigation paths can go directly through the middle of the obstacle and conflict with the avoidance to get agents stuck. + // No place for excessive "round" detail here. Every additional edge adds a high cost for something that needs to be quick, not pretty. + static const int circle_points = 12; + + obstruction_circle_vertices.resize(circle_points); + Vector3 *circle_vertices_ptrw = obstruction_circle_vertices.ptrw(); + const real_t circle_point_step = Math_TAU / circle_points; + + for (int i = 0; i < circle_points; i++) { + const float angle = i * circle_point_step; + circle_vertices_ptrw[i] = obstacle_circle_transform.xform(Vector3(Math::cos(angle) * obstacle_radius, 0.0, Math::sin(angle) * obstacle_radius)); + } + + p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, elevation - obstacle_radius, scaling_max_value * obstacle_radius, obstacle->get_carve_navigation_mesh()); + } + + // Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account. + const Transform3D node_xform = p_source_geometry_data->root_node_transform * Transform3D(Basis().scaled(safe_scale).rotated(Vector3(0.0, 1.0, 0.0), obstacle->get_global_rotation().y), obstacle->get_global_position()); + + const Vector &obstacle_vertices = obstacle->get_vertices(); + + if (obstacle_vertices.is_empty()) { + return; + } + + Vector obstruction_shape_vertices; + obstruction_shape_vertices.resize(obstacle_vertices.size()); + + const Vector3 *obstacle_vertices_ptr = obstacle_vertices.ptr(); + Vector3 *obstruction_shape_vertices_ptrw = obstruction_shape_vertices.ptrw(); + + for (int i = 0; i < obstacle_vertices.size(); i++) { + obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]); + obstruction_shape_vertices_ptrw[i].y = 0.0; + } + p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, elevation, safe_scale.y * obstacle->get_height(), obstacle->get_carve_navigation_mesh()); +} + void NavigationObstacle3D::_update_map(RID p_map) { NavigationServer3D::get_singleton()->obstacle_set_map(obstacle, p_map); map_current = p_map; diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h index 10fe9ed6885..f614fbe2725 100644 --- a/scene/3d/navigation_obstacle_3d.h +++ b/scene/3d/navigation_obstacle_3d.h @@ -35,6 +35,9 @@ #include "scene/3d/node_3d.h" +class NavigationMesh; +class NavigationMeshSourceGeometryData3D; + class NavigationObstacle3D : public Node3D { GDCLASS(NavigationObstacle3D, Node3D); @@ -125,6 +128,14 @@ class NavigationObstacle3D : public Node3D { PackedStringArray get_configuration_warnings() const override; +private: + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + private: void _update_map(RID p_map); void _update_position(const Vector3 p_position); diff --git a/scene/3d/physics/static_body_3d.cpp b/scene/3d/physics/static_body_3d.cpp index 693c0657094..ff982ca71f3 100644 --- a/scene/3d/physics/static_body_3d.cpp +++ b/scene/3d/physics/static_body_3d.cpp @@ -32,6 +32,24 @@ #include "static_body_3d.h" +#include "core/math/convex_hull.h" +#include "scene/resources/3d/box_shape_3d.h" +#include "scene/resources/3d/capsule_shape_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" +#include "scene/resources/3d/cylinder_shape_3d.h" +#include "scene/resources/3d/height_map_shape_3d.h" +#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h" +#include "scene/resources/3d/primitive_meshes.h" +#include "scene/resources/3d/shape_3d.h" +#include "scene/resources/3d/sphere_shape_3d.h" +#include "scene/resources/3d/world_boundary_shape_3d.h" +#include "scene/resources/navigation_mesh.h" +#include "servers/navigation_server_3d.h" + +Callable StaticBody3D::_navmesh_source_geometry_parsing_callback; +RID StaticBody3D::_navmesh_source_geometry_parser; + void StaticBody3D::set_physics_material_override(const Ref &p_physics_material_override) { if (physics_material_override.is_valid()) { physics_material_override->disconnect_changed(callable_mp(this, &StaticBody3D::_reload_physics_characteristics)); @@ -79,6 +97,138 @@ void StaticBody3D::_reload_physics_characteristics() { } } +void StaticBody3D::navmesh_parse_init() { + ERR_FAIL_NULL(NavigationServer3D::get_singleton()); + if (!_navmesh_source_geometry_parser.is_valid()) { + _navmesh_source_geometry_parsing_callback = callable_mp_static(&StaticBody3D::navmesh_parse_source_geometry); + _navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create(); + NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback); + } +} + +void StaticBody3D::navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + StaticBody3D *static_body = Object::cast_to(p_node); + + if (static_body == nullptr) { + return; + } + + NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask(); + + if ((parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) && (static_body->get_collision_layer() & parsed_collision_mask)) { + List shape_owners; + static_body->get_shape_owners(&shape_owners); + for (uint32_t shape_owner : shape_owners) { + if (static_body->is_shape_owner_disabled(shape_owner)) { + continue; + } + const int shape_count = static_body->shape_owner_get_shape_count(shape_owner); + for (int shape_index = 0; shape_index < shape_count; shape_index++) { + Ref s = static_body->shape_owner_get_shape(shape_owner, shape_index); + if (s.is_null()) { + continue; + } + + const Transform3D transform = static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner); + + BoxShape3D *box = Object::cast_to(*s); + if (box) { + Array arr; + arr.resize(RS::ARRAY_MAX); + BoxMesh::create_mesh_array(arr, box->get_size()); + p_source_geometry_data->add_mesh_array(arr, transform); + } + + CapsuleShape3D *capsule = Object::cast_to(*s); + if (capsule) { + Array arr; + arr.resize(RS::ARRAY_MAX); + CapsuleMesh::create_mesh_array(arr, capsule->get_radius(), capsule->get_height()); + p_source_geometry_data->add_mesh_array(arr, transform); + } + + CylinderShape3D *cylinder = Object::cast_to(*s); + if (cylinder) { + Array arr; + arr.resize(RS::ARRAY_MAX); + CylinderMesh::create_mesh_array(arr, cylinder->get_radius(), cylinder->get_radius(), cylinder->get_height()); + p_source_geometry_data->add_mesh_array(arr, transform); + } + + SphereShape3D *sphere = Object::cast_to(*s); + if (sphere) { + Array arr; + arr.resize(RS::ARRAY_MAX); + SphereMesh::create_mesh_array(arr, sphere->get_radius(), sphere->get_radius() * 2.0); + p_source_geometry_data->add_mesh_array(arr, transform); + } + + ConcavePolygonShape3D *concave_polygon = Object::cast_to(*s); + if (concave_polygon) { + p_source_geometry_data->add_faces(concave_polygon->get_faces(), transform); + } + + ConvexPolygonShape3D *convex_polygon = Object::cast_to(*s); + if (convex_polygon) { + Vector varr = Variant(convex_polygon->get_points()); + Geometry3D::MeshData md; + + Error err = ConvexHullComputer::convex_hull(varr, md); + + if (err == OK) { + PackedVector3Array faces; + + for (const Geometry3D::MeshData::Face &face : md.faces) { + for (uint32_t k = 2; k < face.indices.size(); ++k) { + faces.push_back(md.vertices[face.indices[0]]); + faces.push_back(md.vertices[face.indices[k - 1]]); + faces.push_back(md.vertices[face.indices[k]]); + } + } + + p_source_geometry_data->add_faces(faces, transform); + } + } + + HeightMapShape3D *heightmap_shape = Object::cast_to(*s); + if (heightmap_shape) { + int heightmap_depth = heightmap_shape->get_map_depth(); + int heightmap_width = heightmap_shape->get_map_width(); + + if (heightmap_depth >= 2 && heightmap_width >= 2) { + const Vector &map_data = heightmap_shape->get_map_data(); + + Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1); + Vector3 start = Vector3(heightmap_gridsize.x, 0, heightmap_gridsize.y) * -0.5; + + Vector vertex_array; + vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6); + Vector3 *vertex_array_ptrw = vertex_array.ptrw(); + const real_t *map_data_ptr = map_data.ptr(); + int vertex_index = 0; + + for (int d = 0; d < heightmap_depth - 1; d++) { + for (int w = 0; w < heightmap_width - 1; w++) { + vertex_array_ptrw[vertex_index] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + w], d); + vertex_array_ptrw[vertex_index + 1] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); + vertex_array_ptrw[vertex_index + 2] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); + vertex_array_ptrw[vertex_index + 3] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); + vertex_array_ptrw[vertex_index + 4] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + heightmap_width + w + 1], d + 1); + vertex_array_ptrw[vertex_index + 5] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); + vertex_index += 6; + } + } + if (vertex_array.size() > 0) { + p_source_geometry_data->add_faces(vertex_array, transform); + } + } + } + } + } + } +} + void StaticBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody3D::set_constant_linear_velocity); ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody3D::set_constant_angular_velocity); diff --git a/scene/3d/physics/static_body_3d.h b/scene/3d/physics/static_body_3d.h index f58af223461..aa44e2485bc 100644 --- a/scene/3d/physics/static_body_3d.h +++ b/scene/3d/physics/static_body_3d.h @@ -35,6 +35,9 @@ #include "scene/3d/physics/physics_body_3d.h" +class NavigationMesh; +class NavigationMeshSourceGeometryData3D; + class StaticBody3D : public PhysicsBody3D { GDCLASS(StaticBody3D, PhysicsBody3D); @@ -61,6 +64,13 @@ class StaticBody3D : public PhysicsBody3D { private: void _reload_physics_characteristics(); + + static Callable _navmesh_source_geometry_parsing_callback; + static RID _navmesh_source_geometry_parser; + +public: + static void navmesh_parse_init(); + static void navmesh_parse_source_geometry(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); }; #endif // STATIC_BODY_3D_H diff --git a/scene/3d/spring_bone_simulator_3d.cpp b/scene/3d/spring_bone_simulator_3d.cpp index 5b23c789ba2..45579dcf70f 100644 --- a/scene/3d/spring_bone_simulator_3d.cpp +++ b/scene/3d/spring_bone_simulator_3d.cpp @@ -304,7 +304,7 @@ void SpringBoneSimulator3D::_get_property_list(List *p_list) const p_list->push_back(PropertyInfo(Variant::OBJECT, path + "stiffness/damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve")); p_list->push_back(PropertyInfo(Variant::FLOAT, path + "drag/value", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); p_list->push_back(PropertyInfo(Variant::OBJECT, path + "drag/damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve")); - p_list->push_back(PropertyInfo(Variant::FLOAT, path + "gravity/value", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "gravity/value", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater,or_less,suffix:m/s")); p_list->push_back(PropertyInfo(Variant::OBJECT, path + "gravity/damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve")); p_list->push_back(PropertyInfo(Variant::VECTOR3, path + "gravity/direction")); p_list->push_back(PropertyInfo(Variant::INT, path + "joint_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Joints," + path + "joints/,static,const")); @@ -316,7 +316,7 @@ void SpringBoneSimulator3D::_get_property_list(List *p_list) const p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "radius", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m")); p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "stiffness", PROPERTY_HINT_RANGE, "0,4,0.01,or_greater")); p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "drag", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); - p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "gravity", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "gravity", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater,or_less,suffix:m/s")); p_list->push_back(PropertyInfo(Variant::VECTOR3, joint_path + "gravity_direction")); } p_list->push_back(PropertyInfo(Variant::BOOL, path + "enable_all_child_collisions")); @@ -928,6 +928,10 @@ void SpringBoneSimulator3D::set_joint_rotation_axis(int p_index, int p_joint, Ro Vector &joints = settings[p_index]->joints; ERR_FAIL_INDEX(p_joint, joints.size()); joints[p_joint]->rotation_axis = p_axis; + Skeleton3D *sk = get_skeleton(); + if (sk) { + _validate_rotation_axis(sk, p_index, p_joint); + } } SpringBoneSimulator3D::RotationAxis SpringBoneSimulator3D::get_joint_rotation_axis(int p_index, int p_joint) const { @@ -1239,6 +1243,35 @@ void SpringBoneSimulator3D::remove_child_notify(Node *p_child) { } } +void SpringBoneSimulator3D::_validate_rotation_axes(Skeleton3D *p_skeleton) const { + for (int i = 0; i < settings.size(); i++) { + for (int j = 0; j < settings[i]->joints.size(); j++) { + _validate_rotation_axis(p_skeleton, i, j); + } + } +} + +void SpringBoneSimulator3D::_validate_rotation_axis(Skeleton3D *p_skeleton, int p_index, int p_joint) const { + RotationAxis axis = settings[p_index]->joints[p_joint]->rotation_axis; + if (axis == ROTATION_AXIS_ALL) { + return; + } + Vector3 rot = get_vector_from_axis(static_cast((int)axis)); + Vector3 fwd; + if (p_joint < settings[p_index]->joints.size() - 1) { + fwd = p_skeleton->get_bone_rest(settings[p_index]->joints[p_joint + 1]->bone).origin; + } else if (settings[p_index]->extend_end_bone) { + fwd = get_end_bone_axis(settings[p_index]->end_bone, settings[p_index]->end_bone_direction); + if (fwd.is_zero_approx()) { + return; + } + } + fwd.normalize(); + if (Math::is_equal_approx(Math::absf(rot.dot(fwd)), 1.0f)) { + WARN_PRINT_ED("Setting: " + itos(p_index) + " Joint: " + itos(p_joint) + ": Rotation axis and forward vectors are colinear. This is not advised as it may cause unwanted rotation."); + } +} + void SpringBoneSimulator3D::_find_collisions() { if (!collisions_dirty) { return; @@ -1409,6 +1442,10 @@ void SpringBoneSimulator3D::_update_joints() { settings[i]->joints_dirty = false; } joints_dirty = false; + Skeleton3D *sk = get_skeleton(); + if (sk) { + _validate_rotation_axes(sk); + } #ifdef TOOLS_ENABLED update_gizmos(); #endif // TOOLS_ENABLED @@ -1485,6 +1522,7 @@ void SpringBoneSimulator3D::_init_joints(Skeleton3D *p_skeleton, SpringBone3DSet setting->joints[i]->verlet->prev_tail = setting->joints[i]->verlet->current_tail; setting->joints[i]->verlet->forward_vector = axis.normalized(); setting->joints[i]->verlet->length = axis.length(); + setting->joints[i]->verlet->prev_rot = Quaternion(0, 0, 0, 1); } else if (setting->extend_end_bone && setting->end_bone_length > 0) { Vector3 axis = get_end_bone_axis(setting->end_bone, setting->end_bone_direction); if (axis.is_zero_approx()) { @@ -1495,6 +1533,7 @@ void SpringBoneSimulator3D::_init_joints(Skeleton3D *p_skeleton, SpringBone3DSet setting->joints[i]->verlet->length = setting->end_bone_length; setting->joints[i]->verlet->current_tail = setting->cached_center.xform(p_skeleton->get_bone_global_pose(setting->joints[i]->bone).xform(axis * setting->end_bone_length)); setting->joints[i]->verlet->prev_tail = setting->joints[i]->verlet->current_tail; + setting->joints[i]->verlet->prev_rot = Quaternion(0, 0, 0, 1); } } setting->simulation_dirty = false; @@ -1557,10 +1596,13 @@ void SpringBoneSimulator3D::_process_joints(double p_delta, Skeleton3D *p_skelet verlet->prev_tail = verlet->current_tail; verlet->current_tail = next_tail; - // Apply rotation. + // Convert position to rotation. Vector3 from = current_rot.xform(verlet->forward_vector); Vector3 to = p_inverted_center_transform.basis.xform(next_tail - current_origin).normalized(); - Quaternion from_to = get_from_to_rotation(from, to); + Quaternion from_to = get_from_to_rotation(from, to, verlet->prev_rot); + verlet->prev_rot = from_to; + + // Apply rotation. from_to *= current_rot; from_to = get_local_pose_rotation(p_skeleton, p_joints[i]->bone, from_to); p_skeleton->set_bone_pose_rotation(p_joints[i]->bone, from_to); @@ -1575,10 +1617,13 @@ Quaternion SpringBoneSimulator3D::get_local_pose_rotation(Skeleton3D *p_skeleton return p_skeleton->get_bone_global_pose(parent).basis.orthonormalized().inverse() * p_global_pose_rotation; } -Quaternion SpringBoneSimulator3D::get_from_to_rotation(const Vector3 &p_from, const Vector3 &p_to) { +Quaternion SpringBoneSimulator3D::get_from_to_rotation(const Vector3 &p_from, const Vector3 &p_to, const Quaternion &p_prev_rot) { + if (Math::is_equal_approx((float)p_from.dot(p_to), -1.0f)) { + return p_prev_rot; // For preventing to glitch, checking dot for detecting flip is more accurate than checking cross. + } Vector3 axis = p_from.cross(p_to); if (axis.is_zero_approx()) { - return Quaternion(0, 0, 0, 1); + return p_prev_rot; } float angle = p_from.angle_to(p_to); if (Math::is_zero_approx(angle)) { diff --git a/scene/3d/spring_bone_simulator_3d.h b/scene/3d/spring_bone_simulator_3d.h index 092b2295426..750f14f8493 100644 --- a/scene/3d/spring_bone_simulator_3d.h +++ b/scene/3d/spring_bone_simulator_3d.h @@ -74,6 +74,7 @@ class SpringBoneSimulator3D : public SkeletonModifier3D { Vector3 prev_tail; Vector3 current_tail; Vector3 forward_vector; + Quaternion prev_rot; float length = 0.0; }; @@ -165,6 +166,9 @@ class SpringBoneSimulator3D : public SkeletonModifier3D { virtual void move_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; + void _validate_rotation_axes(Skeleton3D *p_skeleton) const; + void _validate_rotation_axis(Skeleton3D *p_skeleton, int p_index, int p_joint) const; + public: // Setting. void set_root_bone_name(int p_index, const String &p_bone_name); @@ -266,7 +270,7 @@ class SpringBoneSimulator3D : public SkeletonModifier3D { // Helper. static Quaternion get_local_pose_rotation(Skeleton3D *p_skeleton, int p_bone, const Quaternion &p_global_pose_rotation); - static Quaternion get_from_to_rotation(const Vector3 &p_from, const Vector3 &p_to); + static Quaternion get_from_to_rotation(const Vector3 &p_from, const Vector3 &p_to, const Quaternion &p_prev_rot); static Vector3 snap_position_to_plane(const Transform3D &p_rest, RotationAxis p_axis, const Vector3 &p_position); static Vector3 limit_length(const Vector3 &p_origin, const Vector3 &p_destination, float p_length); diff --git a/scene/3d/voxelizer.h b/scene/3d/voxelizer.h index 3daa28d42d3..e1cd5192c67 100644 --- a/scene/3d/voxelizer.h +++ b/scene/3d/voxelizer.h @@ -74,13 +74,13 @@ class Voxelizer { struct CellSort { union { + uint64_t key = 0; struct { uint64_t z : 16; uint64_t y : 16; uint64_t x : 16; uint64_t level : 16; }; - uint64_t key = 0; }; int32_t index = 0; diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 50627a3dd2c..5f659ad47e7 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -152,11 +152,11 @@ class GraphEdit : public Control { private: struct ConnectionType { union { + uint64_t key = 0; struct { uint32_t type_a; uint32_t type_b; }; - uint64_t key = 0; }; static uint32_t hash(const ConnectionType &p_conn) { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index d46f7488f04..f7bb13f05d6 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -6067,7 +6067,7 @@ void TextEdit::adjust_viewport_to_caret(int p_caret) { // Get position of the start of caret. if (has_ime_text() && ime_selection.x != 0) { - caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret)); + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret) + ime_selection.x); } else { caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); } @@ -6075,9 +6075,9 @@ void TextEdit::adjust_viewport_to_caret(int p_caret) { // Get position of the end of caret. if (has_ime_text()) { if (ime_selection.y > 0) { - caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret) + ime_selection.x + ime_selection.y); } else { - caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.length(), get_caret_line(p_caret), get_caret_column(p_caret) + ime_text.length()); } } else { caret_pos.y = caret_pos.x; @@ -6119,7 +6119,7 @@ void TextEdit::center_viewport_to_caret(int p_caret) { // Get position of the start of caret. if (has_ime_text() && ime_selection.x != 0) { - caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret)); + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret) + ime_selection.x); } else { caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); } @@ -6127,9 +6127,9 @@ void TextEdit::center_viewport_to_caret(int p_caret) { // Get position of the end of caret. if (has_ime_text()) { if (ime_selection.y > 0) { - caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret) + ime_selection.x + ime_selection.y); } else { - caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.length(), get_caret_line(p_caret), get_caret_column(p_caret) + ime_text.length()); } } else { caret_pos.y = caret_pos.x; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 3720e4edaf0..603bff8bb6b 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -1042,6 +1042,24 @@ void register_scene_types() { OS::get_singleton()->yield(); // may take time to init + // 2D nodes that support navmesh baking need to server register their source geometry parsers. + MeshInstance2D::navmesh_parse_init(); + MultiMeshInstance2D::navmesh_parse_init(); + NavigationObstacle2D::navmesh_parse_init(); + Polygon2D::navmesh_parse_init(); + TileMap::navmesh_parse_init(); + TileMapLayer::navmesh_parse_init(); + StaticBody2D::navmesh_parse_init(); +#ifndef _3D_DISABLED + // 3D nodes that support navmesh baking need to server register their source geometry parsers. + MeshInstance3D::navmesh_parse_init(); + MultiMeshInstance3D::navmesh_parse_init(); + NavigationObstacle3D::navmesh_parse_init(); + StaticBody3D::navmesh_parse_init(); +#endif + + OS::get_singleton()->yield(); // may take time to init + GDREGISTER_ABSTRACT_CLASS(SceneState); GDREGISTER_CLASS(PackedScene); diff --git a/scene/theme/icons/SCsub b/scene/theme/icons/SCsub index 19aca74e57c..5da556f3468 100644 --- a/scene/theme/icons/SCsub +++ b/scene/theme/icons/SCsub @@ -5,16 +5,8 @@ Import("env") import default_theme_icons_builders -env["BUILDERS"]["MakeDefaultThemeIconsBuilder"] = Builder( - action=env.Run(default_theme_icons_builders.make_default_theme_icons_action), - suffix=".h", - src_suffix=".svg", -) - -# Default theme icons -icon_sources = Glob("*.svg") - -env.Alias( - "default_theme_icons", - [env.MakeDefaultThemeIconsBuilder("#scene/theme/default_theme_icons.gen.h", icon_sources)], +env.CommandNoCache( + "#scene/theme/default_theme_icons.gen.h", + Glob("*.svg"), + env.Run(default_theme_icons_builders.make_default_theme_icons_action), ) diff --git a/servers/navigation_server_2d.cpp b/servers/navigation_server_2d.cpp index b7e5873a1bb..d8c1794ab60 100644 --- a/servers/navigation_server_2d.cpp +++ b/servers/navigation_server_2d.cpp @@ -33,10 +33,15 @@ #include "navigation_server_2d.h" #include "navigation_server_2d.compat.inc" +#include "servers/navigation_server_2d_dummy.h" #include "servers/navigation_server_3d.h" NavigationServer2D *NavigationServer2D::singleton = nullptr; +RWLock NavigationServer2D::geometry_parser_rwlock; +RID_Owner NavigationServer2D::geometry_parser_owner; +LocalVector NavigationServer2D::generator_parsers; + void NavigationServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_maps"), &NavigationServer2D::get_maps); @@ -210,6 +215,47 @@ void NavigationServer2D::_emit_navigation_debug_changed_signal() { NavigationServer2D::~NavigationServer2D() { singleton = nullptr; + + RWLockWrite write_lock(geometry_parser_rwlock); + for (NavMeshGeometryParser2D *parser : generator_parsers) { + geometry_parser_owner.free(parser->self); + } + generator_parsers.clear(); +} + +RID NavigationServer2D::source_geometry_parser_create() { + RWLockWrite write_lock(geometry_parser_rwlock); + + RID rid = geometry_parser_owner.make_rid(); + + NavMeshGeometryParser2D *parser = geometry_parser_owner.get_or_null(rid); + parser->self = rid; + + generator_parsers.push_back(parser); + + return rid; +} + +void NavigationServer2D::free(RID p_object) { + if (!geometry_parser_owner.owns(p_object)) { + return; + } + RWLockWrite write_lock(geometry_parser_rwlock); + + NavMeshGeometryParser2D *parser = geometry_parser_owner.get_or_null(p_object); + ERR_FAIL_NULL(parser); + + generator_parsers.erase(parser); + geometry_parser_owner.free(parser->self); +} + +void NavigationServer2D::source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) { + RWLockWrite write_lock(geometry_parser_rwlock); + + NavMeshGeometryParser2D *parser = geometry_parser_owner.get_or_null(p_parser); + ERR_FAIL_NULL(parser); + + parser->callback = p_callback; } void NavigationServer2D::_emit_map_changed(RID p_map) { @@ -420,6 +466,8 @@ bool NavigationServer2D::get_debug_navigation_avoidance_enable_obstacles_static( /////////////////////////////////////////////////////// +static NavigationServer2D *navigation_server_2d = nullptr; + NavigationServer2DCallback NavigationServer2DManager::create_callback = nullptr; void NavigationServer2DManager::set_default_server(NavigationServer2DCallback p_callback) { @@ -433,3 +481,26 @@ NavigationServer2D *NavigationServer2DManager::new_default_server() { return create_callback(); } + +void NavigationServer2DManager::initialize_server() { + // NavigationServer3D must be initialized before NavigationServer2D. + ERR_FAIL_NULL(NavigationServer3D::get_singleton()); + ERR_FAIL_COND(navigation_server_2d != nullptr); + + // Init 2D Navigation Server + navigation_server_2d = NavigationServer2DManager::new_default_server(); + if (!navigation_server_2d) { + WARN_VERBOSE("Failed to initialize NavigationServer2D. Fall back to dummy server."); + navigation_server_2d = memnew(NavigationServer2DDummy); + } + + ERR_FAIL_NULL_MSG(navigation_server_2d, "Failed to initialize NavigationServer2D."); + navigation_server_2d->init(); +} + +void NavigationServer2DManager::finalize_server() { + ERR_FAIL_NULL(navigation_server_2d); + navigation_server_2d->finish(); + memdelete(navigation_server_2d); + navigation_server_2d = nullptr; +} diff --git a/servers/navigation_server_2d.h b/servers/navigation_server_2d.h index cd33a2e55e6..ae9950991cd 100644 --- a/servers/navigation_server_2d.h +++ b/servers/navigation_server_2d.h @@ -45,6 +45,11 @@ class NavMeshGenerator2D; #endif // CLIPPER2_ENABLED +struct NavMeshGeometryParser2D { + RID self; + Callable callback; +}; + // This server exposes the `NavigationServer3D` features in the 2D world. class NavigationServer2D : public Object { GDCLASS(NavigationServer2D, Object); @@ -313,6 +318,12 @@ class NavigationServer2D : public Object { virtual void bake_from_source_geometry_data_async(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, const Callable &p_callback = Callable()) = 0; virtual bool is_baking_navigation_polygon(Ref p_navigation_polygon) const = 0; +protected: + static RWLock geometry_parser_rwlock; + static RID_Owner geometry_parser_owner; + static LocalVector generator_parsers; + +public: virtual RID source_geometry_parser_create() = 0; virtual void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) = 0; @@ -421,6 +432,9 @@ class NavigationServer2DManager { public: static void set_default_server(NavigationServer2DCallback p_callback); static NavigationServer2D *new_default_server(); + + static void initialize_server(); + static void finalize_server(); }; #endif // NAVIGATION_SERVER_2D_H diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp index a2091efb4eb..9eff64c709b 100644 --- a/servers/navigation_server_3d.cpp +++ b/servers/navigation_server_3d.cpp @@ -36,9 +36,14 @@ #include "core/config/project_settings.h" #include "scene/main/node.h" #include "servers/navigation/navigation_globals.h" +#include "servers/navigation_server_3d_dummy.h" NavigationServer3D *NavigationServer3D::singleton = nullptr; +RWLock NavigationServer3D::geometry_parser_rwlock; +RID_Owner NavigationServer3D::geometry_parser_owner; +LocalVector NavigationServer3D::generator_parsers; + void NavigationServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_maps"), &NavigationServer3D::get_maps); @@ -305,6 +310,47 @@ NavigationServer3D::NavigationServer3D() { NavigationServer3D::~NavigationServer3D() { singleton = nullptr; + + RWLockWrite write_lock(geometry_parser_rwlock); + for (NavMeshGeometryParser3D *parser : generator_parsers) { + geometry_parser_owner.free(parser->self); + } + generator_parsers.clear(); +} + +RID NavigationServer3D::source_geometry_parser_create() { + RWLockWrite write_lock(geometry_parser_rwlock); + + RID rid = geometry_parser_owner.make_rid(); + + NavMeshGeometryParser3D *parser = geometry_parser_owner.get_or_null(rid); + parser->self = rid; + + generator_parsers.push_back(parser); + + return rid; +} + +void NavigationServer3D::free(RID p_object) { + if (!geometry_parser_owner.owns(p_object)) { + return; + } + RWLockWrite write_lock(geometry_parser_rwlock); + + NavMeshGeometryParser3D *parser = geometry_parser_owner.get_or_null(p_object); + ERR_FAIL_NULL(parser); + + generator_parsers.erase(parser); + geometry_parser_owner.free(parser->self); +} + +void NavigationServer3D::source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) { + RWLockWrite write_lock(geometry_parser_rwlock); + + NavMeshGeometryParser3D *parser = geometry_parser_owner.get_or_null(p_parser); + ERR_FAIL_NULL(parser); + + parser->callback = p_callback; } void NavigationServer3D::set_debug_enabled(bool p_enabled) { @@ -947,6 +993,8 @@ bool NavigationServer3D::get_debug_avoidance_enabled() const { /////////////////////////////////////////////////////// +static NavigationServer3D *navigation_server_3d = nullptr; + NavigationServer3DCallback NavigationServer3DManager::create_callback = nullptr; void NavigationServer3DManager::set_default_server(NavigationServer3DCallback p_callback) { @@ -960,3 +1008,27 @@ NavigationServer3D *NavigationServer3DManager::new_default_server() { return create_callback(); } + +void NavigationServer3DManager::initialize_server() { + ERR_FAIL_COND(navigation_server_3d != nullptr); + + // Init 3D Navigation Server + navigation_server_3d = NavigationServer3DManager::new_default_server(); + + // Fall back to dummy if no default server has been registered. + if (!navigation_server_3d) { + WARN_VERBOSE("Failed to initialize NavigationServer3D. Fall back to dummy server."); + navigation_server_3d = memnew(NavigationServer3DDummy); + } + + // Should be impossible, but make sure it's not null. + ERR_FAIL_NULL_MSG(navigation_server_3d, "Failed to initialize NavigationServer3D."); + navigation_server_3d->init(); +} + +void NavigationServer3DManager::finalize_server() { + ERR_FAIL_NULL(navigation_server_3d); + navigation_server_3d->finish(); + memdelete(navigation_server_3d); + navigation_server_3d = nullptr; +} diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h index 139c5333023..c304ba8789d 100644 --- a/servers/navigation_server_3d.h +++ b/servers/navigation_server_3d.h @@ -41,6 +41,11 @@ #include "servers/navigation/navigation_path_query_parameters_3d.h" #include "servers/navigation/navigation_path_query_result_3d.h" +struct NavMeshGeometryParser3D { + RID self; + Callable callback; +}; + /// This server uses the concept of internal mutability. /// All the constant functions can be called in multithread because internally /// the server takes care to schedule the functions access. @@ -359,6 +364,12 @@ class NavigationServer3D : public Object { virtual bool is_baking_navigation_mesh(Ref p_navigation_mesh) const = 0; #endif // _3D_DISABLED +protected: + static RWLock geometry_parser_rwlock; + static RID_Owner geometry_parser_owner; + static LocalVector generator_parsers; + +public: virtual RID source_geometry_parser_create() = 0; virtual void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) = 0; @@ -575,6 +586,9 @@ class NavigationServer3DManager { public: static void set_default_server(NavigationServer3DCallback p_callback); static NavigationServer3D *new_default_server(); + + static void initialize_server(); + static void finalize_server(); }; VARIANT_ENUM_CAST(NavigationServer3D::ProcessInfo); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h index 69552d8ed17..657dee34db0 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h @@ -430,6 +430,10 @@ class RenderForwardClustered : public RendererSceneRenderRD { }; union { + struct { + uint64_t sort_key1; + uint64_t sort_key2; + }; struct { uint64_t lod_index : 8; uint64_t surface_index : 8; @@ -445,10 +449,6 @@ class RenderForwardClustered : public RendererSceneRenderRD { uint64_t depth_layer : 4; uint64_t priority : 8; }; - struct { - uint64_t sort_key1; - uint64_t sort_key2; - }; } sort; RS::PrimitiveType primitive = RS::PRIMITIVE_MAX; @@ -543,6 +543,8 @@ class RenderForwardClustered : public RendererSceneRenderRD { struct GlobalPipelineData { union { + uint32_t key; + struct { uint32_t texture_samples : 3; uint32_t use_reflection_probes : 1; @@ -558,8 +560,6 @@ class RenderForwardClustered : public RendererSceneRenderRD { uint32_t use_shadow_cubemaps : 1; uint32_t use_shadow_dual_paraboloid : 1; }; - - uint32_t key; }; }; diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h index 3cf68d8a8f4..83f1b374987 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h @@ -399,6 +399,10 @@ class RenderForwardMobile : public RendererSceneRenderRD { }; union { + struct { + uint64_t sort_key1; + uint64_t sort_key2; + }; struct { // !BAS! CHECK BITS!!! @@ -415,10 +419,6 @@ class RenderForwardMobile : public RendererSceneRenderRD { // uint64_t lod_index : 8; // no need to sort on LOD // uint64_t uses_forward_gi : 1; // no GI here, remove }; - struct { - uint64_t sort_key1; - uint64_t sort_key2; - }; } sort; RS::PrimitiveType primitive = RS::PRIMITIVE_MAX; @@ -577,6 +577,8 @@ class RenderForwardMobile : public RendererSceneRenderRD { struct GlobalPipelineData { union { + uint32_t key; + struct { uint32_t texture_samples : 3; uint32_t target_samples : 3; @@ -588,8 +590,6 @@ class RenderForwardMobile : public RendererSceneRenderRD { uint32_t use_shadow_cubemaps : 1; uint32_t use_shadow_dual_paraboloid : 1; }; - - uint32_t key; }; }; diff --git a/tests/servers/test_text_server.h b/tests/servers/test_text_server.h index d9cbf678e7b..2d39476bde9 100644 --- a/tests/servers/test_text_server.h +++ b/tests/servers/test_text_server.h @@ -817,12 +817,12 @@ TEST_SUITE("[TextServer]") { SUBCASE("[TextServer] Word break") { for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) { Ref ts = TextServerManager::get_singleton()->get_interface(i); + CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface."); if (!ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) { continue; } - CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface."); { String text1 = U"linguistically similar and effectively form"; // 14^ 22^ 26^ 38^ @@ -920,6 +920,47 @@ TEST_SUITE("[TextServer]") { } } } + + SUBCASE("[TextServer] Buffer invalidation") { + for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) { + Ref ts = TextServerManager::get_singleton()->get_interface(i); + CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface."); + + if (!ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) { + continue; + } + + RID font1 = ts->create_font(); + ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size); + + Array font; + font.push_back(font1); + + RID ctx = ts->create_shaped_text(); + CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed."); + bool ok = ts->shaped_text_add_string(ctx, "T", font, 16); + CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed."); + int gl_size = ts->shaped_text_get_glyph_count(ctx); + CHECK_MESSAGE(gl_size == 1, "Shaping failed, invalid glyph count"); + + ok = ts->shaped_text_add_object(ctx, "key", Size2(20, 20), INLINE_ALIGNMENT_CENTER, 1, 0.0); + CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed."); + gl_size = ts->shaped_text_get_glyph_count(ctx); + CHECK_MESSAGE(gl_size == 2, "Shaping failed, invalid glyph count"); + + ok = ts->shaped_text_add_string(ctx, "B", font, 16); + CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed."); + gl_size = ts->shaped_text_get_glyph_count(ctx); + CHECK_MESSAGE(gl_size == 3, "Shaping failed, invalid glyph count"); + + ts->free_rid(ctx); + + for (int j = 0; j < font.size(); j++) { + ts->free_rid(font[j]); + } + font.clear(); + } + } } } }; // namespace TestTextServer diff --git a/thirdparty/README.md b/thirdparty/README.md index cae1e46b641..5e264ae4dee 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -935,7 +935,7 @@ instead of `miniz.h` as an external dependency. ## thorvg - Upstream: https://github.com/thorvg/thorvg -- Version: 0.15.5 (89ab573acb253567975b2494069c7ee9abc9267c, 2024) +- Version: 0.15.8 (bd8c2fca7663a22fba7a339937cb60f2f6247a2e, 2025) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/thorvg/AUTHORS b/thirdparty/thorvg/AUTHORS index a15f3262a82..3a4cc7a11cf 100644 --- a/thirdparty/thorvg/AUTHORS +++ b/thirdparty/thorvg/AUTHORS @@ -22,7 +22,7 @@ Rafał Mikrut Martin Capitanio RuiwenTang YouJin Lee -SergeyLebedkin +Sergii Liebodkin Jinny You Nattu Adnan Gabor Kiss-Vamosi @@ -35,3 +35,6 @@ Thaddeus Crews Josh Soref Elliott Sales de Andrade Łukasz Pomietło +Kelly Loh +Dragoș Tiselice +Marcin Baszczewski diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index 6df6f52d047..62ca775dab5 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -15,5 +15,5 @@ // For internal debugging: //#define THORVG_LOG_ENABLED -#define THORVG_VERSION_STRING "0.15.5" +#define THORVG_VERSION_STRING "0.15.8" #endif diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h index 1ee898ca6ff..8e3ab4e6ce5 100644 --- a/thirdparty/thorvg/inc/thorvg.h +++ b/thirdparty/thorvg/inc/thorvg.h @@ -217,7 +217,10 @@ enum class SceneEffect : uint8_t { ClearAll = 0, ///< Reset all previously applied scene effects, restoring the scene to its original state. GaussianBlur, ///< Apply a blur effect with a Gaussian filter. Param(3) = {sigma(float)[> 0], direction(int)[both: 0 / horizontal: 1 / vertical: 2], border(int)[duplicate: 0 / wrap: 1], quality(int)[0 - 100]} - DropShadow ///< Apply a drop shadow effect with a Gaussian Blur filter. Param(8) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255], angle(float)[0 - 360], distance(float), blur_sigma(float)[> 0], quality(int)[0 - 100]} + DropShadow, ///< Apply a drop shadow effect with a Gaussian Blur filter. Param(8) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255], angle(float)[0 - 360], distance(float), blur_sigma(float)[> 0], quality(int)[0 - 100]} + Fill, ///< Override the scene content color with a given fill information (Experimental API). Param(5) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255]} + Tint, ///< Tinting the current scene color with a given black, white color paramters (Experimental API). Param(7) = {black_R(int)[0 - 255], black_G(int)[0 - 255], black_B(int)[0 - 255], white_R(int)[0 - 255], white_G(int)[0 - 255], white_B(int)[0 - 255], intensity(float)[0 - 100]} + Tritone ///< Apply a tritone color effect to the scene using three color parameters for shadows, midtones, and highlights (Experimental API). Param(9) = {Shadow_R(int)[0 - 255], Shadow_G(int)[0 - 255], Shadow_B(int)[0 - 255], Midtone_R(int)[0 - 255], Midtone_G(int)[0 - 255], Midtone_B(int)[0 - 255], Highlight_R(int)[0 - 255], Highlight_G(int)[0 - 255], Highlight_B(int)[0 - 255]} }; @@ -2110,7 +2113,7 @@ class TVG_API Accessor final /** * @brief Set the access function for traversing the Picture scene tree nodes. * - * @param[in] picture The picture node to traverse the internal scene-tree. + * @param[in] paint The paint node to traverse the internal scene-tree. * @param[in] func The callback function calling for every paint nodes of the Picture. * @param[in] data Data passed to the @p func as its argument. * @@ -2118,7 +2121,7 @@ class TVG_API Accessor final * * @note Experimental API */ - Result set(const Picture* picture, std::function func, void* data) noexcept; + Result set(Paint* paint, std::function func, void* data) noexcept; /** * @brief Generate a unique ID (hash key) from a given name. diff --git a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp index 49c9f6e8aa2..71bf25a62b2 100644 --- a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp +++ b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp @@ -67,6 +67,7 @@ bool PngLoader::open(const string& path) bool PngLoader::open(const char* data, uint32_t size, bool copy) { +#ifdef THORVG_FILE_IO_SUPPORT image->opaque = NULL; if (!png_image_begin_read_from_memory(image, data, size)) return false; @@ -75,6 +76,9 @@ bool PngLoader::open(const char* data, uint32_t size, bool copy) h = (float)image->height; return true; +#else + return false; +#endif } diff --git a/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.cpp b/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.cpp index 0db7d2d233f..6aa497c830d 100644 --- a/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.cpp +++ b/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.cpp @@ -68,6 +68,7 @@ WebpLoader::~WebpLoader() bool WebpLoader::open(const string& path) { +#ifdef THORVG_FILE_IO_SUPPORT auto webpFile = fopen(path.c_str(), "rb"); if (!webpFile) return false; @@ -96,6 +97,9 @@ bool WebpLoader::open(const string& path) finalize: fclose(webpFile); return ret; +#else + return false; +#endif } diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp index cb1306b71a3..b676bebfb99 100644 --- a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp @@ -70,6 +70,7 @@ JpgLoader::~JpgLoader() bool JpgLoader::open(const string& path) { +#ifdef THORVG_FILE_IO_SUPPORT int width, height; decoder = jpgdHeader(path.c_str(), &width, &height); if (!decoder) return false; @@ -78,6 +79,9 @@ bool JpgLoader::open(const string& path) h = static_cast(height); return true; +#else + return false; +#endif } diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp index 1ab3043c24e..6ba433ba230 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -3973,6 +3973,7 @@ bool SvgLoader::open(const char* data, uint32_t size, bool copy) bool SvgLoader::open(const string& path) { +#ifdef THORVG_FILE_IO_SUPPORT clear(); ifstream f; @@ -3990,6 +3991,9 @@ bool SvgLoader::open(const string& path) size = filePath.size(); return header(); +#else + return false; +#endif } diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp index 00c7408a7e8..175739250a1 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -213,7 +213,7 @@ static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape* Matrix m = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; finalTransform *= m; } - if (child->transform) finalTransform = *child->transform * finalTransform; + if (child->transform) finalTransform *= *child->transform; return _appendClipShape(loaderData, child, shape, vBox, svgPath, identity((const Matrix*)(&finalTransform)) ? nullptr : &finalTransform); } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h index 9371ae6c5ad..84fc4d9ea38 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h @@ -547,8 +547,8 @@ SwRle* rleRender(const SwBBox* bbox); void rleFree(SwRle* rle); void rleReset(SwRle* rle); void rleMerge(SwRle* rle, SwRle* clip1, SwRle* clip2); -void rleClip(SwRle* rle, const SwRle* clip); -void rleClip(SwRle* rle, const SwBBox* clip); +bool rleClip(SwRle* rle, const SwRle* clip); +bool rleClip(SwRle* rle, const SwBBox* clip); SwMpool* mpoolInit(uint32_t threads); bool mpoolTerm(SwMpool* mpool); @@ -575,10 +575,17 @@ void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32 void rasterUnpremultiply(RenderSurface* surface); void rasterPremultiply(RenderSurface* surface); bool rasterConvertCS(RenderSurface* surface, ColorSpace to); +uint32_t rasterUnpremultiply(uint32_t data); bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params); bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* effect); -bool effectDropShadow(SwCompositor* cmp, SwSurface* surfaces[2], const RenderEffectDropShadow* params, uint8_t opacity, bool direct); +bool effectDropShadow(SwCompositor* cmp, SwSurface* surfaces[2], const RenderEffectDropShadow* params, bool direct); bool effectDropShadowPrepare(RenderEffectDropShadow* effect); +bool effectFillPrepare(RenderEffectFill* effect); +bool effectFill(SwCompositor* cmp, const RenderEffectFill* params, bool direct); +bool effectTintPrepare(RenderEffectTint* effect); +bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct); +bool effectTritonePrepare(RenderEffectTritone* effect); +bool effectTritone(SwCompositor* cmp, const RenderEffectTritone* params, bool direct); #endif /* _TVG_SW_COMMON_H_ */ diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp index f70ba7a13d6..e012465b708 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp @@ -487,6 +487,7 @@ void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32 auto src = MULTIPLY(A(_pixel(fill, sqrtf(det))), a); auto tmp = maskOp(src, *cmp, 0); *dst = tmp + MULTIPLY(*dst, ~tmp); + det += deltaDet; deltaDet += deltaDeltaDet; b += deltaB; } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp index fd8e532e12a..3afbd660119 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp @@ -150,7 +150,6 @@ bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* params) //invalid if (extends == 0) { - params->invalid = true; free(rd); return false; } @@ -158,6 +157,7 @@ bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* params) _gaussianExtendRegion(params->extend, extends, params->direction); params->rd = rd; + params->valid = true; return true; } @@ -165,11 +165,6 @@ bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* params) bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params) { - if (cmp->image.channelSize != sizeof(uint32_t)) { - TVGERR("SW_ENGINE", "Not supported grayscale Gaussian Blur!"); - return false; - } - auto& buffer = surface->compositor->image; auto data = static_cast(params->rd); auto& bbox = cmp->bbox; @@ -310,7 +305,6 @@ bool effectDropShadowPrepare(RenderEffectDropShadow* params) //invalid if (extends == 0 || params->color[3] == 0) { - params->invalid = true; free(rd); return false; } @@ -327,6 +321,7 @@ bool effectDropShadowPrepare(RenderEffectDropShadow* params) _dropShadowExtendRegion(params->extend, extends, rd->offset); params->rd = rd; + params->valid = true; return true; } @@ -335,13 +330,8 @@ bool effectDropShadowPrepare(RenderEffectDropShadow* params) //A quite same integration with effectGaussianBlur(). See it for detailed comments. //surface[0]: the original image, to overlay it into the filtered image. //surface[1]: temporary buffer for generating the filtered image. -bool effectDropShadow(SwCompositor* cmp, SwSurface* surface[2], const RenderEffectDropShadow* params, uint8_t opacity, bool direct) +bool effectDropShadow(SwCompositor* cmp, SwSurface* surface[2], const RenderEffectDropShadow* params, bool direct) { - if (cmp->image.channelSize != sizeof(uint32_t)) { - TVGERR("SW_ENGINE", "Not supported grayscale Drop Shadow!"); - return false; - } - //FIXME: if the body is partially visible due to clipping, the shadow also becomes partially visible. auto data = static_cast(params->rd); @@ -357,7 +347,8 @@ bool effectDropShadow(SwCompositor* cmp, SwSurface* surface[2], const RenderEffe auto stride = cmp->image.stride; auto front = cmp->image.buf32; auto back = buffer[1]->buf32; - opacity = MULTIPLY(params->color[3], opacity); + + auto opacity = direct ? MULTIPLY(params->color[3], cmp->opacity) : params->color[3]; TVGLOG("SW_ENGINE", "DropShadow region(%ld, %ld, %ld, %ld) params(%f %f %f), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->angle, params->distance, params->sigma, data->level); @@ -408,3 +399,181 @@ bool effectDropShadow(SwCompositor* cmp, SwSurface* surface[2], const RenderEffe return true; } + + +/************************************************************************/ +/* Fill Implementation */ +/************************************************************************/ + +bool effectFillPrepare(RenderEffectFill* params) +{ + params->valid = true; + return true; +} + + +bool effectFill(SwCompositor* cmp, const RenderEffectFill* params, bool direct) +{ + auto opacity = direct ? MULTIPLY(params->color[3], cmp->opacity) : params->color[3]; + + auto& bbox = cmp->bbox; + auto w = size_t(bbox.max.x - bbox.min.x); + auto h = size_t(bbox.max.y - bbox.min.y); + auto color = cmp->recoverSfc->join(params->color[0], params->color[1], params->color[2], 255); + + TVGLOG("SW_ENGINE", "Fill region(%ld, %ld, %ld, %ld), param(%d %d %d %d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->color[0], params->color[1], params->color[2], params->color[3]); + + if (direct) { + auto dbuffer = cmp->recoverSfc->buf32 + (bbox.min.y * cmp->recoverSfc->stride + bbox.min.x); + auto sbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x); + for (size_t y = 0; y < h; ++y) { + auto dst = dbuffer; + auto src = sbuffer; + for (size_t x = 0; x < w; ++x, ++dst, ++src) { + auto a = MULTIPLY(opacity, A(*src)); + auto tmp = ALPHA_BLEND(color, a); + *dst = tmp + ALPHA_BLEND(*dst, 255 - a); + } + dbuffer += cmp->image.stride; + sbuffer += cmp->recoverSfc->stride; + } + cmp->valid = true; //no need the subsequent composition + } else { + auto dbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x); + for (size_t y = 0; y < h; ++y) { + auto dst = dbuffer; + for (size_t x = 0; x < w; ++x, ++dst) { + *dst = ALPHA_BLEND(color, MULTIPLY(opacity, A(*dst))); + } + dbuffer += cmp->image.stride; + } + } + return true; +} + + +/************************************************************************/ +/* Tint Implementation */ +/************************************************************************/ + +bool effectTintPrepare(RenderEffectTint* params) +{ + params->valid = true; + return true; +} + + +bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct) +{ + auto& bbox = cmp->bbox; + auto w = size_t(bbox.max.x - bbox.min.x); + auto h = size_t(bbox.max.y - bbox.min.y); + auto black = cmp->recoverSfc->join(params->black[0], params->black[1], params->black[2], 255); + auto white = cmp->recoverSfc->join(params->white[0], params->white[1], params->white[2], 255); + auto opacity = cmp->opacity; + auto luma = cmp->recoverSfc->alphas[2]; //luma function + + TVGLOG("SW_ENGINE", "Tint region(%ld, %ld, %ld, %ld), param(%d %d %d, %d %d %d, %d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->black[0], params->black[1], params->black[2], params->white[0], params->white[1], params->white[2], params->intensity); + + /* Tint Formula: (1 - L) * Black + L * White, where the L is Luminance. */ + + if (direct) { + auto dbuffer = cmp->recoverSfc->buf32 + (bbox.min.y * cmp->recoverSfc->stride + bbox.min.x); + auto sbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x); + for (size_t y = 0; y < h; ++y) { + auto dst = dbuffer; + auto src = sbuffer; + for (size_t x = 0; x < w; ++x, ++dst, ++src) { + auto tmp = rasterUnpremultiply(*src); + auto val = INTERPOLATE(INTERPOLATE(black, white, luma((uint8_t*)&tmp)), tmp, params->intensity); + *dst = INTERPOLATE(val, *dst, MULTIPLY(opacity, A(tmp))); + } + dbuffer += cmp->image.stride; + sbuffer += cmp->recoverSfc->stride; + } + cmp->valid = true; //no need the subsequent composition + } else { + auto dbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x); + for (size_t y = 0; y < h; ++y) { + auto dst = dbuffer; + for (size_t x = 0; x < w; ++x, ++dst) { + auto tmp = rasterUnpremultiply(*dst); + auto val = INTERPOLATE(INTERPOLATE(black, white, luma((uint8_t*)&tmp)), tmp, params->intensity); + *dst = ALPHA_BLEND(val, A(tmp)); + } + dbuffer += cmp->image.stride; + } + } + + return true; +} + + +/************************************************************************/ +/* Tritone Implementation */ +/************************************************************************/ + +static uint32_t _trintone(uint32_t s, uint32_t m, uint32_t h, int l) +{ + /* Tritone Formula: + if (L < 0.5) { (1 - 2L) * Shadow + 2L * Midtone } + else { (1 - 2(L - 0.5)) * Midtone + (2(L - 0.5)) * Highlight } + Where the L is Luminance. */ + + if (l < 128) { + auto a = std::min(l * 2, 255); + return ALPHA_BLEND(s, 255 - a) + ALPHA_BLEND(m, a); + } else { + auto a = 2 * std::max(0, l - 128); + return ALPHA_BLEND(m, 255 - a) + ALPHA_BLEND(h, a); + } +} + +bool effectTritonePrepare(RenderEffectTritone* params) +{ + params->valid = true; + return true; +} + + +bool effectTritone(SwCompositor* cmp, const RenderEffectTritone* params, bool direct) +{ + auto& bbox = cmp->bbox; + auto w = size_t(bbox.max.x - bbox.min.x); + auto h = size_t(bbox.max.y - bbox.min.y); + auto shadow = cmp->recoverSfc->join(params->shadow[0], params->shadow[1], params->shadow[2], 255); + auto midtone = cmp->recoverSfc->join(params->midtone[0], params->midtone[1], params->midtone[2], 255); + auto highlight = cmp->recoverSfc->join(params->highlight[0], params->highlight[1], params->highlight[2], 255); + auto opacity = cmp->opacity; + auto luma = cmp->recoverSfc->alphas[2]; //luma function + + TVGLOG("SW_ENGINE", "Tritone region(%ld, %ld, %ld, %ld), param(%d %d %d, %d %d %d, %d %d %d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->shadow[0], params->shadow[1], params->shadow[2], params->midtone[0], params->midtone[1], params->midtone[2], params->highlight[0], params->highlight[1], params->highlight[2]); + + if (direct) { + auto dbuffer = cmp->recoverSfc->buf32 + (bbox.min.y * cmp->recoverSfc->stride + bbox.min.x); + auto sbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x); + for (size_t y = 0; y < h; ++y) { + auto dst = dbuffer; + auto src = sbuffer; + for (size_t x = 0; x < w; ++x, ++dst, ++src) { + auto tmp = rasterUnpremultiply(*src); + *dst = INTERPOLATE(_trintone(shadow, midtone, highlight, luma((uint8_t*)&tmp)), *dst, MULTIPLY(opacity, A(tmp))); + } + dbuffer += cmp->image.stride; + sbuffer += cmp->recoverSfc->stride; + } + cmp->valid = true; //no need the subsequent composition + } else { + auto dbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x); + for (size_t y = 0; y < h; ++y) { + auto dst = dbuffer; + for (size_t x = 0; x < w; ++x, ++dst) { + auto tmp = rasterUnpremultiply(*dst); + *dst = ALPHA_BLEND(_trintone(shadow, midtone, highlight, luma((uint8_t*)&tmp)), A(tmp)); + } + dbuffer += cmp->image.stride; + } + } + + return true; +} diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp index 18ffc18e1e0..a404c124ad7 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp @@ -1667,6 +1667,20 @@ bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_ } +uint32_t rasterUnpremultiply(uint32_t data) +{ + uint8_t a = data >> 24; + if (a == 255 || a == 0) return data; + uint16_t r = ((data >> 8) & 0xff00) / a; + uint16_t g = ((data) & 0xff00) / a; + uint16_t b = ((data << 8) & 0xff00) / a; + if (r > 0xff) r = 0xff; + if (g > 0xff) g = 0xff; + if (b > 0xff) b = 0xff; + return (a << 24) | (r << 16) | (g << 8) | (b); +} + + void rasterUnpremultiply(RenderSurface* surface) { if (surface->channelSize != sizeof(uint32_t)) return; @@ -1677,20 +1691,7 @@ void rasterUnpremultiply(RenderSurface* surface) for (uint32_t y = 0; y < surface->h; y++) { auto buffer = surface->buf32 + surface->stride * y; for (uint32_t x = 0; x < surface->w; ++x) { - uint8_t a = buffer[x] >> 24; - if (a == 255) { - continue; - } else if (a == 0) { - buffer[x] = 0x00ffffff; - } else { - uint16_t r = ((buffer[x] >> 8) & 0xff00) / a; - uint16_t g = ((buffer[x]) & 0xff00) / a; - uint16_t b = ((buffer[x] << 8) & 0xff00) / a; - if (r > 0xff) r = 0xff; - if (g > 0xff) g = 0xff; - if (b > 0xff) b = 0xff; - buffer[x] = (a << 24) | (r << 16) | (g << 8) | (b); - } + buffer[x] = rasterUnpremultiply(buffer[x]); } } surface->premultiplied = false; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp index 180f3cc37a2..4a709869203 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -103,11 +103,9 @@ struct SwShapeTask : SwTask bool clip(SwRle* target) override { - if (shape.fastTrack) rleClip(target, &bbox); - else if (shape.rle) rleClip(target, shape.rle); - else return false; - - return true; + if (shape.fastTrack) return rleClip(target, &bbox); + else if (shape.rle) return rleClip(target, shape.rle); + return false; } void run(unsigned tid) override @@ -177,10 +175,8 @@ struct SwShapeTask : SwTask //Clip Path for (auto clip = clips.begin(); clip < clips.end(); ++clip) { auto clipper = static_cast(*clip); - //Clip shape rle - if (shape.rle && !clipper->clip(shape.rle)) goto err; - //Clip stroke rle - if (shape.strokeRle && !clipper->clip(shape.strokeRle)) goto err; + if (shape.rle && !clipper->clip(shape.rle)) goto err; //Clip shape rle + if (shape.strokeRle && !clipper->clip(shape.strokeRle)) goto err; //Clip stroke rle } bbox = renderRegion; //sync @@ -546,15 +542,27 @@ const RenderSurface* SwRenderer::mainSurface() } -SwSurface* SwRenderer::request(int channelSize) +SwSurface* SwRenderer::request(int channelSize, bool square) { SwSurface* cmp = nullptr; + uint32_t w, h; + + if (square) { + //Same Dimensional Size is demanded for the Post Processing Fast Flipping + w = h = std::max(surface->w, surface->h); + } else { + w = surface->w; + h = surface->h; + } //Use cached data for (auto p = compositors.begin(); p < compositors.end(); ++p) { - if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == channelSize) { - cmp = *p; - break; + auto cur = *p; + if (cur->compositor->valid && cur->compositor->image.channelSize == channelSize) { + if (w == cur->w && h == cur->h) { + cmp = *p; + break; + } } } @@ -563,15 +571,13 @@ SwSurface* SwRenderer::request(int channelSize) //Inherits attributes from main surface cmp = new SwSurface(surface); cmp->compositor = new SwCompositor; - cmp->compositor->image.data = (pixel_t*)malloc(channelSize * surface->stride * surface->h); - cmp->compositor->image.w = surface->w; - cmp->compositor->image.h = surface->h; - cmp->compositor->image.stride = surface->stride; + cmp->compositor->image.data = (pixel_t*)malloc(channelSize * w * h); + cmp->w = cmp->compositor->image.w = w; + cmp->h = cmp->compositor->image.h = h; + cmp->compositor->image.stride = w; cmp->compositor->image.direct = true; cmp->compositor->valid = true; cmp->channelSize = cmp->compositor->image.channelSize = channelSize; - cmp->w = cmp->compositor->image.w; - cmp->h = cmp->compositor->image.h; compositors.push(cmp); } @@ -583,7 +589,7 @@ SwSurface* SwRenderer::request(int channelSize) } -RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs) +RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags) { auto x = region.x; auto y = region.y; @@ -595,7 +601,7 @@ RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs) //Out of boundary if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr; - auto cmp = request(CHANNEL_SIZE(cs)); + auto cmp = request(CHANNEL_SIZE(cs), (flags & CompositionFlag::PostProcessing)); //Boundary Check if (x < 0) x = 0; @@ -630,12 +636,15 @@ bool SwRenderer::endComposite(RenderCompositor* cmp) if (!cmp) return false; auto p = static_cast(cmp); - p->valid = true; //Recover Context surface = p->recoverSfc; surface->compositor = p->recoverCmp; + //only invalid (currently used) surface can be composited + if (p->valid) return true; + p->valid = true; + //Default is alpha blending if (p->method == CompositeMethod::None) { Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; @@ -651,30 +660,47 @@ bool SwRenderer::prepare(RenderEffect* effect) switch (effect->type) { case SceneEffect::GaussianBlur: return effectGaussianBlurPrepare(static_cast(effect)); case SceneEffect::DropShadow: return effectDropShadowPrepare(static_cast(effect)); + case SceneEffect::Fill: return effectFillPrepare(static_cast(effect)); + case SceneEffect::Tint: return effectTintPrepare(static_cast(effect)); + case SceneEffect::Tritone: return effectTritonePrepare(static_cast(effect)); default: return false; } } -bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect, uint8_t opacity, bool direct) +bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) { - if (effect->invalid) return false; + if (!effect->valid) return false; auto p = static_cast(cmp); + if (p->image.channelSize != sizeof(uint32_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale Gaussian Blur!"); + return false; + } + switch (effect->type) { case SceneEffect::GaussianBlur: { - return effectGaussianBlur(p, request(surface->channelSize), static_cast(effect)); + return effectGaussianBlur(p, request(surface->channelSize, true), static_cast(effect)); } case SceneEffect::DropShadow: { - auto cmp1 = request(surface->channelSize); + auto cmp1 = request(surface->channelSize, true); cmp1->compositor->valid = false; - auto cmp2 = request(surface->channelSize); + auto cmp2 = request(surface->channelSize, true); SwSurface* surfaces[] = {cmp1, cmp2}; - auto ret = effectDropShadow(p, surfaces, static_cast(effect), opacity, direct); + auto ret = effectDropShadow(p, surfaces, static_cast(effect), direct); cmp1->compositor->valid = true; return ret; } + case SceneEffect::Fill: { + return effectFill(p, static_cast(effect), direct); + } + case SceneEffect::Tint: { + return effectTint(p, static_cast(effect), direct); + } + case SceneEffect::Tritone: { + return effectTritone(p, static_cast(effect), direct); + } default: return false; } } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h index bd6beb8d85c..02fbe3b6b8b 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h @@ -55,13 +55,13 @@ class SwRenderer : public RenderMethod bool target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs); bool mempool(bool shared); - RenderCompositor* target(const RenderRegion& region, ColorSpace cs) override; + RenderCompositor* target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags) override; bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) override; bool endComposite(RenderCompositor* cmp) override; void clearCompositors(); bool prepare(RenderEffect* effect) override; - bool effect(RenderCompositor* cmp, const RenderEffect* effect, uint8_t opacity, bool direct) override; + bool effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) override; static SwRenderer* gen(); static bool init(uint32_t threads); @@ -79,7 +79,7 @@ class SwRenderer : public RenderMethod SwRenderer(); ~SwRenderer(); - SwSurface* request(int channelSize); + SwSurface* request(int channelSize, bool square); RenderData prepareCommon(SwTask* task, const Matrix& transform, const Array& clips, uint8_t opacity, RenderUpdateFlag flags); }; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp index 3e4ad679a8a..cd9f40485ca 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp @@ -188,7 +188,6 @@ * http://www.freetype.org */ -#include #include #include #include "tvgSwCommon.h" @@ -243,8 +242,6 @@ struct RleWorker int bandSize; int bandShoot; - jmp_buf jmpBuf; - void* buffer; long bufferSize; @@ -359,7 +356,7 @@ static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoor rle->spans = static_cast(realloc(rle->spans, rle->alloc * sizeof(SwSpan))); } } - + //Clip x range SwCoord xOver = 0; if (x + aCount >= rw.cellMax.x) xOver -= (x + aCount - rw.cellMax.x); @@ -418,7 +415,7 @@ static Cell* _findCell(RleWorker& rw) pcell = &cell->next; } - if (rw.cellsCnt >= rw.maxCells) longjmp(rw.jmpBuf, 1); + if (rw.cellsCnt >= rw.maxCells) return nullptr; auto cell = rw.cells + rw.cellsCnt++; cell->x = x; @@ -431,17 +428,22 @@ static Cell* _findCell(RleWorker& rw) } -static void _recordCell(RleWorker& rw) +static bool _recordCell(RleWorker& rw) { if (rw.area | rw.cover) { auto cell = _findCell(rw); + + if (cell == nullptr) return false; + cell->area += rw.area; cell->cover += rw.cover; } + + return true; } -static void _setCell(RleWorker& rw, SwPoint pos) +static bool _setCell(RleWorker& rw, SwPoint pos) { /* Move the cell pointer to a new position. We set the `invalid' */ /* flag to indicate that the cell isn't part of those we're interested */ @@ -458,22 +460,26 @@ static void _setCell(RleWorker& rw, SwPoint pos) pos.x -= rw.cellMin.x; pos.y -= rw.cellMin.y; - if (pos.x > rw.cellMax.x) pos.x = rw.cellMax.x; + //exceptions + if (pos.x < 0) pos.x = -1; + else if (pos.x > rw.cellMax.x) pos.x = rw.cellMax.x; //Are we moving to a different cell? if (pos != rw.cellPos) { //Record the current one if it is valid - if (!rw.invalid) _recordCell(rw); + if (!rw.invalid) { + if (!_recordCell(rw)) return false; + } + rw.area = rw.cover = 0; + rw.cellPos = pos; } - - rw.area = 0; - rw.cover = 0; - rw.cellPos = pos; rw.invalid = ((unsigned)pos.y >= (unsigned)rw.cellYCnt || pos.x >= rw.cellXCnt); + + return true; } -static void _startCell(RleWorker& rw, SwPoint pos) +static bool _startCell(RleWorker& rw, SwPoint pos) { if (pos.x > rw.cellMax.x) pos.x = rw.cellMax.x; if (pos.x < rw.cellMin.x) pos.x = rw.cellMin.x; @@ -483,23 +489,27 @@ static void _startCell(RleWorker& rw, SwPoint pos) rw.cellPos = pos - rw.cellMin; rw.invalid = false; - _setCell(rw, pos); + return _setCell(rw, pos); } -static void _moveTo(RleWorker& rw, const SwPoint& to) +static bool _moveTo(RleWorker& rw, const SwPoint& to) { //record current cell, if any */ - if (!rw.invalid) _recordCell(rw); + if (!rw.invalid) { + if (!_recordCell(rw)) return false; + } //start to a new position - _startCell(rw, TRUNC(to)); + if (!_startCell(rw, TRUNC(to))) return false; rw.pos = to; + + return true; } -static void _lineTo(RleWorker& rw, const SwPoint& to) +static bool _lineTo(RleWorker& rw, const SwPoint& to) { #define SW_UDIV(a, b) \ static_cast(((unsigned long)(a) * (unsigned long)(b)) >> \ @@ -511,7 +521,7 @@ static void _lineTo(RleWorker& rw, const SwPoint& to) //vertical clipping if ((e1.y >= rw.cellMax.y && e2.y >= rw.cellMax.y) || (e1.y < rw.cellMin.y && e2.y < rw.cellMin.y)) { rw.pos = to; - return; + return true; } auto line = rw.lineStack; @@ -539,7 +549,7 @@ static void _lineTo(RleWorker& rw, const SwPoint& to) //any horizontal line } else if (diff.y == 0) { e1.x = e2.x; - _setCell(rw, e1); + if (!_setCell(rw, e1)) return false; } else if (diff.x == 0) { //vertical line up if (diff.y > 0) { @@ -549,7 +559,7 @@ static void _lineTo(RleWorker& rw, const SwPoint& to) rw.area += (f2.y - f1.y) * f1.x * 2; f1.y = 0; ++e1.y; - _setCell(rw, e1); + if (!_setCell(rw, e1)) return false; } while(e1.y != e2.y); //vertical line down } else { @@ -559,7 +569,7 @@ static void _lineTo(RleWorker& rw, const SwPoint& to) rw.area += (f2.y - f1.y) * f1.x * 2; f1.y = ONE_PIXEL; --e1.y; - _setCell(rw, e1); + if (!_setCell(rw, e1)) return false; } while(e1.y != e2.y); } //any other line @@ -612,7 +622,7 @@ static void _lineTo(RleWorker& rw, const SwPoint& to) --e1.y; } - _setCell(rw, e1); + if (!_setCell(rw, e1)) return false; } while(e1 != e2); } @@ -622,12 +632,12 @@ static void _lineTo(RleWorker& rw, const SwPoint& to) rw.area += (f2.y - f1.y) * (f1.x + f2.x); rw.pos = line[0]; - if (line-- == rw.lineStack) return; + if (line-- == rw.lineStack) return true; } } -static void _cubicTo(RleWorker& rw, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) +static bool _cubicTo(RleWorker& rw, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) { auto arc = rw.bezStack; arc[0] = to; @@ -691,14 +701,14 @@ static void _cubicTo(RleWorker& rw, const SwPoint& ctrl1, const SwPoint& ctrl2, continue; draw: - _lineTo(rw, arc[0]); - if (arc == rw.bezStack) return; + if (!_lineTo(rw, arc[0])) return false; + if (arc == rw.bezStack) return true; arc -= 3; } } -static void _decomposeOutline(RleWorker& rw) +static bool _decomposeOutline(RleWorker& rw) { auto outline = rw.outline; auto first = 0; //index of first point in contour @@ -711,38 +721,43 @@ static void _decomposeOutline(RleWorker& rw) auto types = outline->types.data + first; ++types; - _moveTo(rw, UPSCALE(outline->pts[first])); + if (!_moveTo(rw, UPSCALE(outline->pts[first]))) return false; while (pt < limit) { //emit a single line_to if (types[0] == SW_CURVE_TYPE_POINT) { ++pt; ++types; - _lineTo(rw, UPSCALE(*pt)); + if (!_lineTo(rw, UPSCALE(*pt))) return false; //types cubic } else { pt += 3; types += 3; - if (pt <= limit) _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), UPSCALE(pt[0])); - else if (pt - 1 == limit) _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), start); + if (pt <= limit) { + if (!_cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), UPSCALE(pt[0]))) return false; + } + else if (pt - 1 == limit) { + if (!_cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), start)) return false; + } else goto close; } } close: - _lineTo(rw, start); + if (!_lineTo(rw, start)) return false; first = last + 1; } + + return true; } static int _genRle(RleWorker& rw) { - if (setjmp(rw.jmpBuf) == 0) { - _decomposeOutline(rw); - if (!rw.invalid) _recordCell(rw); - return 0; + if (!_decomposeOutline(rw)) return -1; + if (!rw.invalid) { + if (!_recordCell(rw)) return -1; } - return -1; //lack of cell memory + return 0; } @@ -1005,9 +1020,9 @@ void rleFree(SwRle* rle) } -void rleClip(SwRle *rle, const SwRle *clip) +bool rleClip(SwRle *rle, const SwRle *clip) { - if (rle->size == 0 || clip->size == 0) return; + if (rle->size == 0 || clip->size == 0) return false; auto spanCnt = rle->size > clip->size ? rle->size : clip->size; auto spans = static_cast(malloc(sizeof(SwSpan) * (spanCnt))); auto spansEnd = _intersectSpansRegion(clip, rle, spans, spanCnt); @@ -1015,16 +1030,21 @@ void rleClip(SwRle *rle, const SwRle *clip) _replaceClipSpan(rle, spans, spansEnd - spans); TVGLOG("SW_ENGINE", "Using Path Clipping!"); + + return true; } -void rleClip(SwRle *rle, const SwBBox* clip) +bool rleClip(SwRle *rle, const SwBBox* clip) { - if (rle->size == 0) return; + if (rle->size == 0) return false; auto spans = static_cast(malloc(sizeof(SwSpan) * (rle->size))); auto spansEnd = _intersectSpansRect(clip, rle, spans, rle->size); _replaceClipSpan(rle, spans, spansEnd - spans); TVGLOG("SW_ENGINE", "Using Box Clipping!"); + + return true; } + diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp index e195f72adf2..fcc78b4bbb1 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp @@ -374,8 +374,12 @@ static void _lineTo(SwStroke& stroke, const SwPoint& to) { auto delta = to - stroke.center; - //a zero-length lineto is a no-op; avoid creating a spurious corner - if (delta.zero()) return; + //a zero-length lineto is a no-op + if (delta.zero()) { + //round and square caps are expected to be drawn as a dot even for zero-length lines + if (stroke.firstPt && stroke.cap != StrokeCap::Butt) _firstSubPath(stroke, 0, 0); + return; + } /* The lineLength is used to determine the intersection of strokes outlines. The scale needs to be reverted since the stroke width has not been scaled. @@ -454,6 +458,9 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl //ignoreable size if (valid < 0 && arc == bezStack) { stroke.center = to; + + //round and square caps are expected to be drawn as a dot even for zero-length lines + if (stroke.firstPt && stroke.cap != StrokeCap::Butt) _firstSubPath(stroke, 0, 0); return; } diff --git a/thirdparty/thorvg/src/renderer/tvgAccessor.cpp b/thirdparty/thorvg/src/renderer/tvgAccessor.cpp index a1447268047..21caa2c2536 100644 --- a/thirdparty/thorvg/src/renderer/tvgAccessor.cpp +++ b/thirdparty/thorvg/src/renderer/tvgAccessor.cpp @@ -64,17 +64,17 @@ TVG_DEPRECATED unique_ptr Accessor::set(unique_ptr picture, fu } -Result Accessor::set(const Picture* picture, function func, void* data) noexcept +Result Accessor::set(Paint* paint, function func, void* data) noexcept { - if (!picture || !func) return Result::InvalidArguments; + if (!paint || !func) return Result::InvalidArguments; - //Use the Preorder Tree-Search + //Use the Preorder Tree-Searc //Root - if (!func(picture, data)) return Result::Success; + if (!func(paint, data)) return Result::Success; //Children - if (auto it = IteratorAccessor::iterator(picture)) { + if (auto it = IteratorAccessor::iterator(paint)) { accessChildren(it, func, data); delete(it); } diff --git a/thirdparty/thorvg/src/renderer/tvgLoader.cpp b/thirdparty/thorvg/src/renderer/tvgLoader.cpp index db51fc215a0..e69fb678a3a 100644 --- a/thirdparty/thorvg/src/renderer/tvgLoader.cpp +++ b/thirdparty/thorvg/src/renderer/tvgLoader.cpp @@ -172,6 +172,7 @@ static LoadModule* _find(FileType type) } +#ifdef THORVG_FILE_IO_SUPPORT static LoadModule* _findByPath(const string& path) { auto ext = path.substr(path.find_last_of(".") + 1); @@ -185,6 +186,7 @@ static LoadModule* _findByPath(const string& path) if (!ext.compare("otf") || !ext.compare("otc")) return _find(FileType::Ttf); return nullptr; } +#endif static FileType _convert(const string& mimeType) @@ -292,6 +294,7 @@ bool LoaderMgr::retrieve(LoadModule* loader) LoadModule* LoaderMgr::loader(const string& path, bool* invalid) { +#ifdef THORVG_FILE_IO_SUPPORT *invalid = false; //TODO: svg & lottie is not sharable. @@ -335,6 +338,7 @@ LoadModule* LoaderMgr::loader(const string& path, bool* invalid) } } *invalid = true; +#endif return nullptr; } diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.cpp b/thirdparty/thorvg/src/renderer/tvgPaint.cpp index 536e1878521..e00c2c1954e 100644 --- a/thirdparty/thorvg/src/renderer/tvgPaint.cpp +++ b/thirdparty/thorvg/src/renderer/tvgPaint.cpp @@ -216,7 +216,7 @@ bool Paint::Impl::render(RenderMethod* renderer) if (MASK_REGION_MERGING(compData->method)) region.add(P(compData->target)->bounds(renderer)); if (region.w == 0 || region.h == 0) return true; - cmp = renderer->target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method)); + cmp = renderer->target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method), CompositionFlag::Masking); if (renderer->beginComposite(cmp, CompositeMethod::None, 255)) { compData->target->pImpl->render(renderer); } @@ -374,7 +374,7 @@ void Paint::Impl::reset() blendMethod = BlendMethod::Normal; renderFlag = RenderUpdateFlag::None; - ctxFlag = ContextFlag::Invalid; + ctxFlag = ContextFlag::Default; opacity = 255; paint->id = 0; } diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.h b/thirdparty/thorvg/src/renderer/tvgPaint.h index d78e9bb3d1c..149dc0e0b51 100644 --- a/thirdparty/thorvg/src/renderer/tvgPaint.h +++ b/thirdparty/thorvg/src/renderer/tvgPaint.h @@ -28,7 +28,7 @@ namespace tvg { - enum ContextFlag : uint8_t {Invalid = 0, FastTrack = 1}; + enum ContextFlag : uint8_t {Default = 0, FastTrack = 1}; struct Iterator { diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.cpp b/thirdparty/thorvg/src/renderer/tvgPicture.cpp index d3e31d198a3..a268f0ed201 100644 --- a/thirdparty/thorvg/src/renderer/tvgPicture.cpp +++ b/thirdparty/thorvg/src/renderer/tvgPicture.cpp @@ -56,18 +56,20 @@ RenderUpdateFlag Picture::Impl::load() } -bool Picture::Impl::needComposition(uint8_t opacity) +void Picture::Impl::queryComposition(uint8_t opacity) { + cFlag = CompositionFlag::Invalid; + //In this case, paint(scene) would try composition itself. - if (opacity < 255) return false; + if (opacity < 255) return; //Composition test const Paint* target; auto method = picture->composite(&target); - if (!target || method == tvg::CompositeMethod::ClipPath) return false; - if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return false; + if (!target || method == tvg::CompositeMethod::ClipPath) return; + if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return; - return true; + cFlag = CompositionFlag::Opacity; } @@ -79,8 +81,8 @@ bool Picture::Impl::render(RenderMethod* renderer) if (surface) return renderer->renderImage(rd); else if (paint) { RenderCompositor* cmp = nullptr; - if (needComp) { - cmp = renderer->target(bounds(renderer), renderer->colorSpace()); + if (cFlag) { + cmp = renderer->target(bounds(renderer), renderer->colorSpace(), static_cast(cFlag)); renderer->beginComposite(cmp, CompositeMethod::None, 255); } ret = paint->pImpl->render(renderer); @@ -164,9 +166,14 @@ Type Picture::type() const noexcept Result Picture::load(const std::string& path) noexcept { +#ifdef THORVG_FILE_IO_SUPPORT if (path.empty()) return Result::InvalidArguments; return pImpl->load(path); +#else + TVGLOG("RENDERER", "FILE IO is disabled!"); + return Result::NonSupport; +#endif } diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.h b/thirdparty/thorvg/src/renderer/tvgPicture.h index bbbc4391059..4b75b362497 100644 --- a/thirdparty/thorvg/src/renderer/tvgPicture.h +++ b/thirdparty/thorvg/src/renderer/tvgPicture.h @@ -64,10 +64,10 @@ struct Picture::Impl RenderData rd = nullptr; //engine data float w = 0, h = 0; Picture* picture = nullptr; + uint8_t cFlag = CompositionFlag::Invalid; bool resizing = false; - bool needComp = false; //need composition - bool needComposition(uint8_t opacity); + void queryComposition(uint8_t opacity); bool render(RenderMethod* renderer); bool size(float w, float h); RenderRegion bounds(RenderMethod* renderer); @@ -107,7 +107,7 @@ struct Picture::Impl loader->resize(paint, w, h); resizing = false; } - needComp = needComposition(opacity) ? true : false; + queryComposition(opacity); rd = paint->pImpl->update(renderer, transform, clips, opacity, flag, false); } return rd; diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h index eae44a2e8ad..6558bd3ed05 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.h +++ b/thirdparty/thorvg/src/renderer/tvgRender.h @@ -36,6 +36,7 @@ using RenderData = void*; using pixel_t = uint32_t; enum RenderUpdateFlag : uint8_t {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, Blend = 128, All = 255}; +enum CompositionFlag : uint8_t {Invalid = 0, Opacity = 1, Blending = 2, Masking = 4, PostProcessing = 8}; //Composition Purpose //TODO: Move this in public header unifying with SwCanvas::Colorspace enum ColorSpace : uint8_t @@ -137,6 +138,7 @@ struct RenderStroke dashPattern = nullptr; } dashCnt = rhs.dashCnt; + dashOffset = rhs.dashOffset; miterlimit = rhs.miterlimit; cap = rhs.cap; join = rhs.join; @@ -268,7 +270,7 @@ struct RenderEffect RenderData rd = nullptr; RenderRegion extend = {0, 0, 0, 0}; SceneEffect type; - bool invalid = false; + bool valid = false; virtual ~RenderEffect() { @@ -309,7 +311,7 @@ struct RenderEffectDropShadow : RenderEffect inst->color[0] = va_arg(args, int); inst->color[1] = va_arg(args, int); inst->color[2] = va_arg(args, int); - inst->color[3] = std::min(va_arg(args, int), 255); + inst->color[3] = va_arg(args, int); inst->angle = (float) va_arg(args, double); inst->distance = (float) va_arg(args, double); inst->sigma = std::max((float) va_arg(args, double), 0.0f); @@ -319,6 +321,66 @@ struct RenderEffectDropShadow : RenderEffect } }; +struct RenderEffectFill : RenderEffect +{ + uint8_t color[4]; //rgba + + static RenderEffectFill* gen(va_list& args) + { + auto inst = new RenderEffectFill; + inst->color[0] = va_arg(args, int); + inst->color[1] = va_arg(args, int); + inst->color[2] = va_arg(args, int); + inst->color[3] = va_arg(args, int); + inst->type = SceneEffect::Fill; + return inst; + } +}; + +struct RenderEffectTint : RenderEffect +{ + uint8_t black[3]; //rgb + uint8_t white[3]; //rgb + uint8_t intensity; //0 - 255 + + static RenderEffectTint* gen(va_list& args) + { + auto inst = new RenderEffectTint; + inst->black[0] = va_arg(args, int); + inst->black[1] = va_arg(args, int); + inst->black[2] = va_arg(args, int); + inst->white[0] = va_arg(args, int); + inst->white[1] = va_arg(args, int); + inst->white[2] = va_arg(args, int); + inst->intensity = (uint8_t)(va_arg(args, double) * 2.55f); + inst->type = SceneEffect::Tint; + return inst; + } +}; + +struct RenderEffectTritone : RenderEffect +{ + uint8_t shadow[3]; //rgb + uint8_t midtone[3]; //rgb + uint8_t highlight[3]; //rgb + + static RenderEffectTritone* gen(va_list& args) + { + auto inst = new RenderEffectTritone; + inst->shadow[0] = va_arg(args, int); + inst->shadow[1] = va_arg(args, int); + inst->shadow[2] = va_arg(args, int); + inst->midtone[0] = va_arg(args, int); + inst->midtone[1] = va_arg(args, int); + inst->midtone[2] = va_arg(args, int); + inst->highlight[0] = va_arg(args, int); + inst->highlight[1] = va_arg(args, int); + inst->highlight[2] = va_arg(args, int); + inst->type = SceneEffect::Tritone; + return inst; + } +}; + class RenderMethod { private: @@ -347,12 +409,12 @@ class RenderMethod virtual bool clear() = 0; virtual bool sync() = 0; - virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs) = 0; + virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags) = 0; virtual bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) = 0; virtual bool endComposite(RenderCompositor* cmp) = 0; virtual bool prepare(RenderEffect* effect) = 0; - virtual bool effect(RenderCompositor* cmp, const RenderEffect* effect, uint8_t opacity, bool direct) = 0; + virtual bool effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) = 0; }; static inline bool MASK_REGION_MERGING(CompositeMethod method) diff --git a/thirdparty/thorvg/src/renderer/tvgScene.cpp b/thirdparty/thorvg/src/renderer/tvgScene.cpp index ce169d33ba6..9e9c3421e0d 100644 --- a/thirdparty/thorvg/src/renderer/tvgScene.cpp +++ b/thirdparty/thorvg/src/renderer/tvgScene.cpp @@ -128,6 +128,18 @@ Result Scene::push(SceneEffect effect, ...) noexcept re = RenderEffectDropShadow::gen(args); break; } + case SceneEffect::Fill: { + re = RenderEffectFill::gen(args); + break; + } + case SceneEffect::Tint: { + re = RenderEffectTint::gen(args); + break; + } + case SceneEffect::Tritone: { + re = RenderEffectTritone::gen(args); + break; + } default: break; } diff --git a/thirdparty/thorvg/src/renderer/tvgScene.h b/thirdparty/thorvg/src/renderer/tvgScene.h index 7972ae33fb1..bc789584ef5 100644 --- a/thirdparty/thorvg/src/renderer/tvgScene.h +++ b/thirdparty/thorvg/src/renderer/tvgScene.h @@ -62,8 +62,8 @@ struct Scene::Impl Scene* scene = nullptr; RenderRegion vport = {0, 0, INT32_MAX, INT32_MAX}; Array* effects = nullptr; + uint8_t compFlag = CompositionFlag::Invalid; uint8_t opacity; //for composition - bool needComp = false; //composite or not Impl(Scene* s) : scene(s) { @@ -82,36 +82,36 @@ struct Scene::Impl } } - bool needComposition(uint8_t opacity) + uint8_t needComposition(uint8_t opacity) { - if (opacity == 0 || paints.empty()) return false; + compFlag = CompositionFlag::Invalid; - //post effects requires composition - if (effects) return true; + if (opacity == 0 || paints.empty()) return 0; - //Masking may require composition (even if opacity == 255) + //post effects, masking, blending may require composition + if (effects) compFlag |= CompositionFlag::PostProcessing; auto compMethod = scene->composite(nullptr); - if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) return true; - - //Blending may require composition (even if opacity == 255) - if (PP(scene)->blendMethod != BlendMethod::Normal) return true; + if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) compFlag |= CompositionFlag::Masking; + if (PP(scene)->blendMethod != BlendMethod::Normal) compFlag |= CompositionFlag::Blending; //Half translucent requires intermediate composition. - if (opacity == 255) return false; + if (opacity == 255) return compFlag; //If scene has several children or only scene, it may require composition. //OPTIMIZE: the bitmap type of the picture would not need the composition. //OPTIMIZE: a single paint of a scene would not need the composition. - if (paints.size() == 1 && paints.front()->type() == Type::Shape) return false; + if (paints.size() == 1 && paints.front()->type() == Type::Shape) return compFlag; - return true; + compFlag |= CompositionFlag::Opacity; + + return 1; } RenderData update(RenderMethod* renderer, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag flag, TVG_UNUSED bool clipper) { this->vport = renderer->viewport(); - if ((needComp = needComposition(opacity))) { + if (needComposition(opacity)) { /* Overriding opacity value. If this scene is half-translucent, It must do intermediate composition with that opacity value. */ this->opacity = opacity; @@ -131,8 +131,8 @@ struct Scene::Impl renderer->blend(PP(scene)->blendMethod); - if (needComp) { - cmp = renderer->target(bounds(renderer), renderer->colorSpace()); + if (compFlag) { + cmp = renderer->target(bounds(renderer), renderer->colorSpace(), static_cast(compFlag)); renderer->beginComposite(cmp, CompositeMethod::None, opacity); } @@ -143,9 +143,10 @@ struct Scene::Impl if (cmp) { //Apply post effects if any. if (effects) { - auto direct = effects->count == 1 ? true : false; + //Notify the possiblity of the direct composition of the effect result to the origin surface. + auto direct = (effects->count == 1) & (compFlag == CompositionFlag::PostProcessing); for (auto e = effects->begin(); e < effects->end(); ++e) { - renderer->effect(cmp, *e, opacity, direct); + renderer->effect(cmp, *e, direct); } } renderer->endComposite(cmp); @@ -178,7 +179,7 @@ struct Scene::Impl if (effects) { for (auto e = effects->begin(); e < effects->end(); ++e) { auto effect = *e; - if (effect->rd || renderer->prepare(effect)) { + if (effect->valid || renderer->prepare(effect)) { ex = std::min(ex, effect->extend.x); ey = std::min(ey, effect->extend.y); ew = std::max(ew, effect->extend.w); diff --git a/thirdparty/thorvg/src/renderer/tvgShape.cpp b/thirdparty/thorvg/src/renderer/tvgShape.cpp index 269d951f05a..f3091337aa5 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.cpp +++ b/thirdparty/thorvg/src/renderer/tvgShape.cpp @@ -66,7 +66,7 @@ Result Shape::reset() noexcept pImpl->rs.path.cmds.clear(); pImpl->rs.path.pts.clear(); - pImpl->flag |= RenderUpdateFlag::Path; + pImpl->rFlag |= RenderUpdateFlag::Path; return Result::Success; } @@ -93,7 +93,7 @@ Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point* pImpl->grow(cmdCnt, ptsCnt); pImpl->append(cmds, cmdCnt, pts, ptsCnt); - pImpl->flag |= RenderUpdateFlag::Path; + pImpl->rFlag |= RenderUpdateFlag::Path; return Result::Success; } @@ -111,7 +111,7 @@ Result Shape::lineTo(float x, float y) noexcept { pImpl->lineTo(x, y); - pImpl->flag |= RenderUpdateFlag::Path; + pImpl->rFlag |= RenderUpdateFlag::Path; return Result::Success; } @@ -121,7 +121,7 @@ Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float { pImpl->cubicTo(cx1, cy1, cx2, cy2, x, y); - pImpl->flag |= RenderUpdateFlag::Path; + pImpl->rFlag |= RenderUpdateFlag::Path; return Result::Success; } @@ -131,7 +131,7 @@ Result Shape::close() noexcept { pImpl->close(); - pImpl->flag |= RenderUpdateFlag::Path; + pImpl->rFlag |= RenderUpdateFlag::Path; return Result::Success; } @@ -150,7 +150,7 @@ Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept pImpl->cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy); pImpl->close(); - pImpl->flag |= RenderUpdateFlag::Path; + pImpl->rFlag |= RenderUpdateFlag::Path; return Result::Success; } @@ -212,7 +212,7 @@ TVG_DEPRECATED Result Shape::appendArc(float cx, float cy, float radius, float s if (pie) pImpl->close(); - pImpl->flag |= RenderUpdateFlag::Path; + pImpl->rFlag |= RenderUpdateFlag::Path; return Result::Success; } @@ -252,7 +252,7 @@ Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry) pImpl->close(); } - pImpl->flag |= RenderUpdateFlag::Path; + pImpl->rFlag |= RenderUpdateFlag::Path; return Result::Success; } @@ -263,7 +263,7 @@ Result Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept if (pImpl->rs.fill) { delete(pImpl->rs.fill); pImpl->rs.fill = nullptr; - pImpl->flag |= RenderUpdateFlag::Gradient; + pImpl->rFlag |= RenderUpdateFlag::Gradient; } if (r == pImpl->rs.color[0] && g == pImpl->rs.color[1] && b == pImpl->rs.color[2] && a == pImpl->rs.color[3]) return Result::Success; @@ -272,7 +272,7 @@ Result Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept pImpl->rs.color[1] = g; pImpl->rs.color[2] = b; pImpl->rs.color[3] = a; - pImpl->flag |= RenderUpdateFlag::Color; + pImpl->rFlag |= RenderUpdateFlag::Color; return Result::Success; } @@ -285,7 +285,7 @@ Result Shape::fill(unique_ptr f) noexcept if (pImpl->rs.fill && pImpl->rs.fill != p) delete(pImpl->rs.fill); pImpl->rs.fill = p; - pImpl->flag |= RenderUpdateFlag::Gradient; + pImpl->rFlag |= RenderUpdateFlag::Gradient; return Result::Success; } diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h index 42f81520606..a19b8d6ca62 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.h +++ b/thirdparty/thorvg/src/renderer/tvgShape.h @@ -33,10 +33,9 @@ struct Shape::Impl RenderShape rs; //shape data RenderData rd = nullptr; //engine data Shape* shape; - uint8_t flag = RenderUpdateFlag::None; - + uint8_t rFlag = RenderUpdateFlag::None; + uint8_t cFlag = CompositionFlag::Invalid; uint8_t opacity; //for composition - bool needComp = false; //composite or not Impl(Shape* s) : shape(s) { @@ -57,8 +56,8 @@ struct Shape::Impl renderer->blend(PP(shape)->blendMethod); - if (needComp) { - cmp = renderer->target(bounds(renderer), renderer->colorSpace()); + if (cFlag) { + cmp = renderer->target(bounds(renderer), renderer->colorSpace(), static_cast(cFlag)); renderer->beginComposite(cmp, CompositeMethod::None, opacity); } @@ -69,6 +68,8 @@ struct Shape::Impl bool needComposition(uint8_t opacity) { + cFlag = CompositionFlag::Invalid; + if (opacity == 0) return false; //Shape composition is only necessary when stroking & fill are valid. @@ -76,7 +77,10 @@ struct Shape::Impl if (!rs.fill && rs.color[3] == 0) return false; //translucent fill & stroke - if (opacity < 255) return true; + if (opacity < 255) { + cFlag = CompositionFlag::Opacity; + return true; + } //Composition test const Paint* target; @@ -97,22 +101,23 @@ struct Shape::Impl } } + cFlag = CompositionFlag::Masking; return true; } RenderData update(RenderMethod* renderer, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) { - if (static_cast(pFlag | flag) == RenderUpdateFlag::None) return rd; + if (static_cast(pFlag | rFlag) == RenderUpdateFlag::None) return rd; - if ((needComp = needComposition(opacity))) { + if (needComposition(opacity)) { /* Overriding opacity value. If this scene is half-translucent, It must do intermediate composition with that opacity value. */ this->opacity = opacity; opacity = 255; } - rd = renderer->prepare(rs, rd, transform, clips, opacity, static_cast(pFlag | flag), clipper); - flag = RenderUpdateFlag::None; + rd = renderer->prepare(rs, rd, transform, clips, opacity, static_cast(pFlag | rFlag), clipper); + rFlag = RenderUpdateFlag::None; return rd; } @@ -209,7 +214,7 @@ struct Shape::Impl { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->width = width; - flag |= RenderUpdateFlag::Stroke; + rFlag |= RenderUpdateFlag::Stroke; } void strokeTrim(float begin, float end, bool simultaneous) @@ -225,7 +230,7 @@ struct Shape::Impl rs.stroke->trim.begin = begin; rs.stroke->trim.end = end; rs.stroke->trim.simultaneous = simultaneous; - flag |= RenderUpdateFlag::Stroke; + rFlag |= RenderUpdateFlag::Stroke; } bool strokeTrim(float* begin, float* end) @@ -245,21 +250,21 @@ struct Shape::Impl { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->cap = cap; - flag |= RenderUpdateFlag::Stroke; + rFlag |= RenderUpdateFlag::Stroke; } void strokeJoin(StrokeJoin join) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->join = join; - flag |= RenderUpdateFlag::Stroke; + rFlag |= RenderUpdateFlag::Stroke; } void strokeMiterlimit(float miterlimit) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->miterlimit = miterlimit; - flag |= RenderUpdateFlag::Stroke; + rFlag |= RenderUpdateFlag::Stroke; } void strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) @@ -268,7 +273,7 @@ struct Shape::Impl if (rs.stroke->fill) { delete(rs.stroke->fill); rs.stroke->fill = nullptr; - flag |= RenderUpdateFlag::GradientStroke; + rFlag |= RenderUpdateFlag::GradientStroke; } rs.stroke->color[0] = r; @@ -276,7 +281,7 @@ struct Shape::Impl rs.stroke->color[2] = b; rs.stroke->color[3] = a; - flag |= RenderUpdateFlag::Stroke; + rFlag |= RenderUpdateFlag::Stroke; } Result strokeFill(unique_ptr f) @@ -289,8 +294,8 @@ struct Shape::Impl rs.stroke->fill = p; rs.stroke->color[3] = 0; - flag |= RenderUpdateFlag::Stroke; - flag |= RenderUpdateFlag::GradientStroke; + rFlag |= RenderUpdateFlag::Stroke; + rFlag |= RenderUpdateFlag::GradientStroke; return Result::Success; } @@ -325,7 +330,7 @@ struct Shape::Impl } rs.stroke->dashCnt = cnt; rs.stroke->dashOffset = offset; - flag |= RenderUpdateFlag::Stroke; + rFlag |= RenderUpdateFlag::Stroke; return Result::Success; } @@ -340,12 +345,12 @@ struct Shape::Impl { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->strokeFirst = strokeFirst; - flag |= RenderUpdateFlag::Stroke; + rFlag |= RenderUpdateFlag::Stroke; } void update(RenderUpdateFlag flag) { - this->flag |= flag; + rFlag |= flag; } Paint* duplicate(Paint* ret) @@ -358,7 +363,7 @@ struct Shape::Impl delete(dup->rs.fill); //Default Properties - dup->flag = RenderUpdateFlag::All; + dup->rFlag = RenderUpdateFlag::All; dup->rs.rule = rs.rule; //Color diff --git a/thirdparty/thorvg/src/renderer/tvgText.cpp b/thirdparty/thorvg/src/renderer/tvgText.cpp index b324b95049e..a09bb302e57 100644 --- a/thirdparty/thorvg/src/renderer/tvgText.cpp +++ b/thirdparty/thorvg/src/renderer/tvgText.cpp @@ -60,13 +60,17 @@ Result Text::font(const char* name, float size, const char* style) noexcept Result Text::load(const std::string& path) noexcept { +#ifdef THORVG_FILE_IO_SUPPORT bool invalid; //invalid path if (!LoaderMgr::loader(path, &invalid)) { if (invalid) return Result::InvalidArguments; else return Result::NonSupport; } - return Result::Success; +#else + TVGLOG("RENDERER", "FILE IO is disabled!"); + return Result::NonSupport; +#endif } @@ -87,8 +91,13 @@ Result Text::load(const char* name, const char* data, uint32_t size, const strin Result Text::unload(const std::string& path) noexcept { +#ifdef THORVG_FILE_IO_SUPPORT if (LoaderMgr::retrieve(path)) return Result::Success; return Result::InsufficientCondition; +#else + TVGLOG("RENDERER", "FILE IO is disabled!"); + return Result::NonSupport; +#endif } diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h index 11e01b58ce6..d92495ef714 100644 --- a/thirdparty/thorvg/src/renderer/tvgText.h +++ b/thirdparty/thorvg/src/renderer/tvgText.h @@ -113,7 +113,7 @@ struct Text::Impl //transform the gradient coordinates based on the final scaled font. auto fill = P(shape)->rs.fill; - if (fill && P(shape)->flag & RenderUpdateFlag::Gradient) { + if (fill && P(shape)->rFlag & RenderUpdateFlag::Gradient) { auto scale = 1.0f / loader->scale; if (fill->type() == Type::LinearGradient) { P(static_cast(fill))->x1 *= scale; diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index f9953f2fc9e..5aeac089395 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -VERSION=0.15.5 +VERSION=0.15.8 # Uncomment and set a git hash to use specific commit instead of tag. #GIT_COMMIT=